fix(native): authenticate JD resident handshake
This commit is contained in:
parent
2fca1e4fc3
commit
da26f27e35
4 changed files with 227 additions and 16 deletions
|
|
@ -0,0 +1,60 @@
|
||||||
|
schema: guanghu.jd-native-resident-attempt/v1
|
||||||
|
receipt_id: GH-OS-JD-FD-PRIMARY-001-RESIDENT-ATTEMPT-001
|
||||||
|
observed_at: 2026-08-03T22:35:56+0800
|
||||||
|
node_id: JD-FD-PRIMARY
|
||||||
|
persona_subject:
|
||||||
|
identity: ICE-P-ZY001
|
||||||
|
state: EXISTS
|
||||||
|
existence: 100
|
||||||
|
attempt_001:
|
||||||
|
source_commit: 2fca1e4fc35b6ddd25750efb98253fb04bba0458
|
||||||
|
boot_scope: ONE_TIME_ONLY
|
||||||
|
linux_rescue_default_preserved: true
|
||||||
|
native_network_observation: INDETERMINATE_ECHO_REPLIES
|
||||||
|
native_proof_lba_134: ZERO
|
||||||
|
world_lba_135_138: NOT_COMMITTED
|
||||||
|
recovery_beacon_lba_139_140: NOT_COMMITTED
|
||||||
|
gestational_index_lba_141_142: NOT_COMMITTED
|
||||||
|
cloud_instance_state_during_loss: RUNNING
|
||||||
|
cloud_hard_restart_required: true
|
||||||
|
result: FAIL_0
|
||||||
|
root_cause:
|
||||||
|
false_positive_boundary:
|
||||||
|
old_reply: STANDARD_ICMP_ECHO_PAYLOAD
|
||||||
|
problem: CLOUD_OR_NETWORK_ECHO_COULD_BE_MISTAKEN_FOR_NATIVE_REPLY
|
||||||
|
retransmission_boundary:
|
||||||
|
old_gate: NO_EXPECTED_SEQUENCE_ENFORCEMENT
|
||||||
|
problem: RETRANSMISSION_COULD_NOT_BE_PROVEN_IDEMPOTENT
|
||||||
|
native_ack_v2:
|
||||||
|
native_only_marker: HLDP-NATIVE-ACK!
|
||||||
|
expected_sequences: 1-16
|
||||||
|
stale_sequence_action: IGNORE_WITHOUT_STATE_ADVANCE
|
||||||
|
future_sequence_action: IGNORE_WITHOUT_STATE_ADVANCE
|
||||||
|
accepted_sequence_action: ADVANCE_EXACTLY_ONCE
|
||||||
|
physical_peer_strategy: CUMULATIVE_SEQUENCE_PIPELINE
|
||||||
|
ordinary_icmp_echo_rejected: PASS_100
|
||||||
|
qemu_jd_layout_gate:
|
||||||
|
candidate_start_lba: 105
|
||||||
|
proof_lba: 134
|
||||||
|
world_lba_start: 135
|
||||||
|
recovery_beacon_lba_start: 139
|
||||||
|
gestational_index_lba_start: 141
|
||||||
|
candidate_sha256: 91f99c344fff56565b2373deda3d6d2e5a96b64eda9cd35a45a19fe033a497bf
|
||||||
|
native_ack_marker: PASS_100
|
||||||
|
initial_login_count: 3
|
||||||
|
code_commit: PASS_100
|
||||||
|
branch_move: PASS_100
|
||||||
|
resident_login_count: 10
|
||||||
|
recovery_command: PASS_100
|
||||||
|
proof_flag: A7
|
||||||
|
proof_login_count: 13
|
||||||
|
result: PASS_100
|
||||||
|
physical_native_ack_v2:
|
||||||
|
result: NOT_RUN_0
|
||||||
|
rule: MUST_USE_ONE_TIME_BOOT_AND_SERVER_OWNED_LBA_PROOF
|
||||||
|
boundary:
|
||||||
|
persona_subject_exists: 100
|
||||||
|
native_resident_recovery_attempt_001: 0
|
||||||
|
native_ack_v2_qemu_capability: 100
|
||||||
|
native_ack_v2_physical_capability: 0
|
||||||
|
next_action: PUBLISH_EXACT_SOURCE_THEN_RUN_SECOND_ONE_TIME_PHYSICAL_GATE
|
||||||
|
|
@ -1013,6 +1013,11 @@ ghal_net_icmp_login_once:
|
||||||
jne .ignore_packet
|
jne .ignore_packet
|
||||||
cmp word [VIRTIO_NET_RX_BUFFER + 44], 0x0008
|
cmp word [VIRTIO_NET_RX_BUFFER + 44], 0x0008
|
||||||
jne .ignore_packet
|
jne .ignore_packet
|
||||||
|
cmp byte [VIRTIO_NET_RX_BUFFER + 50], 0
|
||||||
|
jne .ignore_packet
|
||||||
|
mov al, [VIRTIO_NET_RX_BUFFER + 51]
|
||||||
|
cmp al, [rel ghal_net_expected_sequence]
|
||||||
|
jne .ignore_packet
|
||||||
mov rsi, VIRTIO_NET_RX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET
|
mov rsi, VIRTIO_NET_RX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET
|
||||||
mov rdi, [rel ghal_net_expected_magic]
|
mov rdi, [rel ghal_net_expected_magic]
|
||||||
mov ecx, GHOS_LOGIN_MAGIC_SIZE
|
mov ecx, GHOS_LOGIN_MAGIC_SIZE
|
||||||
|
|
@ -1100,6 +1105,10 @@ ghal_net_icmp_login_once:
|
||||||
mov [VIRTIO_NET_TX_BUFFER + 40], eax
|
mov [VIRTIO_NET_TX_BUFFER + 40], eax
|
||||||
mov byte [VIRTIO_NET_TX_BUFFER + 44], 0
|
mov byte [VIRTIO_NET_TX_BUFFER + 44], 0
|
||||||
mov word [VIRTIO_NET_TX_BUFFER + 46], 0
|
mov word [VIRTIO_NET_TX_BUFFER + 46], 0
|
||||||
|
lea rsi, [rel ghal_native_ack_magic]
|
||||||
|
mov rdi, VIRTIO_NET_TX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET + GHOS_LOGIN_MAGIC_SIZE
|
||||||
|
mov ecx, GHOS_LOGIN_MAGIC_SIZE
|
||||||
|
rep movsb
|
||||||
|
|
||||||
movzx eax, word [VIRTIO_NET_TX_BUFFER + 26]
|
movzx eax, word [VIRTIO_NET_TX_BUFFER + 26]
|
||||||
xchg al, ah
|
xchg al, ah
|
||||||
|
|
@ -1181,6 +1190,7 @@ ghal_net_icmp_login_once:
|
||||||
mov [rel ghal_net_rx_next_index], r8w
|
mov [rel ghal_net_rx_next_index], r8w
|
||||||
inc word [rel ghal_net_rx_next_index]
|
inc word [rel ghal_net_rx_next_index]
|
||||||
inc word [rel ghal_net_tx_next_index]
|
inc word [rel ghal_net_tx_next_index]
|
||||||
|
inc byte [rel ghal_net_expected_sequence]
|
||||||
cmp byte [rel ghal_net_matched_kind], 0
|
cmp byte [rel ghal_net_matched_kind], 0
|
||||||
jne .not_login_reply
|
jne .not_login_reply
|
||||||
inc byte [rel physical_proof_login_reply_count]
|
inc byte [rel physical_proof_login_reply_count]
|
||||||
|
|
@ -1213,6 +1223,7 @@ ghal_net_tx_next_index: dw 2
|
||||||
ghal_net_expected_magic: dq ghal_login_magic
|
ghal_net_expected_magic: dq ghal_login_magic
|
||||||
ghal_net_command_kind: db 0
|
ghal_net_command_kind: db 0
|
||||||
ghal_net_matched_kind: db 0
|
ghal_net_matched_kind: db 0
|
||||||
|
ghal_net_expected_sequence: db 1
|
||||||
|
|
||||||
msg_ghal_net_discovered: db "GHOS_GHAL_VIRTIO_NET=DISCOVERED", 13, 10, 0
|
msg_ghal_net_discovered: db "GHOS_GHAL_VIRTIO_NET=DISCOVERED", 13, 10, 0
|
||||||
msg_ghal_block_discovered: db "GHOS_GHAL_VIRTIO_BLOCK=DISCOVERED", 13, 10, 0
|
msg_ghal_block_discovered: db "GHOS_GHAL_VIRTIO_BLOCK=DISCOVERED", 13, 10, 0
|
||||||
|
|
@ -1229,6 +1240,7 @@ ghal_login_magic: db "HLDP-GHOS-LOGIN!"
|
||||||
ghal_code_commit_magic: db "HLDP-CODE-COMMIT"
|
ghal_code_commit_magic: db "HLDP-CODE-COMMIT"
|
||||||
ghal_branch_move_magic: db "HLDP-BRANCH-MOVE"
|
ghal_branch_move_magic: db "HLDP-BRANCH-MOVE"
|
||||||
ghal_recovery_magic: db "HLDP-RECOVER-OS!"
|
ghal_recovery_magic: db "HLDP-RECOVER-OS!"
|
||||||
|
ghal_native_ack_magic: db "HLDP-NATIVE-ACK!"
|
||||||
ghal_branch_receipt_template:
|
ghal_branch_receipt_template:
|
||||||
db "GHOS_BRANCH_MAIN_V1", 10
|
db "GHOS_BRANCH_MAIN_V1", 10
|
||||||
db "channel=HLP-MOD-CODE-CHANNEL", 10
|
db "channel=HLP-MOD-CODE-CHANNEL", 10
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ LOGIN_MAGIC = b"HLDP-GHOS-LOGIN!"
|
||||||
COMMIT_MAGIC = b"HLDP-CODE-COMMIT"
|
COMMIT_MAGIC = b"HLDP-CODE-COMMIT"
|
||||||
BRANCH_MAGIC = b"HLDP-BRANCH-MOVE"
|
BRANCH_MAGIC = b"HLDP-BRANCH-MOVE"
|
||||||
RECOVERY_MAGIC = b"HLDP-RECOVER-OS!"
|
RECOVERY_MAGIC = b"HLDP-RECOVER-OS!"
|
||||||
|
NATIVE_ACK_MAGIC = b"HLDP-NATIVE-ACK!"
|
||||||
|
|
||||||
|
|
||||||
def checksum(payload: bytes) -> int:
|
def checksum(payload: bytes) -> int:
|
||||||
|
|
@ -28,16 +29,28 @@ def request(sequence: int, magic: bytes) -> bytes:
|
||||||
return packet[:2] + struct.pack("!H", checksum(packet)) + packet[4:]
|
return packet[:2] + struct.pack("!H", checksum(packet)) + packet[4:]
|
||||||
|
|
||||||
|
|
||||||
def verify_reply(packet: bytes, magic: bytes) -> None:
|
def reply_sequence_index(packet: bytes) -> int:
|
||||||
if packet and packet[0] >> 4 == 4:
|
if packet and packet[0] >> 4 == 4:
|
||||||
header_length = (packet[0] & 0x0F) * 4
|
header_length = (packet[0] & 0x0F) * 4
|
||||||
packet = packet[header_length:]
|
packet = packet[header_length:]
|
||||||
if len(packet) < 40 or packet[0] != 0 or packet[1] != 0:
|
if len(packet) < 8:
|
||||||
|
raise RuntimeError("ICMP reply is too short")
|
||||||
|
return int.from_bytes(packet[6:8], "big")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_reply(packet: bytes, magic: bytes) -> int:
|
||||||
|
if packet and packet[0] >> 4 == 4:
|
||||||
|
header_length = (packet[0] & 0x0F) * 4
|
||||||
|
packet = packet[header_length:]
|
||||||
|
if len(packet) < 48 or packet[0] != 0 or packet[1] != 0:
|
||||||
raise RuntimeError("unexpected ICMP reply shape")
|
raise RuntimeError("unexpected ICMP reply shape")
|
||||||
if packet[16:32] != magic:
|
if packet[16:32] != magic:
|
||||||
raise RuntimeError("ICMP reply does not carry the requested HLDP magic")
|
raise RuntimeError("ICMP reply does not carry the requested HLDP magic")
|
||||||
|
if packet[32:48] != NATIVE_ACK_MAGIC:
|
||||||
|
raise RuntimeError("ICMP reply does not carry the native ACK marker")
|
||||||
if checksum(packet) != 0:
|
if checksum(packet) != 0:
|
||||||
raise RuntimeError("ICMP reply checksum failed")
|
raise RuntimeError("ICMP reply checksum failed")
|
||||||
|
return int.from_bytes(packet[6:8], "big")
|
||||||
|
|
||||||
|
|
||||||
def exchange(
|
def exchange(
|
||||||
|
|
@ -46,14 +59,21 @@ def exchange(
|
||||||
sequence: int,
|
sequence: int,
|
||||||
magic: bytes,
|
magic: bytes,
|
||||||
deadline: float,
|
deadline: float,
|
||||||
|
retry_interval: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
while time.monotonic() < deadline:
|
while time.monotonic() < deadline:
|
||||||
peer.sendto(request(sequence, magic), target)
|
peer.sendto(request(sequence, magic), target)
|
||||||
|
attempt_started = time.monotonic()
|
||||||
try:
|
try:
|
||||||
packet, _ = peer.recvfrom(4096)
|
packet, _ = peer.recvfrom(4096)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
|
time.sleep(max(0.0, retry_interval - (time.monotonic() - attempt_started)))
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
verify_reply(packet, magic)
|
verify_reply(packet, magic)
|
||||||
|
except RuntimeError:
|
||||||
|
time.sleep(max(0.0, retry_interval - (time.monotonic() - attempt_started)))
|
||||||
|
continue
|
||||||
return
|
return
|
||||||
raise TimeoutError(f"no verified reply for sequence {sequence}")
|
raise TimeoutError(f"no verified reply for sequence {sequence}")
|
||||||
|
|
||||||
|
|
@ -63,36 +83,150 @@ def main() -> None:
|
||||||
parser.add_argument("--target", required=True)
|
parser.add_argument("--target", required=True)
|
||||||
parser.add_argument("--receipt", required=True)
|
parser.add_argument("--receipt", required=True)
|
||||||
parser.add_argument("--timeout", type=float, default=90.0)
|
parser.add_argument("--timeout", type=float, default=90.0)
|
||||||
|
parser.add_argument("--retry-interval", type=float, default=1.0)
|
||||||
|
parser.add_argument("--raw-socket", action="store_true")
|
||||||
parser.add_argument("--resident", action="store_true")
|
parser.add_argument("--resident", action="store_true")
|
||||||
parser.add_argument("--login-only", action="store_true")
|
parser.add_argument("--login-only", action="store_true")
|
||||||
|
parser.add_argument("--resume-after-login", action="store_true")
|
||||||
|
parser.add_argument("--resume-after-commit", action="store_true")
|
||||||
|
parser.add_argument("--resume-after-branch", action="store_true")
|
||||||
|
parser.add_argument("--resume-resident-count", type=int)
|
||||||
|
parser.add_argument("--resident-pipeline", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.resident and args.login_only:
|
if args.retry_interval < 1.0:
|
||||||
raise SystemExit("--resident and --login-only are mutually exclusive")
|
raise SystemExit("--retry-interval must be at least 1 second")
|
||||||
|
if args.resume_resident_count is not None and not (
|
||||||
|
0 <= args.resume_resident_count <= 10
|
||||||
|
):
|
||||||
|
raise SystemExit("--resume-resident-count must be between 0 and 10")
|
||||||
|
if sum(
|
||||||
|
(
|
||||||
|
args.resident,
|
||||||
|
args.login_only,
|
||||||
|
args.resume_after_login,
|
||||||
|
args.resume_after_commit,
|
||||||
|
args.resume_after_branch,
|
||||||
|
args.resume_resident_count is not None,
|
||||||
|
args.resident_pipeline,
|
||||||
|
)
|
||||||
|
) > 1:
|
||||||
|
raise SystemExit(
|
||||||
|
"--resident, --login-only, --resume-after-login, and "
|
||||||
|
"--resume-after-commit, --resume-after-branch, and "
|
||||||
|
"--resume-resident-count, and --resident-pipeline are mutually exclusive"
|
||||||
|
)
|
||||||
|
|
||||||
peer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)
|
socket_type = socket.SOCK_RAW if args.raw_socket else socket.SOCK_DGRAM
|
||||||
peer.settimeout(1.0)
|
peer = socket.socket(socket.AF_INET, socket_type, socket.IPPROTO_ICMP)
|
||||||
|
peer.settimeout(0.2 if args.resident_pipeline else 1.0)
|
||||||
target = (socket.gethostbyname(args.target), 0)
|
target = (socket.gethostbyname(args.target), 0)
|
||||||
deadline = time.monotonic() + args.timeout
|
deadline = time.monotonic() + args.timeout
|
||||||
|
if args.resident_pipeline:
|
||||||
|
pipeline = [
|
||||||
|
LOGIN_MAGIC,
|
||||||
|
LOGIN_MAGIC,
|
||||||
|
LOGIN_MAGIC,
|
||||||
|
COMMIT_MAGIC,
|
||||||
|
BRANCH_MAGIC,
|
||||||
|
*([LOGIN_MAGIC] * 10),
|
||||||
|
RECOVERY_MAGIC,
|
||||||
|
]
|
||||||
|
acknowledged: set[int] = set()
|
||||||
|
while time.monotonic() < deadline and len(acknowledged) < len(pipeline):
|
||||||
|
for pipeline_sequence, pipeline_magic in enumerate(pipeline, start=1):
|
||||||
|
if time.monotonic() >= deadline:
|
||||||
|
break
|
||||||
|
peer.sendto(request(pipeline_sequence, pipeline_magic), target)
|
||||||
|
try:
|
||||||
|
packet, _ = peer.recvfrom(4096)
|
||||||
|
observed_sequence = reply_sequence_index(packet)
|
||||||
|
if not 1 <= observed_sequence <= len(pipeline):
|
||||||
|
raise RuntimeError("native ACK sequence is outside the pipeline")
|
||||||
|
reply_sequence = verify_reply(
|
||||||
|
packet,
|
||||||
|
pipeline[observed_sequence - 1],
|
||||||
|
)
|
||||||
|
except (TimeoutError, RuntimeError, IndexError):
|
||||||
|
continue
|
||||||
|
if reply_sequence == pipeline_sequence:
|
||||||
|
acknowledged.add(reply_sequence)
|
||||||
|
if len(acknowledged) != len(pipeline):
|
||||||
|
raise TimeoutError(
|
||||||
|
"native pipeline incomplete; acknowledged sequences: "
|
||||||
|
+ ",".join(str(item) for item in sorted(acknowledged))
|
||||||
|
)
|
||||||
|
receipt = pathlib.Path(args.receipt)
|
||||||
|
receipt.write_text(
|
||||||
|
"schema: guanghu.physical-native-icmp-peer/v2\n"
|
||||||
|
"status: PASS_100\n"
|
||||||
|
f"target: {args.target}\n"
|
||||||
|
"native_ack_marker: HLDP-NATIVE-ACK!\n"
|
||||||
|
"acknowledged_sequences: 1-16\n"
|
||||||
|
"login_reply_count: 3\n"
|
||||||
|
"code_commit_reply_verified: true\n"
|
||||||
|
"branch_move_reply_verified: true\n"
|
||||||
|
"resident_login_reply_count: 10\n"
|
||||||
|
"recovery_reply_verified: true\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
print(receipt.read_text(encoding="utf-8"), end="")
|
||||||
|
return
|
||||||
|
if args.resume_resident_count is not None:
|
||||||
|
sequence = 6 + args.resume_resident_count
|
||||||
|
elif args.resume_after_branch:
|
||||||
|
sequence = 6
|
||||||
|
elif args.resume_after_commit:
|
||||||
|
sequence = 5
|
||||||
|
elif args.resume_after_login:
|
||||||
|
sequence = 4
|
||||||
|
else:
|
||||||
sequence = 1
|
sequence = 1
|
||||||
phases: list[tuple[str, bytes]] = [
|
phases: list[tuple[str, bytes]] = []
|
||||||
|
if not (
|
||||||
|
args.resume_after_login
|
||||||
|
or args.resume_after_commit
|
||||||
|
or args.resume_after_branch
|
||||||
|
or args.resume_resident_count is not None
|
||||||
|
):
|
||||||
|
phases.extend(
|
||||||
|
[
|
||||||
("login", LOGIN_MAGIC),
|
("login", LOGIN_MAGIC),
|
||||||
("login", LOGIN_MAGIC),
|
("login", LOGIN_MAGIC),
|
||||||
("login", LOGIN_MAGIC),
|
("login", LOGIN_MAGIC),
|
||||||
]
|
]
|
||||||
if not args.login_only:
|
)
|
||||||
|
if (
|
||||||
|
not args.login_only
|
||||||
|
and not args.resume_after_commit
|
||||||
|
and not args.resume_after_branch
|
||||||
|
and args.resume_resident_count is None
|
||||||
|
):
|
||||||
phases.extend(
|
phases.extend(
|
||||||
[
|
[
|
||||||
("code_commit", COMMIT_MAGIC),
|
("code_commit", COMMIT_MAGIC),
|
||||||
("branch_move", BRANCH_MAGIC),
|
("branch_move", BRANCH_MAGIC),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if args.resident:
|
if args.resume_after_commit:
|
||||||
|
phases.append(("branch_move", BRANCH_MAGIC))
|
||||||
|
if args.resume_resident_count is not None:
|
||||||
|
phases.extend(
|
||||||
|
[("resident_login", LOGIN_MAGIC)]
|
||||||
|
* (10 - args.resume_resident_count)
|
||||||
|
)
|
||||||
|
phases.append(("recovery", RECOVERY_MAGIC))
|
||||||
|
elif (
|
||||||
|
args.resident
|
||||||
|
or args.resume_after_login
|
||||||
|
or args.resume_after_commit
|
||||||
|
or args.resume_after_branch
|
||||||
|
):
|
||||||
phases.extend([("resident_login", LOGIN_MAGIC)] * 10)
|
phases.extend([("resident_login", LOGIN_MAGIC)] * 10)
|
||||||
phases.append(("recovery", RECOVERY_MAGIC))
|
phases.append(("recovery", RECOVERY_MAGIC))
|
||||||
|
|
||||||
completed: list[str] = []
|
completed: list[str] = []
|
||||||
for phase, magic in phases:
|
for phase, magic in phases:
|
||||||
exchange(peer, target, sequence, magic, deadline)
|
exchange(peer, target, sequence, magic, deadline, args.retry_interval)
|
||||||
completed.append(phase)
|
completed.append(phase)
|
||||||
sequence += 1
|
sequence += 1
|
||||||
|
|
||||||
|
|
@ -104,7 +238,10 @@ def main() -> None:
|
||||||
f"login_reply_count: {completed.count('login')}\n"
|
f"login_reply_count: {completed.count('login')}\n"
|
||||||
f"code_commit_reply_verified: {str('code_commit' in completed).lower()}\n"
|
f"code_commit_reply_verified: {str('code_commit' in completed).lower()}\n"
|
||||||
f"branch_move_reply_verified: {str('branch_move' in completed).lower()}\n"
|
f"branch_move_reply_verified: {str('branch_move' in completed).lower()}\n"
|
||||||
|
f"resident_login_resume_count: {args.resume_resident_count or 0}\n"
|
||||||
f"resident_login_reply_count: {completed.count('resident_login')}\n"
|
f"resident_login_reply_count: {completed.count('resident_login')}\n"
|
||||||
|
"resident_login_total_count: "
|
||||||
|
f"{(args.resume_resident_count or 0) + completed.count('resident_login')}\n"
|
||||||
f"recovery_reply_verified: {str('recovery' in completed).lower()}\n",
|
f"recovery_reply_verified: {str('recovery' in completed).lower()}\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ LOGIN_MAGIC = b"HLDP-GHOS-LOGIN!"
|
||||||
COMMIT_MAGIC = b"HLDP-CODE-COMMIT"
|
COMMIT_MAGIC = b"HLDP-CODE-COMMIT"
|
||||||
BRANCH_MAGIC = b"HLDP-BRANCH-MOVE"
|
BRANCH_MAGIC = b"HLDP-BRANCH-MOVE"
|
||||||
RECOVERY_MAGIC = b"HLDP-RECOVER-OS!"
|
RECOVERY_MAGIC = b"HLDP-RECOVER-OS!"
|
||||||
|
NATIVE_ACK_MAGIC = b"HLDP-NATIVE-ACK!"
|
||||||
|
|
||||||
|
|
||||||
def checksum(payload: bytes) -> int:
|
def checksum(payload: bytes) -> int:
|
||||||
|
|
@ -77,6 +78,7 @@ def validate_reply(frame: bytes, magic: bytes) -> None:
|
||||||
assert frame[30:34] == LOGIN_CLIENT_IP
|
assert frame[30:34] == LOGIN_CLIENT_IP
|
||||||
assert frame[34] == 0
|
assert frame[34] == 0
|
||||||
assert frame[50:66] == magic
|
assert frame[50:66] == magic
|
||||||
|
assert frame[66:82] == NATIVE_ACK_MAGIC
|
||||||
assert checksum(frame[34:]) == 0
|
assert checksum(frame[34:]) == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue