Correct wire format to disassembly: raw S1||crypt(S2), radix 32, int snno/seconds, decryptData response parser, --probe
- envelope: 7 fixed keys + conditional dataArea, no data key; int
type/snno, seconds timestamp, data setter auto len/crc (CRC32 upper);
SequenceNumber 1..9999 (increaseSN)
- crypto: encrypt is plaintext-header-first a||transform(b), key radix 32
at both tryParse sites; decrypt_data ports decryptData (dual-0x58
markers, s+'}' equipNo probe, s+','+T return); decrypt_request kept
for the device-side known-header split
- socket: raw send (no length prefix), accumulate-and-try receive
- battery: queryAPowerData shape (data={'update':2}), radix default 32,
--probe prints exact TX/RX bytes per variant; device_local.send_probe
- docs: RE_DART_FINDINGS ss3/5/8 + open items, BATTERY_POC host recipe
- tests: 19 new protocol tests, suite 44/44 green
This commit is contained in:
1 parent
2438a04ed5
commit
9e69f07636
10 files changed
+744
-182
No files matched your search
@@ -11,6 +11,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from .crc import crc16_hex_string, crc16_modbus, int16_to_le_bytes, le_bytes_to_int16
|
||||
@@ -130,30 +131,51 @@ def magicEquals(a: bytes, b: bytes) -> bool:
|
||||
|
||||
# --------------------------------------------------------------------- socket
|
||||
def frame_socket_payload(payload: bytes) -> bytes:
|
||||
"""Length-prefixed local-socket frame (candidate — §8).
|
||||
"""Local-socket frame: the app sends the encrypted envelope **raw**.
|
||||
|
||||
``int16LE(len(payload)) || payload`` — the app's socket layer frames with
|
||||
the same little-endian int16 helpers.
|
||||
``ClientSocket.send`` forwards its argument straight to
|
||||
``dart:io _Socket.add`` with no length prefix, markers, or handshake
|
||||
(see §8); ``SocketManager._sendData`` only clears the receive buffer
|
||||
afterwards. This helper is therefore the identity (kept so call sites
|
||||
read like the app).
|
||||
"""
|
||||
if len(payload) > 0xFFFF:
|
||||
raise ValueError("socket payload exceeds the int16 length field")
|
||||
return int16_to_le_bytes(len(payload)) + bytes(payload)
|
||||
return bytes(payload)
|
||||
|
||||
|
||||
class SocketFrameDecoder:
|
||||
"""Reassembles length-prefixed socket frames from a byte stream."""
|
||||
"""Accumulating socket receiver (``SocketManager._onSocketReceiveData``).
|
||||
|
||||
Each incoming chunk is appended to a growable buffer and ``decrypt_data``
|
||||
is attempted over the whole buffer (inside try/catch in the app); a
|
||||
non-empty decoded string that parses as JSON is one complete reply, at
|
||||
which point the buffer is cleared. Use :meth:`feed` for raw chunks and
|
||||
:meth:`feed_json` when you want parsed envelopes back.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._buf = bytearray()
|
||||
|
||||
def feed(self, data: bytes) -> List[bytes]:
|
||||
"""Append *data*; return a list of complete payloads (may be empty)."""
|
||||
def feed(self, data: bytes) -> List[str]:
|
||||
"""Append *data*; return a list of complete decoded JSON strings."""
|
||||
from .crypto import decrypt_data # local import: cheap, no cycles
|
||||
|
||||
self._buf.extend(data)
|
||||
out: List[bytes] = []
|
||||
while len(self._buf) >= 2:
|
||||
length = le_bytes_to_int16(bytes(self._buf[:2]))
|
||||
if len(self._buf) < 2 + length:
|
||||
break
|
||||
out.append(bytes(self._buf[2:2 + length]))
|
||||
del self._buf[2 + length]
|
||||
decoded = decrypt_data(bytes(self._buf))
|
||||
if not decoded:
|
||||
return []
|
||||
try:
|
||||
json.loads(decoded)
|
||||
except ValueError:
|
||||
return []
|
||||
del self._buf[:]
|
||||
return [decoded]
|
||||
|
||||
# Backwards-compatible alias for the old length-prefixed probing API.
|
||||
def feed_frames(self, data: bytes) -> List[bytes]:
|
||||
"""Append *data*; return complete **raw wire** buffers (legacy)."""
|
||||
self._buf.extend(data)
|
||||
if not self._buf:
|
||||
return []
|
||||
out = [bytes(self._buf)]
|
||||
del self._buf[:]
|
||||
return out
|
||||
Reference in new issue
Block a user