fix(mstp): Clause 9 CRC, USB stream assembly, and 9.5.6 token/PFM - #467
Conversation
Host async serial reads arrive in multi-millisecond chunks; treating inter-read gaps as Clause 9 T_frame_abort (~1.56ms @ 38400) discarded partial frames mid-assembly. Add decode_frame_stream (Complete/NeedMore/ Invalid), preserve trailing lone 0x55 on preamble miss, and replace wire T_frame_abort with a host stale-partial timeout based on max frame wire time plus USB scheduling slack. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace incorrect reflected polynomials (0xE0 / Modbus 0xA001) with Clause 9.6 values (0x81 / 0x8408). Add literal golden vectors including the live Token 0<-7 frame (CRC 0x37) and data frame 01 00 → 9F 16 so self-round-trip tests cannot mask interoperability failures. Co-authored-by: Cursor <cursoragent@cursor.com>
Move Clause 9 golden / stream tests into mstp_frame_tests.rs so mstp_frame.rs stays under the upstream file-size CI gate. Co-authored-by: Cursor <cursoragent@cursor.com>
DONE_WITH_TOKEN no longer reverse-scans from NS+1 or emits Token TS→TS. Add done_with_token(), FindNewSuccessor via PFM after Nretry_token, and regression tests A–E for multi-master coexistence. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Follow-up Python surface (separate PR): MS/TP bindings + |
|
Python follow-up (bindings + example, separate from this stack PR): #468 — please merge this MS/TP transport PR first for live trunk coexistence. |
Note from lab / contributor (bbartling)This PR closes the live MS/TP interoperability blockers we hit on a real trunk (Clause 9.6 CRCs, USB chunk ≠ Still outstanding on the rusty-bacnet stack side (not solved by this PR — please treat as follow-up work):
Happy to help on follow-up PRs for (1)–(3) once this lands. The Python MS/TP surface is separate: #468 (depends on this). |
Review cycle 1 — one major repair requiredDF-467-001 (major): oversized outbound NPDUs are rejected too late. The new 501-byte codec cap is correct, but Required bounded repair: validate the supported standard-frame payload limit at queue/reply ingress before destructive state mutation, return or otherwise surface a deterministic failure, and prove oversized values produce no wire output while preserving queue/state invariants. Keep extended frames and segmentation deferred. The holistic review otherwise passed; exact-head CI was green. |
Summary
0xE0/ Modbus0xA001) with BACnet header0x81and data0x8408, plus live golden vectors (Token0←7CRC0x37).T_frame_abort— async serial reads arrive in multi-ms chunks; treating inter-read gaps as wireT_frame_abort(~1.56 ms @ 38400) discarded partial frames. Stream decoder (Complete/NeedMore/Invalid) + host stale-partial timeout.DONE_WITH_TOKENno longer reverse-scans fromNS+1or emits TokenTS→TS; maintenancePSadvancesTS→NSonly; FindNewSuccessor via PollForMaster. Regressions A–E.mstp_frame_tests.rsfor the 700 LOC file-size gate.Lab evidence (not claimed as formal conformance)
Validated on a live MS/TP trunk (BASRT MAC 0 + JCI FEC MAC 7 + Waveshare USB RS-485 @ 38400) with JENEsys Workbench: passive token decode, FEC ReadProperty coexistence, and a Rust mini-device (MAC 3) discovered with points Polled
{ok}. Details in bbartling playground vibe13 docs.Limitations (current)
T_frame_abortis not emulated on chunk boundaries.max_retry_budget3s wall-clock flake is not in this PR (tracked on forkmainonly).Test plan
cargo test -p bacnet-transport --features serialclause956_tests.rsand CRC goldens inmstp_frame_tests.rsMade with Cursor