LAPB requirements — ND point-to-point link (compact validation spec)¶
Authoritative source. The single normative LAPB Layer-2 spec now lives at WSL
/home/ronny/repos/os/x25emu/docs/lapb-nd-spec.md(the X25Emulator repo); where this compact note and that spec disagree, the spec wins. The C#LapbLayer(SINTRAN/XMSG/SRC/Xmsg.Live/LapbLayer.cs) has been rebuilt to conform to it — seeLAPB-CONFORMANCE.mdin this folder for the section 8.1 checklist mapped to code and tests, and the byte vectors S1-S8 inSINTRAN/XMSG/SRC/Xmsg.Live.Tests/LapbConformanceVectorsTests.cs. This note is retained as a quick companion checklist.
Self-contained requirements for the LAPB/HDLC layer used between Norsk Data SINTRAN III
machines (and our C# node) over an nd100x --hdlc byte stream. Written so a reviewer
(human or LLM) can validate an implementation against it WITHOUT other documents.
Normative words: MUST / MUST NOT / SHOULD. Source of truth: 13 FCS-verified captures
(1947 frames) + live runs; details in XMSG-PROTOCOL.md §2–3. Items marked [ND] deviate
from ITU-T X.25 LAPB; items marked [STD] are standard LAPB the wire is assumed to follow
where the corpus is silent.
1. Framing (async HDLC)¶
- F1. Frames MUST be delimited by
0x7Eflags:7E | stuffed(frame | FCS) | 7E. - F2. Byte-stuffing MUST escape exactly
{0x7E, 0x7D}as0x7D, byte^0x20— including the two FCS bytes — and de-stuffing MUST be the exact inverse. - F3. FCS-16: CRC-CCITT poly
0x1021(reflected0x8408), init0xFFFF, one's-complement, transmitted LOW byte first, computed overaddress|control|info(flags excluded). Receiver check: CRC folded overframe|FCS== residue0xF0B8. - F4. Any
0x7EMUST terminate frame accumulation (it can never occur inside a stuffed frame). FCS-invalid frames MUST be discarded — and SHOULD be counted/logged, never silently (a silent drop hid a live fault once). - Test vector:
01 3F 00 64→ FCS0x092E→ wire7E 01 3F 00 64 2E 09 7E.
2. Frame formats¶
- A1. [ND] Address byte encodes frame ROLE, not station:
0x01= link management (SABM/UA),0x09= data transfer (I/RR/RNR/REJ). There is no command/response addressing. - A1b. [ND] Address bit
0x80= odd-info-length marker. An I-frame whose information field has an ODD byte count MUST carry address0x89; even-length I-frames carry0x09. VERIFIED across all captures (230+ data frames, zero true counterexamples; the apparent ones are byte-stuffing artifacts in the dumps and non-XMSG noise in test1). S/U frames always have 2-byte (even) info, so0x81/0x89never occur on them. A real ND machine silently DISCARDS an odd-length I-frame sent with0x09(before LAPB sequence processing — V(R) does not advance; the next frame then draws REJ), and the discard is deterministic on retransmission — this was the root cause of two live stalls. RX side: accept both values, route onaddress & 0x7F, and MAY validate the parity bit against the actual length. Mechanism [INFERRED]: the ND-100 word-oriented DMA needs to know whether the final 16-bit word of the info field carries one or two valid bytes; a wrong flag off-by-ones the length/FCS reconstruction. ITU-T LAPB has no such bit — pure ND extension. - A2. Control byte (modulo-8): I-frame
bit0=0,ctrl = N(R)<<5 | P<<4 | N(S)<<1; S-framebits1..0=01,ctrl = N(R)<<5 | PF<<4 | type<<2 | 01with RR=0 RNR=1 REJ=2 (i.e. low nibble RR0x1, RNR0x5, REJ0x9); U-frames: SABM0x3F, UA0x73, DISC0x43, DM0x0F, FRMR0x87[STD for the last three — not in corpus]. - A3. S-frame subtype MUST be decoded via
control & 0x0F.control & 0x03alone cannot distinguish RR/RNR/REJ. Trap: REJ's low nibble (0x09) equals the data ADDRESS byte — logs and parsers MUST NOT conflate them, and MUST NOT label every S-frame "RR". - A4. [ND] SABM, UA and RR MUST carry a 2-byte info field = the sender's node (CPU)
number, big-endian decimal (100=
0x0064, 102=0x0066, 103=0x0067). The state machine MUST be initialized with the local node number and stamp it on every locally-originated link-management frame. Other U/S frames: unverified on the wire — stamp the same way, tolerate either on receive. -
A5. I-frame information field = opaque upper-layer (SINTRAN/XMSG) payload. The LAPB layer MUST NOT read or depend on it; max info field 622 bytes.
CORRECTED 2026-08-05, was 312. Measured over all 33 recorded
.pcapngfiles, 3673 information frames:largest info field 622 over the stated 312 452 size bands 0:3041 100:132 200:48 400:226 600:226The 226/226 pairing is the whole story: those are fragment pairs - a 622-byte first fragment and its ~450-byte continuation - and they occur in the four file-transfer captures and nowhere else. Every other traffic class (file access, listing, TAD, routing) tops out at 292, which is why 312 held for so long.
622 is not a new measurement so much as a derived one: it is
SintranMessageFragment.FirstFragmentBodyLength + FirstFragmentBodyOffset= 594 + 28, the largest frame the fragmentation scheme can produce, and exactly the largest ever recorded.LapbLayer.MaxInformationLengthcomputes it that way rather than restating the number. Pinned byLapbInformationLengthTests; the measurement is commit86eab68.A receiver enforcing 312 answers a legal fragment with an FRMR, so a file can be sent but not received - which is exactly how it presented.
The X25Emulator spec still says 312 (WSL
~/repos/os/x25emu/docs/lapb-nd-spec.md). That document is in another repository and has NOT been updated; the two disagree deliberately until someone with that repo open fixes it.
3. Link establishment¶
- E1. [ND] The link is balanced: BOTH stations send SABM and each answers the peer's SABM with UA (info = own node number). The link is up when both directions completed SABM→UA.
- E2. On every SABM sent-and-UA-received, and on every SABM received (answer UA): V(S)=V(A)=V(R)=0 and the retransmit queue is cleared. Sequence "adoption" from the peer's first I-frame MUST NOT be implemented (a live desync source; the ND kernel resets on SABM).
- E3. On receiving the peer's node number (from SABM/UA/RR info), record it as the PHYSICAL neighbour id, exposed read-only. It MUST NOT be used as a routing source — on relayed traffic the upper layer's logical source differs from the LAPB peer.
- E4. Periodic RR keepalive (carrying the node number) SHOULD be sent when idle.
4. Data transfer state machine¶
- D1. Maintain V(S) (next send), V(A) (oldest unacked), V(R) (next expected), all mod 8.
- D2. Send window: at most k unacked I-frames outstanding, k ≤ 7. Every sent I-frame MUST be retained in a retransmit queue keyed by N(S) until acknowledged — a single "last frame" buffer is non-conformant (this node routinely has 2–4 in flight).
- D3. Every received I/RR/RNR/REJ MUST process N(R) as a CUMULATIVE ack: frames V(A) through N(R)−1 acknowledged and dropped from the queue; V(A)=N(R). N(R) outside [V(A), V(S)] is a protocol error (log; recover by link reset) [STD].
- D4. In-sequence I-frame (N(S)==V(R)): deliver payload up, V(R)+=1, acknowledge with RR(V(R)) (or piggyback N(R) on the next I-frame).
- D5. Duplicate I-frame (N(S) behind V(R)): discard payload, answer RR with current V(R). Out-of-sequence I-frame (gap): discard, send REJ(V(R)) once until it clears.
- D6. Received REJ(N(R)): MUST immediately retransmit ALL unacknowledged I-frames starting at N(R) (go-back-N).
- D7. Received RNR: peer is busy — ack per N(R), then queue (MUST NOT send new I-frames); probe with RR; resume on RR. Note: a busy ND peer legally DISCARDS I-frames sent into RNR — this is how a lossless TCP link "loses" frames.
- D8. T1 retransmission timer MUST run in the live event loop (start on send when queue was empty; restart on partial ack; stop when V(A)==V(S)). On expiry: retransmit the oldest unacked I-frame (or poll with RR P=1); N2 retries then declare link failure and re-establish. An implementation whose runner "passes no tick" fails this.
- D9. [STD] Any received S/I frame with P=1 MUST be answered promptly with an F=1 supervisory carrying current N(R). (ND P/F usage unproven in captures; implement the standard behaviour.)
- D10. DISC received → answer UA, go disconnected [STD, not in corpus].
5. Layering rule¶
- L1. Exactly ONE LAPB state machine per physical link. It knows nothing of XMSG; the XMSG datagram sequence (Flags1/secure-ACK) is an independent end-to-end layer and MUST NOT share state, counters or identity with LAPB. U/S frames never carry XMSG bytes.
6. Validator checklist (ask these of the implementation)¶
- Does S-frame parsing extract
control & 0x0F? Do logs print RR/RNR/REJ distinctly? - Is there a retransmit queue covering the whole window (not one buffer)? Show the go-back-N path triggered by REJ.
- Where is T1 ticked in the LIVE runner (not just in a test)? What are T1/N2 values?
- Is RNR handled (busy flag blocks new I-frames, RR probe)?
- Is P=1 answered with F=1?
- On SABM (sent or received): are V(S)/V(A)/V(R) reset to 0 and the queue cleared? Grep for any "adopt peer sequence" logic — it must be gone.
- Are FCS bytes byte-stuffed on TX? Does the test vector in §1 round-trip?
- Are FCS-invalid RX frames counted/logged?
- Is the state machine constructed with the 16-bit node number, and stamped on SABM/UA/RR info fields? Is the peer's number kept OUT of routing decisions?
- Can two I-frames be outstanding and the FIRST be retransmitted on REJ(first)? (Regression test for the depth-1-buffer bug.)
- Does any code path send an I-frame while peer-busy (RNR) is set?
- Is the LAPB module free of references to XMSG types/fields (layering, §5)?
7. Minimal conformance scenarios (unit-testable)¶
- S1. Balanced bring-up: exchange SABM/UA both ways → link up, all counters 0.
- S2. Send I(ns=0), I(ns=1); receive REJ nr=0 → both frames retransmitted in order.
- S3. Send I(ns=0); receive RNR nr=1 → ack recorded, sends held; RR nr=1 → resume.
- S4. Send I(ns=0); NO response → T1 fires → retransmit; ×N2 → link declared down, SABM re-sent.
- S5. Receive I(ns=V(R)+1) (gap) → REJ(V(R)) emitted, payload not delivered.
- S6. Receive RR P=1 → RR F=1 with current N(R) emitted immediately.
- S7. Mid-session SABM received → UA sent, counters reset, queue cleared; next I goes out with ns=0.
- S8. TX frame whose FCS bytes contain 0x7E/0x7D → correctly stuffed and decodable by the RX path.
Compiled 2026-07-04 from XMSG-PROTOCOL.md §2–3.6/§17 and the confirmed live defects
in the master review (XMSG-MASTER-REVIEW-ANSWERS-2026-07-03.md).