EnvelopeCodec message layer; scripts are thin business-API wrappers

This commit is contained in:
kfj001 committed 2026-10-16 04:44:45 +00:00
1 parent f15ae42db7
commit 363c7cc6a2
14 files changed
+551 -900

No files matched your search

+29 -27
View File
@@ -19,7 +19,6 @@ the direct-connect path of the app (see ``docs/RE_DART_FINDINGS.md`` §8,
from __future__ import annotations
import json
import logging
import socket
from typing import Any, Dict, Optional
@@ -32,12 +31,11 @@ from .protocol import (
CommunicationOperate,
SequenceNumber,
SocketFrameDecoder,
decrypt_wire,
direct_connect_request,
encrypt_object,
frame_socket_payload,
generate_secret_key,
)
from .protocol.messages import EnvelopeCodec
from .wifi import DEVICE_GATEWAY_IP
log = logging.getLogger(__name__)
@@ -96,6 +94,7 @@ class LocalDeviceClient:
self.key_radix = int(key_radix)
self.timeout = timeout
self.key = generate_secret_key(sn)
self.codec = EnvelopeCodec(self.key, radix=self.key_radix)
self._sn_seq = SequenceNumber()
self._session = session or requests.Session()
self._session.headers.update(
@@ -196,57 +195,60 @@ class LocalDeviceClient:
return self.send_envelope(envelope)
def send_envelope(self, envelope: CommunicationObject) -> Dict[str, Any]:
wire = encrypt_object(envelope.to_json_string(), self.key,
radix=self.key_radix)
raw = self._socket_roundtrip(frame_socket_payload(wire))
raw = self._socket_roundtrip(
frame_socket_payload(self.codec.encode(envelope)))
try:
reply = decrypt_wire(raw, radix=self.key_radix)
reply = self.codec.decode(raw).to_dict()
except ValueError:
raise ConnectionError(
f"undecodable reply ({len(raw)} bytes) from "
f"{self.socket_host}:{self.socket_port}"
)
return json.loads(reply)
return reply
def send_probe(
self,
cmd: CommunicationCmd,
data: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
equip_no: Optional[str] = None,
) -> Dict[str, Any]:
"""Send one envelope, return exact TX/RX bytes plus any parsed reply.
Never raises on a device-level failure: ``reply`` is the parsed JSON
(or ``None``), ``reply_raw`` the exact bytes received (``b""`` when
the device accepted the frame but answered with zero bytes).
Never raises on a device-level failure: ``reply`` is the parsed
envelope dict (or ``None``), ``reply_raw`` the exact bytes received
(``b""`` when the device accepted the frame but answered with zero
bytes). ``equip_no`` overrides the serial (``""`` reproduces the
app's empty-SN discovery frame).
"""
envelope = direct_connect_request(
cmd, self.sn, data=data, snno=self._sn_seq.next(),
cmd, self.sn if equip_no is None else equip_no, data=data,
snno=self._sn_seq.next(),
)
wire = encrypt_object(envelope.to_json_string(), self.key,
radix=self.key_radix)
wire = self.codec.encode(envelope)
reply_raw = self._socket_roundtrip(
frame_socket_payload(wire), timeout=timeout, collect_only=True,
)
reply: Optional[Dict[str, Any]] = None
if reply_raw:
try:
decoded = decrypt_wire(reply_raw, radix=self.key_radix)
except ValueError:
decoded = ""
if decoded:
try:
reply = json.loads(decoded)
except ValueError:
reply = None
return {
"cmd": str(cmd),
"envelope": envelope.to_dict(),
"tx": wire,
"reply_raw": reply_raw,
"reply": reply,
"reply": self.codec.try_decode(reply_raw),
}
def send_raw(self, payload: bytes,
timeout: Optional[float] = None) -> bytes:
"""Low-level escape hatch: one connection, send *payload*, drain.
Used by transport-level probes (framing/handshake variants) that
are not valid envelopes. Returns the exact bytes received (``b""``
when the peer answers with nothing). Envelope traffic must use
:meth:`send_envelope` / :meth:`send_probe` instead.
"""
return self._socket_roundtrip(bytes(payload), timeout=timeout,
collect_only=True)
def _socket_roundtrip(self, framed: bytes,
timeout: Optional[float] = None,
collect_only: bool = False) -> bytes:
@@ -278,7 +280,7 @@ class LocalDeviceClient:
if collect_only:
return bytes(received)
try:
decrypt_wire(bytes(received), radix=self.key_radix)
self.codec.decode(bytes(received))
except ValueError:
raise ConnectionError(
f"no reply frame from {self.socket_host}:{self.socket_port}"