Audit findings, 2026-08-06 - partial¶
Two of the planned sweeps in PLAN-PROTOCOL-AUDIT-2026-08-06.md
completed before a usage limit stopped the rest. Their results are below. The other two
sweeps - protocol unknowns across SRC+DOC, and the source-material inventory - never ran.
Provenance. These findings come from analysis agents, not from a line-by-line read by the author. Every claim carries a path and line so it can be checked. Two headline claims WERE spot-checked and one of them was wrong - see the correction in section 2. Verify before acting.
1. The best finding: LAPB build and parse keep separate copies of the same constants¶
E:\Dev\Ronny\NDInsight\SINTRAN\XMSG\SRC\Xmsg.Live\LapbLayer.cs:98-110 names every LAPB
control-field constant:
private const byte SabmBase = 0x2F; DiscBase = 0x43; UaBase = 0x63;
private const byte DmBase = 0x0F; FrmrBase = 0x87;
private const byte RrNibble = 0x01; RnrNibble = 0x05; RejNibble = 0x09;
private const byte PollFinalBit = 0x10;
E:\Dev\Ronny\NDInsight\SINTRAN\XMSG\SRC\Xmsg.Hdlc\LapbFrame.cs - the parse side - re-types
the identical values as bare literals: 0x2F/0x43/0x63/0x0F/0x87 at lines 307-315,
0x1/0x5/0x9 at 281-285, 0x10 at 181 and 305, & 0x0F at 279, and the N(S)/N(R) shifts
and & 0x07 masks at 153-167.
VERIFIED by direct read on 2026-08-06. Both halves exist exactly as described.
Why this is the top item. One side builds frames, the other parses them, and they hold
independent copies of the same numbers. An edit to one silently desynchronises the other - which
is precisely the failure mode that has cost this project the most time this week
(TadTerminalResponder was a hand-copy of TadSession and carried an identical checksum bug).
Proposed fix: a public static class LapbControl in Xmsg.Hdlc (the lower project, which
Xmsg.Live already references) holding the five U-frame bases, three S-frame nibbles,
PollFinalBit, SequenceMask, NsShift, NrShift. Delete the private copies in LapbLayer.cs.
Risk: low. These are compile-time constants with no behavioural coupling. The LAPB tests should pass unchanged; if any moves, the two copies had already diverged and that is the bug.
2. CORRECTION - the HDLC framing bytes are NOT four bare copies¶
The agent reported 0x7E / 0x7D / 0x20 as duplicated bare literals across four files and
called it "the worst duplication in the audit". That is wrong. Checked directly:
Xmsg.Hdlc\HdlcDeframer.cs:24,29,34-private const byte Flag/Escape/EscapeMask. Named.Xmsg.Live\HdlcEncoder.cs:29,34,39- the same three, also named.
The remaining grep hits are XML doc comments, which are documentation, not code. So this is two private copies of correctly-named constants in two projects - mild duplication worth tidying, not a bare-literal defect and nowhere near the top of the list.
Recorded because the mis-ranking matters: an agent's severity ordering is a suggestion, not a measurement.
3. FRMR reason bits should be a [Flags] enum¶
E:\Dev\Ronny\NDInsight\SINTRAN\XMSG\SRC\Xmsg.Live\LapbLayer.cs:112-115 - VERIFIED by direct
read:
// FRMR reason bits (spec 2.3.3), OR-combined into the diagnostic's third byte.
private const byte FrmrReasonW = 0x01; // control field invalid / not implemented
private const byte FrmrReasonY = 0x04; // I-field length exceeded the maximum
private const byte FrmrReasonZ = 0x08; // N(R) invalid (outside [V(A), V(S)])
The comment says OR-combined; that is the definition of a flags enum. Proposed:
[Flags]
public enum LapbFrmrReason : byte
{
None = 0,
ControlFieldInvalid = 1 << 0, // W
InformationFieldTooLong = 1 << 2, // Y
ReceiveSequenceInvalid = 1 << 3, // Z
}
The gap at 1 << 1 is deliberate - the wire positions are spec-fixed, not sequential.
The missing bit is a real question, not a bug. Standard LAPB puts reason X ("information
field not permitted in this frame type") at 0x02. We neither send nor recognise it. The agent
flagged that its bit position is standard-LAPB-derived and not verified against the ND spec
section 2.3.3. Do not add it as fact - add it marked UNVERIFIED, or read section 2.3.3 first.
4. Other literals worth naming (agent-reported, NOT individually verified)¶
Ranked by whether getting it wrong corrupts a frame.
| Where | What | Proposed |
|---|---|---|
Xmsg.Ethernet\NdLinkLayer.cs:312 |
if (header.ReceiverLinkId != 0) |
!= UnknownPeerLinkId - the constant is declared in the same file at line 55, and this is the peer-learning gate whose comment records a live defect from getting the branch wrong |
Xmsg.Ethernet\NdLinkHeader.cs:386 |
(Kind & 0xF0) == 0x60 |
NpduType == NdNpduType.DisconnectRequestByNetworkService - NpduType already IS the high nibble, so no new constant is needed |
Xmsg.Hdlc\Fcs16.cs:45,84 + Xmsg.Live\HdlcEncoder.cs:57 |
0xFFFF init and one's-complement, re-implemented in the encoder |
Fcs16.InitialValue + a shared Fcs16.ComputeTransmitted(...) |
Xmsg.Hdlc\LapbFrame.cs:139,149 + Fcs16.cs:77,82 |
frame overhead 4 and 2, unnamed in two files |
Fcs16.FcsLength, LapbFrame.HeaderLength, LapbFrame.MinimumFrameLength |
Xmsg.Ethernet\NdLinkLayer.cs:427 |
ConnectionConfirmKindUnverified = 0x1F declared 300 lines from the enum it belongs to |
move into NdLinkFrameKind (NdLinkHeader.cs:96-143) keeping its UNVERIFIED remarks |
Xmsg.Ethernet\TcpEthernetBackend.cs:46 + UdpEthernetBackend.cs:44 |
3094 declared twice with identical doc |
one NdEthernetDefaultPort |
Deliberately NOT to be changed, and the agent was right to say so:
LapbLayer.MaxInformationLengthis derived from the fragment constants with a full derivation note. Correct as-is.Ieee8023Frame.cs,NdMacAddress.cs,HdlcDeframer.cs,LapbOptions.csare already fully named.NdLinkFrameKindis missing CC / WO / DR-by-user / DC on purpose - their wire bytes have never been captured and the file says so rather than guessing. Leave the gap.
5. Allocation findings (agent-reported, NOT verified)¶
No LINQ and no foreach anywhere in the audited scope - the index-loop discipline is intact.
What the agent found instead was allocation on the receive path:
Xmsg.Live\HdlcEncoder.cs:64,76- aList<byte>plusToArray()per transmitted frame, when line 64 already computes the exact worst-case size. ASpanoverload removes both.Xmsg.Live\Seam\LapbLayerAdapter.cs:353-362andXmsg.Live\LiveNode.cs:221-230- each received frame is re-wrapped in flags just to reuse the splitter: three allocations per frame.HdlcDeframer.Unstuffis private (HdlcDeframer.cs:127); making it public removes the wrapper and the list.- The receive accumulator is a
List<byte>filled oneAddper byte - the hottest loop in the stack. LapbLayer.cs:940,965- a 4-byte array per supervisory/unnumbered frame;stackallocwould do, but only after theLapbTransmitdelegate takes aReadOnlySpan<byte>.
Not a defect, leave it: LapbLayer.cs:385 _pending.Enqueue(info.ToArray()) - the copy is
required because the payload must survive until acknowledged, and the remarks say so.
6. COSMOS Ethernet / ENNS0 gaps (separate agent, NOT verified)¶
A different sweep inventoried the Ethernet transport docs. Its top items, all offline-closable unless noted:
- ENNS0, the ND-100-side COSMOS Ethernet driver, has never been read. It is the parent of
most other gaps - without it we can only echo frames we have seen. Already linked and ready
for Ghidra at
Installation\Communication\Ethernet\x\linked\ENNS0-LNK.PROG, entryENNS0at octal 32241. - Wire bytes for NPDU types WO, DR-by-user and DC are unknown, as is the low nibble of the kind byte. We cannot originate a window update or a user disconnect.
- ~~The C# node cannot relay (route-through)~~ - THIS CLAIM IS WRONG. Checked
2026-08-06. The agent took it from a doc line dated 2026-08-01 that has since gone stale.
Relay IS implemented:
Xmsg.Node\Seam\SintranDatagramRelay.csdoes the stamping and the checksum recompute,Xmsg.Node\Seam\DatagramRelay.csdoes the routing decision including the never-send-back-out-the-arrival-link rule, andXmsg.Node.Tests\DatagramRelayTests.cscovers both. The actual gap is narrower: nothing outside the tests ever constructs aDatagramRelay, so it is a complete tested component with no production caller. Wiring it in needs a node holding two links and a route table fromtopology.json. The source doc has been corrected. ENNS0_STARTED_FLAGsemantics are wrong in our emulator comment -0x4C0read0x0000on all 324 polls while the link worked, contradicting the "1 = server ready" comment.- The "goes deaf after idle" failure has never been traced with the named cells - the only trace covers a healthy link. Cheapest outstanding live experiment.
The same sweep listed a dozen items as already closed by later docs - reference field order,
word 6 as a checksum, 0x1F = CC, route-through on Ethernet, the four stray padding bytes. Do
not re-open those.
7. Source-material inventory - the three best unmined items¶
The inventory sweep completed. Full detail is in the agent's report; these are the items I verified myself and the one claim I had to correct.
VERIFIED PRESENT, and none of them cited anywhere in DOC\:
| Item | Size | Why it matters |
|---|---|---|
F:\ND\SINTRAN-L-XMSG\FLOPPY\210373L_X-Message.pdf |
3,325,116 B, 44 pages | ND's own X-Message manual, product 210373 - the exact product on our wire. A pure scan with no text layer, so it needs the OCR pipeline before it can be grepped |
E:\Dev\Ronny\NDIX-C\baseline\bin\cps\xmsgerrors.h |
2,956 B | see below - already paid off |
E:\Dev\Ronny\NDIX-C\baseline\bin\cps\xrouterrors.h |
2,924 B | the same for XROUT |
E:\Dev\Ronny\NDIX-C\baseline\bin\cps\systemerrors.h |
20,124 B | SINTRAN file-system error codes, which FA replies carry |
E:\Dev\Ronny\NDInsight\SINTRAN\XMSG\COS-FSART-E02.BPUN |
66,074 B | FSART, implicated in the known d102 hang |
E:\Dev\Ronny\NDInsight\SINTRAN\XMSG\COS-FAU-VSX-E03.BPUN |
90,486 B | the file-access unit behind the FA operations we still infer |
CORRECTED: the sweep flagged XMSG-PL-VALUES-M.INCL / XMSG-VALUES-M.SYMB as under-used and
warned "the constants we quote may be a revision behind the machine we test against". We are
not. xmsg-constants.json declares xmsg_version: M and names that exact include as its
source - checked. An L-vs-M diff is still worth doing, but to learn what CHANGED between
revisions, not because we are stale.
7a. The error headers already paid for themselves¶
xmsgerrors.h and xrouterrors.h are ND-authored (Norsk Data Ltd, NDIX release 3, dated
1988-08-12). They carry something our XmsgError / XroutError enums do not: a recovery
disposition for every code - what a client is supposed to DO about it.
struct SIIIerror xmsgerrors[] = {
XMSUX, "No error", SIII_OK,
XMXEACB, "All Channels Busy.", SIII_RETRY,
XMXEIMA, "Invalid magic number.", SIII_RETRY,
XMXEILF, "Illegale function code in monitor call.", SIII_GIVE_UP,
...
Across the two files, 99 classified entries: 57 SIII_GIVE_UP, 35 SIII_RETRY, 2 SIII_UNKNOWN,
2 SIII_SUSPEND, 2 SIII_OK, 1 SIII_SLEEP.
Directly relevant to the open post-close XEIMA (section "Known open questions"): XEIMA is
classified SIII_RETRY, not SIII_GIVE_UP. So a client that meets "invalid magic number" is
expected to try again rather than abandon the conversation. That fits what we see exactly - every
FA operation completes, D100 objects after the close, and nothing is harmed because there is
nothing left to retry. It does not explain WHY the close draws it, but it does explain why it has
never hurt.
Worth importing the dispositions into our enums as XML documentation, and worth reading the plain-English descriptions - they are ND's own wording for codes we currently carry as bare names.
What to do first¶
- Section 1 - unify the LAPB control constants. Verified, low risk, removes a live build/parse desync hazard.
- Section 3 - the FRMR
[Flags]enum, without inventing reason X. - Section 4 rows 1-2 - the two literals on branch conditions, where a wrong branch has already caused a defect once.
- Everything else is worth doing but is tidying, not risk reduction.
Still not run: the protocol-unknowns sweep and the source-material inventory. Those are the two that answer "what don't we know" - the question that prompted this. Relaunch them from the plan document.