Radix 16 (live-verified), universal reply splitter, golden capture vectors, host_probe6

This commit is contained in:
kfj001 committed 2026-10-16 02:50:23 +00:00
1 parent 0cd6b34a2e
commit 62e0fd649b
10 files changed
+418 -82

No files matched your search

+33 -12
View File
@@ -161,14 +161,20 @@ unpadded. E.g. SN `A03X2516013210060005` → byte sum `1052 & 0xFF = 28` → `"1
2. `S1 = parts[0] + "," + parts[1] + ","` (header, *with* trailing comma);
`a = UTF-8(S1)`.
3. `S2 = join(",", parts[2..])` (the rest); `b = UTF-8(S2)`.
4. `keyInt = int.tryParse(key, radix: 32)` (literal `0x20` at the call site;
null → 0).
4. `keyInt = int.tryParse(key)` (default radix **16** — two live frames
decrypt cleanly only with radix 16 and then verify byte-exact on
`len`/CRC; an earlier radix-32 reading of a nearby `0x20` literal is
superseded).
5. `out[i] = (b[i] + i + len(a) + keyInt) & 0xFF` for all i (so each tail
byte is shifted by its *absolute* JSON index plus the key).
6. Result = `a ++ out` — the plaintext header goes on the wire **first**,
transformed tail after (`addAll` onto the header copy).
**`decryptData(bytes)`** (single arg, the accumulated receive buffer):
as recovered from the disassembly (two-`0x58`-marker scan, `s + "}"`
equipNo probe, `s + "," + T` return). NOTE (2026-10-16): live socket
replies decrypt with the *request-side* split instead (see below); the
marker model is kept for the unverified Bluetooth path.
1. `i = indexWhere(byte == 0x58)`, `j = indexWhere(byte == 0x58, start: i+1)`;
either missing → return `""`.
2. `s = utf8(wire[0..j])`; `probe = jsonDecode(s + "}")`, null → `""`.
@@ -273,17 +279,32 @@ Full TCP sweep of `10.100.1.1` (ports 1–65535): exactly **two** ports open —
for *every* probe (TLS ClientHello, HTTP GET, MQTT CONNECT, exact-spec
envelopes raw/split/length-prefixed, greeting-first reads).
### Update (2026-10-15) — socket cmd space + live capture
### Update (2026-10-16) — binary capture fully decoded
All pre-2026-10-15 probes used `off_8` ids (`25/0/29/30`) and were dropped
silently — the socket transport speaks the `off_14` id space (§4), so the
device saw unknown commands. A PCAPdroid capture of the working app shows
real socket frames: `{"cmdType":1101,…}` (queryDeviceInfo, empty SN) and
`{"cmdType":1102,"equipNo":"1006005A03X25160132",…}` (922 B). Open:
dst IP:port + direction per frame (need capture metadata / full `.pcap`),
the meaning of `1102` (no pool cmd maps to `0x44E`), and the 17-char
`equipNo` (sub-device? battery unit? reply convention?). Current probes
use socket-space ids (`1101/1307/1705/1707`) via `scripts/host_probe3.py`.
Raw `.bin` payloads (no text mangling) decrypt completely offline with
radix 16 (`tests/fixtures/*.bin` are golden vectors):
- Discovery request (153 B): `{"cmdType":1101,"equipNo":"00000000",…,
"dataArea":{"opt":0,"minProtocolVer":"V1.00.00"}}`, key `"80"`/n=128.
- Device-info reply (1154 B): `cmdType` **1102**, `equipNo`
`10060005A03X25160132` (the unit's IBG SN — label SN
`A03X2516013210060005` shares its trailing 12 chars), `len` 1024 exact,
CRC exact, `dataArea.result == 0` with the full version inventory
(`FHP_NUM: 2`, two `FHP_SN`, two `BMS_SN`, `protocolVer V1.12.36`).
Encrypted with the **gateway** key (`"1c"`/n=28) while addressed to the
IBG SN — the gateway is the crypto endpoint; sub-unit addressing rides
inside.
Reply rules learned: same `S1 || transform(S2)` framing both directions;
reply id observed as request + 1 (`1101 → 1102`); universal split at the
2nd comma needs no ids up front (`decrypt_wire`). The remaining unknown
is the live endpoint (dst IP:port + direction per capture).
Superseded notes: pre-2026-10-16 probes additionally used the wrong key
radix (32 instead of 16) and mostly omitted the `opt`-seeded payload, so
every silent run is fully explained by bytes, with no need for exotic
causes. Current probes use socket-space ids + radix 16 + `opt` payloads
via `scripts/host_probe6.py`.
## 14. Direct-connect flow (manual AP join → return to app)