AX.25: XID/MDL responder + peer-capability cache — activates SREJ + v2.2-preferred connect - #63
Merged
Merged
Conversation
…te with C#) New `src/ax25/xid/` module mirroring `Packet.Ax25.Xid`: - `info_field` — FI/GI/GL header + ordered PI/PL/PV parameter codec (`encode`/`encode_into`/`parse`/`parse_with`). Zero-alloc `encode_into`; strict-by-construction encode; ascending-PI ordering; §3.8 MSB-octet-first HDLC PV. - `parameters::XidParameters` — decoded parameter set (`None` = absent). - `classes_of_procedures::ClassesOfProcedures` — PI=2 duplex (ABM bit 0). - `hdlc_optional_functions` — PI=3 reject/modulo/segmenter + `RejectMode`. - `parse_options::XidParseOptions` — Strict (default) / Lenient leniency knobs. Tests lift the exact Figure 4.6 worked-example bytes and the strict-vs-lenient pairs from the C# `XidInfoFieldTests`. 27 XID tests green; clippy clean; no_std (--no-default-features --features alloc) builds. Co-Authored-By: Claude Code <noreply@anthropic.com>
- bridge `classify_incoming`: 0xAF/0x87/0xE3 now map to XidReceived /
FrmrReceived / TestReceived instead of falling to ControlFieldError. XID is a
single XidReceived event (the command/response split rides in
FrameInfo::is_command), matching the C# Ax25FrameClassifier.
- signal: new FrameSpec::Xid { is_command, pf, info } — an XID U-frame carrying
no PID, info = encoded XID parameter TLVs. build_frame gains its arm; it falls
through encode_spec's extended match (U-frames are 1 octet in both modulos).
Tests: XID command/response classify, FRMR/TEST classify, and a
FrameSpec::Xid build→decode→classify round-trip. 664 core tests green; clippy
clean; no_std builds.
Co-Authored-By: Claude Code <noreply@anthropic.com>
…stener) New `src/sdl/mdl.rs` porting the on-air MDL pieces: - `apply_negotiated` — the §6.3.2 reverts-to merge (lesser-of reject/modulo, min-of window/N1, max-of T1/N2, mutual-AND segmenter), mirroring `XidNegotiator.ApplyNegotiated`. - `apply_version_20_defaults` — the §1436 full v2.0 default set. - `default_offer_for` — derive our XID offer from the session context. - `respond_to_xid_command` / `respond_pre_session_xid` — the un-transcribed figc5.1 responder: seed SREJ-capable, merge the command's offer, apply agreed values to the context, return the F=1 XID response info. Mirrors `Ax25ManagementDataLink.RespondToXidCommand` + `HandleNoCachedSession`. Manager wiring: `post_with_local` intercepts an inbound XID *command* for a Disconnected (pre-session) slot with accept_incoming, answers it directly (connectionless — no LM-SEIZE, no ConnectIndication), and stages the negotiated params on the cached slot so the subsequent SABM's Set Version 2.0 preserves `srej_enabled` into the link. Tests: the §6.3.2 merge per-parameter (mirroring XidNegotiatorTests), the v2.0 defaults, the responder (SREJ kept/reverted/empty-info), and two manager integration tests mirroring Ax25ListenerPreSessionXidTests (XID command → XID response, not DM; SABM-after-XID → Connected with SREJ adopted). 677 core tests green; clippy clean; no_std builds; fw release build + test cross-compile green. Co-Authored-By: Claude Code <noreply@anthropic.com>
…eerCapabilityCache) New `src/sdl/capability.rs` — a fixed-capacity, no_std, alloc-free port of `Packet.Node.Core.Capabilities.PeerCapabilityCache`: - `plan_dial(port, peer, policy, now_ms)` — miss ⇒ optimistic policy default; fresh learned positive/negative honoured; stale (>30d) re-probed. Extended and pre-connect-XID decisions exactly as C# PlanDial. - `record_outcome(...)` — plan-aware learning (a dimension is learned only when the dial probed it; last_refused stamped on an extended degrade, carried forward otherwise), mirroring RecordOutcome. - `PeerCapabilityRecord` / `PeerDialPlan` / `PeerDialPolicy`. Embedded deviations (documented): in-memory only (no SQLite; LRU-evict on full), u8 port index instead of a string PortId, explicit now_ms u64 instead of TimeProvider. Manager: `connect_planned(local, peer, plan, timers)` supplies the plan's extended choice at dial time (SABME vs SABM). The initiator pre-connect-XID *probe* (async NegotiateSrejBeforeConnectAsync fast-probe) is documented as deferred — the responder half (task 3) + apply_negotiated are in place for a fw-side initiator MDL. 26 capability tests (mirroring PeerCapabilityCacheTests) + a connect_planned test. 698 core tests green; clippy clean; no_std builds. Co-Authored-By: Claude Code <noreply@anthropic.com>
The SREJ recovery code was correct but dormant: `srej_enabled` was never set (no XID machine). Task 3's pre-session responder / `apply_negotiated` now sets it from the negotiated XID, so Selective Reject engages on-air. Tests: a negotiated-SREJ link emits an SREJ (targeting the gap V(R)) on an out-of-sequence I-frame and stores the frame; a go-back-N link (SREJ not negotiated) emits REJ instead — proving the SREJ path is gated on the XID-negotiated `srej_enabled`. Added a Recorder `supervisory()` helper. Clippy clean. Co-Authored-By: Claude Code <noreply@anthropic.com>
…endedConnect) Now that both refusal degrades are present on the session (FRMR fallback #45 and DM-refusal degrade #48), a plain `SessionManager::connect` defaults to a mod-128 SABME-first dial — matching `Ax25ListenerOptions.PreferExtendedConnect = true` — and degrades to a mod-8 SABM re-establishment if the peer refuses, rather than stranding the connect. - `SessionManager::new`: prefer_extended_connect true; updated docs on `new` and `with_prefer_extended_connect` (removed the stale "#48 owned by another track" rationale). - Updated `plain_connect_honours_prefer_extended_default` for the new default (SABME-first; opt-out → SABM). - New regression `default_extended_connect_degrades_to_mod8_sabm_on_dm_refusal`: a DM-refusing peer degrades to mod-8 SABM (the DM analogue of the FRMR test) — the safety net that makes the flipped default safe. 701 core tests green; clippy clean; no_std builds. Co-Authored-By: Claude Code <noreply@anthropic.com>
`vectors/xid.json` carries C#-derived XID information-field bytes (the exact Figure 4.6 worked-example bytes XidInfoFieldTests pins, canonical + literal-print variants, the empty header, and a mod-8 SREJ interlink offer). New `tests/xid_golden_vectors.rs` parses each vector, asserts the decoded parameters, and (when roundtrip) re-encodes byte-identical — proving the pico-node XID codec is byte-for-byte with Packet.Ax25.Xid. The parity drift-guard (`node scripts/parity-check.mjs`) passes (0 gaps): the xid vector set was already opted-in; this backs the declaration with real byte-parity vectors. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The XID keystone (Table A #4/#5, In). Byte-for-byte with C#
Packet.Ax25.Xid(validated against spec Figure 4.5-4.6 golden bytes +XidInfoFieldTests):ax25/xid/*) — FI/GI/GL + PI/PL/PV, MSB-first PV, ascending-PI,XidParseOptions(Strict default).ControlFieldError);FrameSpec::Xidoutbound.sdl/mdl.rs) — §6.3.2 reverts-to merge, answers inbound XID (F=1, no ConnectIndication), stages negotiated params so the following SABM preserves them. MirrorsRespondToXidCommand/HandleNoCachedSession.sdl/capability.rs) — fixed-capacity port ofPeerCapabilityCache;connect_planneddial seam.srej_enabledwas never set).prefer_extended_connectdefault → true (matches C#PreferExtendedConnect), with a DM-refusing-peer degrade-to-mod-8 regression (safe now fw: unified node web panel (pre-filled config + firmware + maintenance) #48 is on main).vectors/xid.json+ runner; drift-guard passes 0 gaps.+69 tests (702 total). clippy/no_std/fw-build/drift-guard all green.
One flagged residual: the initiator pre-connect XID probe (
NegotiateSrejBeforeConnectAsync— proactively sending our XID before we dial) is NOT included — an async multi-step flow best driven fw-side; the agent stopped rather than guess. The responder half is complete and the seam (apply_negotiated+capability.rs) is in place for it.🤖 Generated with Claude Code