feat(k1): recover exact application bootstrap
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
|
||||
ApplicationBootstrapError,
|
||||
ApplicationControlAuthority,
|
||||
LiveDeviceControlBinding,
|
||||
build_shadow_bootstrap,
|
||||
decode_and_bind_device_info_response,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.modeling_control import OPENAPI_SUCCESS
|
||||
from k1link.device_plugins.xgrids_k1.protocol.protobuf_wire import iter_fields
|
||||
|
||||
APPLICATION_KEY = "11111111-2222-3333-4444-555555555555"
|
||||
VENDOR_DEVICE_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||
|
||||
|
||||
def _varint(value: int) -> bytes:
|
||||
encoded = bytearray()
|
||||
while value > 0x7F:
|
||||
encoded.append((value & 0x7F) | 0x80)
|
||||
value >>= 7
|
||||
encoded.append(value)
|
||||
return bytes(encoded)
|
||||
|
||||
|
||||
def _uint(number: int, value: int) -> bytes:
|
||||
return _varint(number << 3) + _varint(value)
|
||||
|
||||
|
||||
def _bytes(number: int, value: bytes) -> bytes:
|
||||
return _varint((number << 3) | 2) + _varint(len(value)) + value
|
||||
|
||||
|
||||
def _text(number: int, value: str) -> bytes:
|
||||
return _bytes(number, value.encode())
|
||||
|
||||
|
||||
def _authority() -> ApplicationControlAuthority:
|
||||
return ApplicationControlAuthority(openapi_key=APPLICATION_KEY)
|
||||
|
||||
|
||||
def _binding(**changes: object) -> LiveDeviceControlBinding:
|
||||
values = {
|
||||
"vendor_device_id": VENDOR_DEVICE_ID,
|
||||
"device_serial": "K1SERIAL01",
|
||||
"software_version": "V3.0.2-20260101-release",
|
||||
"system_version": "V3.0.2",
|
||||
"device_model": "LixelKity K1",
|
||||
"device_type": "K1",
|
||||
"is_activated": True,
|
||||
}
|
||||
values.update(changes)
|
||||
return LiveDeviceControlBinding(**values) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def _device_info_response(*, session_id: str = ":DeviceInfoRequest") -> bytes:
|
||||
header = _text(4, VENDOR_DEVICE_ID) + _text(5, session_id) + _text(6, APPLICATION_KEY)
|
||||
base_info = b"".join(
|
||||
(
|
||||
_text(1, "2026-01-01T00:00:00"),
|
||||
_text(2, "V3.0.2-20260101-release"),
|
||||
_text(3, "V3.0.2"),
|
||||
_text(4, "V1.2.3"),
|
||||
_text(5, "V1.0"),
|
||||
_text(6, "LixelKity K1"),
|
||||
_text(7, "K1SERIAL01"),
|
||||
_text(8, "K1"),
|
||||
)
|
||||
)
|
||||
working_status = _uint(1, 1) + _uint(2, 0)
|
||||
device_info = _bytes(2, base_info) + _bytes(7, working_status)
|
||||
error = _uint(1, OPENAPI_SUCCESS) + _text(2, "success")
|
||||
return _bytes(1, header) + _bytes(2, device_info) + _bytes(15, error)
|
||||
|
||||
|
||||
def test_exact_clean_cycle_pre_start_order_and_wire_lengths() -> None:
|
||||
plan = build_shadow_bootstrap(
|
||||
_authority(),
|
||||
_binding(),
|
||||
epoch_seconds=1_752_680_000,
|
||||
timezone_name="Europe/Moscow",
|
||||
)
|
||||
|
||||
assert [request.message_type for request in plan.requests] == [
|
||||
"DeviceInfoRequest",
|
||||
"ModelingStatusRequest",
|
||||
"GetRtkAdvanceRequest",
|
||||
"DeviceConfigRequest",
|
||||
"DeviceInfoRequest",
|
||||
"GetRtkAdvanceRequest",
|
||||
"GetNtripProfileRequest",
|
||||
"GetCloudServerConfigRequest",
|
||||
"GetRtkAdvanceRequest",
|
||||
"DeviceInfoRequest",
|
||||
]
|
||||
assert [request.payload_bytes for request in plan.requests] == [
|
||||
60,
|
||||
64,
|
||||
63,
|
||||
195,
|
||||
135,
|
||||
138,
|
||||
140,
|
||||
145,
|
||||
138,
|
||||
135,
|
||||
]
|
||||
assert sum(request.mutates_device for request in plan.requests) == 1
|
||||
assert plan.requests[3].mutates_device
|
||||
assert plan.executable is False
|
||||
assert plan.blockers == ("vendor-writes-disabled", "publisher-not-installed")
|
||||
assert plan.retry_policy == "never-automatic"
|
||||
assert APPLICATION_KEY not in repr(plan)
|
||||
assert APPLICATION_KEY not in str(plan.as_dict())
|
||||
|
||||
|
||||
def test_bootstrap_headers_switch_from_unbound_to_live_device_identity() -> None:
|
||||
plan = build_shadow_bootstrap(
|
||||
_authority(),
|
||||
_binding(),
|
||||
epoch_seconds=1_752_680_000,
|
||||
timezone_name="Europe/Moscow",
|
||||
)
|
||||
|
||||
first_header = next(iter_fields(plan.requests[0].payload)).value
|
||||
assert isinstance(first_header, bytes)
|
||||
first_fields = {field.number: field.value for field in iter_fields(first_header)}
|
||||
assert 4 not in first_fields
|
||||
assert first_fields[5] == b":DeviceInfoRequest"
|
||||
|
||||
config_header = next(iter_fields(plan.requests[3].payload)).value
|
||||
assert isinstance(config_header, bytes)
|
||||
config_fields = {field.number: field.value for field in iter_fields(config_header)}
|
||||
assert config_fields[4] == VENDOR_DEVICE_ID.encode()
|
||||
assert (
|
||||
config_fields[5]
|
||||
== (f"{VENDOR_DEVICE_ID}:DeviceConfigRequest:Publish_Proto_DeviceConfig_SetTime").encode()
|
||||
)
|
||||
|
||||
|
||||
def test_device_info_response_produces_live_binding_without_a_saved_device_profile() -> None:
|
||||
response = decode_and_bind_device_info_response(_device_info_response(), _authority())
|
||||
|
||||
assert response.result_code == OPENAPI_SUCCESS
|
||||
assert response.binding.ready_for_reviewed_profile
|
||||
assert response.binding.matches_reviewed_firmware
|
||||
assert response.binding.is_activated
|
||||
assert VENDOR_DEVICE_ID not in repr(response)
|
||||
assert APPLICATION_KEY not in repr(response)
|
||||
|
||||
|
||||
def test_device_info_response_correlation_and_profile_attestation_fail_closed() -> None:
|
||||
with pytest.raises(ApplicationBootstrapError, match="session correlation"):
|
||||
decode_and_bind_device_info_response(
|
||||
_device_info_response(session_id=":AnotherRequest"),
|
||||
_authority(),
|
||||
)
|
||||
|
||||
with pytest.raises(ApplicationBootstrapError, match="does not attest"):
|
||||
build_shadow_bootstrap(
|
||||
_authority(),
|
||||
_binding(system_version="V3.0.3"),
|
||||
epoch_seconds=1_752_680_000,
|
||||
timezone_name="Europe/Moscow",
|
||||
)
|
||||
@@ -2,6 +2,10 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
|
||||
ApplicationControlAuthority,
|
||||
LiveDeviceControlBinding,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.modeling_control import (
|
||||
MODELING_STATE_BASE,
|
||||
ModelingAction,
|
||||
@@ -9,7 +13,6 @@ from k1link.device_plugins.xgrids_k1.protocol.modeling_control import (
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.protocol.modeling_safety import (
|
||||
DEVICE_STATUS_TOPIC,
|
||||
DeviceBoundControlEnrollment,
|
||||
LiveModelingControlSafety,
|
||||
ModelingControlSafetyError,
|
||||
)
|
||||
@@ -61,14 +64,22 @@ def _status_message(
|
||||
)
|
||||
|
||||
|
||||
def _enrollment(**changes: str) -> DeviceBoundControlEnrollment:
|
||||
def _authority() -> ApplicationControlAuthority:
|
||||
return ApplicationControlAuthority(openapi_key="application-private-key")
|
||||
|
||||
|
||||
def _binding(**changes: object) -> LiveDeviceControlBinding:
|
||||
values = {
|
||||
"vendor_device_id": "vendor-device-001",
|
||||
"device_serial": "serial-001",
|
||||
"openapi_key": "device-bound-private-key",
|
||||
"software_version": "V3.0.2-build.1",
|
||||
"system_version": "V3.0.2",
|
||||
"device_model": "LixelKity K1",
|
||||
"device_type": "K1",
|
||||
"is_activated": True,
|
||||
}
|
||||
values.update(changes)
|
||||
return DeviceBoundControlEnrollment(**values)
|
||||
return LiveDeviceControlBinding(**values) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_live_status_identity_is_observed_without_accepting_replay() -> None:
|
||||
@@ -95,7 +106,7 @@ def test_start_shadow_plan_is_exact_bounded_and_never_executable() -> None:
|
||||
safety = LiveModelingControlSafety()
|
||||
safety.observe(_status_message(SessionState.READY), BridgeMetrics())
|
||||
|
||||
plan = safety.plan_start(_enrollment(), project_name="SAFE_PROJECT")
|
||||
plan = safety.plan_start(_authority(), _binding(), project_name="SAFE_PROJECT")
|
||||
fields = {field.number: field for field in iter_fields(plan.command.payload, max_fields=16)}
|
||||
|
||||
assert plan.action is ModelingAction.START
|
||||
@@ -109,8 +120,8 @@ def test_start_shadow_plan_is_exact_bounded_and_never_executable() -> None:
|
||||
assert plan.executable is False
|
||||
assert plan.blockers == ("vendor-writes-disabled", "publisher-not-installed")
|
||||
assert plan.retry_policy == "never-automatic"
|
||||
assert "device-bound-private-key" not in repr(plan)
|
||||
assert "device-bound-private-key" not in str(plan.as_dict())
|
||||
assert "application-private-key" not in repr(plan)
|
||||
assert "application-private-key" not in str(plan.as_dict())
|
||||
|
||||
|
||||
def test_stop_shadow_plan_requires_same_live_device_and_active_project() -> None:
|
||||
@@ -120,13 +131,13 @@ def test_stop_shadow_plan_requires_same_live_device_and_active_project() -> None
|
||||
BridgeMetrics(),
|
||||
)
|
||||
|
||||
plan = safety.plan_stop(_enrollment())
|
||||
plan = safety.plan_stop(_authority(), _binding())
|
||||
fields = list(iter_fields(plan.command.payload, max_fields=8))
|
||||
assert plan.action is ModelingAction.STOP
|
||||
assert [field.number for field in fields] == [1, 2]
|
||||
|
||||
with pytest.raises(ModelingControlSafetyError, match="does not match"):
|
||||
safety.plan_stop(_enrollment(device_serial="another-serial"))
|
||||
safety.plan_stop(_authority(), _binding(device_serial="another-serial"))
|
||||
|
||||
|
||||
def test_identity_drift_and_decode_failure_fail_closed() -> None:
|
||||
@@ -139,7 +150,7 @@ def test_identity_drift_and_decode_failure_fail_closed() -> None:
|
||||
)
|
||||
assert safety.snapshot().identity_conflict
|
||||
with pytest.raises(ModelingControlSafetyError, match="conflicted"):
|
||||
safety.plan_start(_enrollment(), project_name="SAFE_PROJECT")
|
||||
safety.plan_start(_authority(), _binding(), project_name="SAFE_PROJECT")
|
||||
|
||||
safety.reset()
|
||||
malformed = StreamMessage(
|
||||
@@ -153,23 +164,31 @@ def test_identity_drift_and_decode_failure_fail_closed() -> None:
|
||||
assert safety.observe(malformed, metrics)
|
||||
assert safety.snapshot().decode_errors == 1
|
||||
with pytest.raises(ModelingControlSafetyError, match="conflicted"):
|
||||
safety.plan_start(_enrollment(), project_name="SAFE_PROJECT")
|
||||
safety.plan_start(_authority(), _binding(), project_name="SAFE_PROJECT")
|
||||
|
||||
|
||||
def test_shadow_plan_requires_exact_safe_lifecycle_state() -> None:
|
||||
safety = LiveModelingControlSafety()
|
||||
safety.observe(_status_message(SessionState.SCAN_STARTING), BridgeMetrics())
|
||||
with pytest.raises(ModelingControlSafetyError, match="must be ready"):
|
||||
safety.plan_start(_enrollment(), project_name="SAFE_PROJECT")
|
||||
safety.plan_start(_authority(), _binding(), project_name="SAFE_PROJECT")
|
||||
with pytest.raises(ModelingControlSafetyError, match="must be scanning"):
|
||||
safety.plan_stop(_enrollment())
|
||||
safety.plan_stop(_authority(), _binding())
|
||||
|
||||
|
||||
def test_enrollment_rejects_runtime_profile_substitution() -> None:
|
||||
with pytest.raises(ModelingControlSafetyError, match="exact reviewed K1 profile"):
|
||||
DeviceBoundControlEnrollment(
|
||||
vendor_device_id="vendor-device-001",
|
||||
device_serial="serial-001",
|
||||
openapi_key="device-bound-private-key",
|
||||
firmware_version="3.0.3", # type: ignore[arg-type]
|
||||
def test_device_info_binding_rejects_unreviewed_or_inactive_profile() -> None:
|
||||
safety = LiveModelingControlSafety()
|
||||
safety.observe(_status_message(SessionState.READY), BridgeMetrics())
|
||||
|
||||
with pytest.raises(ModelingControlSafetyError, match="does not attest"):
|
||||
safety.plan_start(
|
||||
_authority(),
|
||||
_binding(software_version="V3.0.3"),
|
||||
project_name="SAFE_PROJECT",
|
||||
)
|
||||
with pytest.raises(ModelingControlSafetyError, match="does not attest"):
|
||||
safety.plan_start(
|
||||
_authority(),
|
||||
_binding(is_activated=False),
|
||||
project_name="SAFE_PROJECT",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user