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
+39
-8
@@ -19,11 +19,17 @@ see `RE_DART_FINDINGS.md` §§3–5, 8, 10.
|
||||
|
||||
1. Probes TCP ports `9000, 18000` in order (`9000` was open on the tested
|
||||
unit; `0x4650 = 18000` is the app constant but was closed there).
|
||||
2. Sends `DEVICE_APOWER_INFO (0x19)`, then `BMS (0x1D)`, `EMS (0x1E)`:
|
||||
compact-JSON `CommunicationObject` → `encrypt_object` (key
|
||||
`hex(sum(sn) & 0xFF)`) → int16LE length framing.
|
||||
3. Decrypts the reply and extracts `currentBatterySoc` → `soc` →
|
||||
`avg(socArray)`.
|
||||
2. Sends `DEVICE_APOWER_INFO (0x19)` with `data={"update": 2}` (exactly what
|
||||
the app's `queryAPowerData` passes as `arguments`), then `BMS (0x1D)`,
|
||||
`EMS (0x1E)`: compact-JSON `CommunicationObject`
|
||||
(`cmdType, equipNo, type, timeStamp, snno, len, crc[, dataArea]`, no
|
||||
`data` key) → `encrypt_object` (`S1` plaintext header first, tail
|
||||
`(byte + abs_index + n) & 0xFF` with `n = int(key, 32)`) → sent **raw**
|
||||
over the socket (no length prefix — `ClientSocket.send` forwards to
|
||||
`_Socket.add` directly).
|
||||
3. Accumulates reply chunks and runs the app's `decryptData` logic (second
|
||||
`0x58` marker → `s + "}"` equipNo probe → tail decode → `s + "," + T`),
|
||||
then extracts `currentBatterySoc` → `soc` → `avg(socArray)`.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -36,11 +42,14 @@ python -m franklinwh_agate_client.battery --sn <20-char-SN>
|
||||
|
||||
# full reply envelope
|
||||
python -m franklinwh_agate_client.battery --sn <20-char-SN> --raw
|
||||
|
||||
# low-level probe: exact TX bytes + exact RX bytes per request variant
|
||||
python -m franklinwh_agate_client.battery --sn <20-char-SN> --probe
|
||||
```
|
||||
|
||||
Options: `--host` (default `10.100.1.1`), `--ports 9000,18000`,
|
||||
`--key-radix {16,32}` (default 16; 32 is the alternate candidate in the
|
||||
disassembly), `--timeout`.
|
||||
`--key-radix {16,32}` (default 32, per the `radix: 32` literal at both
|
||||
`tryParse` sites), `--timeout`.
|
||||
|
||||
Programmatic:
|
||||
|
||||
@@ -50,9 +59,31 @@ out = get_battery("...", host="10.100.1.1")
|
||||
print(out["value"], out["source"]) # e.g. 78.0 currentBatterySoc
|
||||
```
|
||||
|
||||
## Host probe recipe (run on the machine joined to the device AP)
|
||||
|
||||
The agent container cannot reach `10.100.1.x`; run this on the host:
|
||||
|
||||
```bash
|
||||
cd franklinwh-agate-client
|
||||
python3 -m franklinwh_agate_client.battery \
|
||||
--sn A03X2516013210060005 --host 10.100.1.1 --ports 9000,18000 --probe
|
||||
```
|
||||
|
||||
Expected: `secret key for ...: 1c (radix 32)`, then per-variant `tx`
|
||||
(encrypted envelope, starts with the ASCII header
|
||||
`{"cmdType":25,"equipNo":"A03X2516013210060005",`) and `rx` bytes.
|
||||
A rejected frame is met with zero bytes + close; any non-empty `rx` is
|
||||
the device answering — paste the `rx` hex back and the reply parses via
|
||||
`decrypt_data`.
|
||||
|
||||
If every variant still returns zero bytes, capture one app-driven session
|
||||
instead: install the Frida hook (`blutter_out/blutter_frida.js` pattern —
|
||||
log `Socket Manager send object: ` / `receive object: `), tap *Refresh* in
|
||||
the app while joined to the AP, and compare its TX bytes against ours.
|
||||
|
||||
## Limits
|
||||
|
||||
- No live round-trip has succeeded yet from an isolated container netns
|
||||
(all probes `ECONNREFUSED`); first success needs a `10.100.1.x` source.
|
||||
- `key_radix`, BLE field order, and `X-Device-*` binding remain open
|
||||
- BLE field order and `X-Device-*` binding remain open
|
||||
(`RE_DART_FINDINGS.md` Open items).
|
||||
Reference in new issue
Block a user