fix(protocol): implement partial DLE stuffing for 0x10 bytes in params to prevent request corruption
This commit is contained in:
@@ -4,6 +4,55 @@ All notable changes to seismo-relay are documented here.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v0.14.3 — 2026-05-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`build_5a_frame` — DLE-stuffing rule for 0x10 bytes in params (the
|
||||||
|
long-standing >1-sec event 0 "won't open in BW" bug).**
|
||||||
|
|
||||||
|
Previously `build_5a_frame` wrote params bytes RAW with no DLE stuffing,
|
||||||
|
based on the incorrect assumption that the device handled all `0x10`
|
||||||
|
bytes in params literally. It does not. The device's actual de-stuffing
|
||||||
|
rule for the params region is:
|
||||||
|
|
||||||
|
- `10 10` → de-stuffs to `10`
|
||||||
|
- `10 02/03/04` → kept literal (inner-frame markers)
|
||||||
|
- `10 X` for other X → de-stuffs to just `X` (drops the `0x10`)
|
||||||
|
|
||||||
|
When the counter passed in params has `0x10` in the high byte (e.g.
|
||||||
|
counter=`0x1000` produces params bytes `... 10 00 ...`), the device
|
||||||
|
silently corrupts the request to counter=`0x__00` and responds with
|
||||||
|
whatever lives at that wrong address. For counter=0x1000 the wrong
|
||||||
|
address was 0x0000, so the response was a copy of the file header +
|
||||||
|
STRT record. That STRT block then got embedded in the assembled body
|
||||||
|
at file offset `0x1016`, and Blastware refused to open the file
|
||||||
|
(interprets the second STRT as a malformed multi-event file).
|
||||||
|
|
||||||
|
This explains the entire >1-sec event-0 failure pattern:
|
||||||
|
|
||||||
|
- 1-sec events have `end_offset < 0x1000`, so the chunk walk never
|
||||||
|
requests counter `0x10__` and the bug never triggers.
|
||||||
|
- 2-sec / 3-sec / longer events all need a chunk at counter `0x1000`
|
||||||
|
(and longer events also need `0x1200`, `0x1400`, etc., none of which
|
||||||
|
have `0x10` in the high byte except `0x1000`). Just one corrupted
|
||||||
|
response is enough to embed STRT in the body and break the file.
|
||||||
|
|
||||||
|
Verified against BW 5-1-26 "copy 3sec" capture: all 17 5A request
|
||||||
|
frames (probe + 2 metadata pages + 13 sample chunks + TERM) now match
|
||||||
|
BW's wire output **byte-for-byte**, including the doubled `10 10 00`
|
||||||
|
for counter=0x1000.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- `0x10` bytes in `offset_hi` (the standalone offset field at body[5])
|
||||||
|
are still written RAW — confirmed correct per the 1-2-26 capture.
|
||||||
|
- BW's actual encoding of `10 02` / `10 04` for meta pages 0x1002 /
|
||||||
|
0x1004 is *not* doubled — it relies on the device keeping `10 02`
|
||||||
|
and `10 04` as literal pairs. This is preserved by the fix.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.14.2 — 2026-05-04
|
## v0.14.2 — 2026-05-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for
|
Ground-up Python replacement for **Blastware**, Instantel's Windows-only software for
|
||||||
managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem
|
managing MiniMate Plus seismographs. Connects over direct RS-232 or cellular modem
|
||||||
(Sierra Wireless RV50 / RV55). Current version: **v0.14.2**.
|
(Sierra Wireless RV50 / RV55). Current version: **v0.14.3**.
|
||||||
|
|
||||||
When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document
|
When new information about the protocol is discovered, please update the instantel_protocol_reference.md with the findings in addition to this document
|
||||||
|
|
||||||
@@ -115,8 +115,31 @@ S3→BW (response):
|
|||||||
section contribute only `XX` to the running sum; lone bytes contribute normally. This
|
section contribute only `XX` to the running sum; lone bytes contribute normally. This
|
||||||
differs from the standard SUM8-of-destuffed-payload that all other commands use.
|
differs from the standard SUM8-of-destuffed-payload that all other commands use.
|
||||||
|
|
||||||
Both differences confirmed by reproducing Blastware's exact wire bytes from the 1-2-26
|
3. **Params region uses partial DLE stuffing (CONFIRMED 2026-05-05).** The device's
|
||||||
BW TX capture. All 10 frames verified.
|
de-stuffing rule for bytes inside the params region is:
|
||||||
|
|
||||||
|
- `10 10` → de-stuffs to `10`
|
||||||
|
- `10 02 / 03 / 04` → kept literal (these are inner-frame markers)
|
||||||
|
- `10 X` for other X → de-stuffs to just `X` (drops the leading `0x10`)
|
||||||
|
|
||||||
|
Therefore any `0x10` byte in the *logical* params that is followed by a byte NOT in
|
||||||
|
`{0x02, 0x03, 0x04, 0x10}` MUST be doubled on the wire (`10 X` → `10 10 X`) so the
|
||||||
|
device's de-stuffer reproduces the original `10 X` pair. This applies most commonly
|
||||||
|
to counters with `0x10` in the high byte (e.g. counter=`0x1000` produces logical
|
||||||
|
params bytes `... 10 00 ...`, which BW encodes on the wire as `... 10 10 00 ...`).
|
||||||
|
Without this stuffing the device interprets counter=`0x1000` as `0x0000` and returns
|
||||||
|
the probe response (which contains a copy of the file header + STRT record). That
|
||||||
|
STRT block then gets embedded in the assembled file body at offset `0x1016`, and
|
||||||
|
Blastware refuses to open the file — see the v0.14.3 entry in `CHANGELOG.md`.
|
||||||
|
|
||||||
|
`0x10` bytes in `offset_hi` (body[5]) are still written RAW — only the params region
|
||||||
|
has this stuffing requirement. The metadata-page params for counter `0x1002` /
|
||||||
|
`0x1004` survive without stuffing because `10 02` and `10 04` fall in the "kept
|
||||||
|
literal" carve-out.
|
||||||
|
|
||||||
|
Both differences (1) and (2) confirmed by reproducing Blastware's exact wire bytes from
|
||||||
|
the 1-2-26 BW TX capture (10 frames). Difference (3) confirmed against the 5-1-26
|
||||||
|
"bwcap3sec" capture (17 frames, all match byte-for-byte after fix).
|
||||||
|
|
||||||
### SUB 5A — chunk counter formula (REWRITTEN 2026-05-01 — see 5-1-26 captures)
|
### SUB 5A — chunk counter formula (REWRITTEN 2026-05-01 — see 5-1-26 captures)
|
||||||
|
|
||||||
|
|||||||
+33
-1
@@ -137,8 +137,40 @@ def build_5a_frame(offset_word: int, raw_params: bytes) -> bytes:
|
|||||||
s += b"\x00" # field3
|
s += b"\x00" # field3
|
||||||
s += bytes([(offset_word >> 8) & 0xFF, # offset_hi — raw, NOT stuffed
|
s += bytes([(offset_word >> 8) & 0xFF, # offset_hi — raw, NOT stuffed
|
||||||
offset_word & 0xFF]) # offset_lo
|
offset_word & 0xFF]) # offset_lo
|
||||||
for b in raw_params: # params — NOT DLE-stuffed (raw bytes, match BW wire format)
|
# Params — partial DLE stuffing of 0x10 bytes (CONFIRMED 2026-05-05).
|
||||||
|
#
|
||||||
|
# The device's de-stuffing rule for params is:
|
||||||
|
# • `10 10` → de-stuffs to `10`
|
||||||
|
# • `10 02/03/04` → kept literal (these are inner-frame markers)
|
||||||
|
# • `10 X` other → de-stuffs to just `X` (drops the 0x10)
|
||||||
|
#
|
||||||
|
# So for any 0x10 byte in the *logical* params that is followed by a
|
||||||
|
# byte NOT in {0x02, 0x03, 0x04, 0x10}, we must double the 0x10 on the
|
||||||
|
# wire (`10 X` → `10 10 X`) so the device's de-stuffer reproduces the
|
||||||
|
# original `10 X` pair. Without this, counter values with `0x10` in
|
||||||
|
# the high byte (e.g. counter=0x1000 has params bytes `10 00`) are
|
||||||
|
# silently corrupted to `0x__00` on the device side, and the device
|
||||||
|
# responds for the wrong address — for counter=0x1000 it returns the
|
||||||
|
# probe response (counter=0x0000), which contains the file header +
|
||||||
|
# STRT. That STRT block then lands in the assembled file body and
|
||||||
|
# Blastware rejects the file as malformed.
|
||||||
|
#
|
||||||
|
# Confirmed against BW capture 5-1-26 / bwcap3sec frame 20: params
|
||||||
|
# logical bytes `00 01 11 10 00 00 00 00 00 00 00` (counter=0x1000)
|
||||||
|
# are encoded on the wire as `00 01 11 10 10 00 00 00 00 00 00 00`.
|
||||||
|
# BW frames 13/14 (meta @ 0x1002 / 0x1004) leave `10 02` and `10 04`
|
||||||
|
# raw — the device handles those literal pairs correctly.
|
||||||
|
i = 0
|
||||||
|
while i < len(raw_params):
|
||||||
|
b = raw_params[i]
|
||||||
s.append(b)
|
s.append(b)
|
||||||
|
if (
|
||||||
|
b == 0x10
|
||||||
|
and i + 1 < len(raw_params)
|
||||||
|
and raw_params[i + 1] not in (0x02, 0x03, 0x04, 0x10)
|
||||||
|
):
|
||||||
|
s.append(0x10) # double the 0x10 so it survives device de-stuffing
|
||||||
|
i += 1
|
||||||
|
|
||||||
# DLE-aware checksum: for 0x10 XX pairs count XX; for lone bytes count them
|
# DLE-aware checksum: for 0x10 XX pairs count XX; for lone bytes count them
|
||||||
chk, i = 0, 0
|
chk, i = 0, 0
|
||||||
|
|||||||
Reference in New Issue
Block a user