Windowed RX receipts: app-layer delivery truth, frame-exact against the rx.seq ledger (#363 step 3) - #370
Conversation
…he rx.seq ledger Neither the hardware ACK (horizon = chip-FIFO admission) nor CCX reports (fw emission ceiling ~1.3k/s) can state what the receiving APPLICATION got. src/cell/RxReceipt.h closes that: the receiver notes every consumed frame index in a sliding ring bitmap (ReceiptWindow) and mails overlapping, versioned receipt TLVs on its feedback path; the transmitter merges them into a delivered-set (ReceiptLedger — idempotent, strict-prefix parse, absorbs only receipts naming its own TA). Same caller-side contract as UeRxAttribution: demos feed frames in, the RX loops are untouched, the library never assumes a payload convention. Wiring: duplex notes on the existing rx.seq path and injects a receipt frame every DEVOURER_RX_RECEIPT_MS (802.11 data at 6M, RA = the receipted transmitter; concurrent send_packet callers serialize on a demo mutex); txdemo absorbs under DEVOURER_TX_RECEIPTS and emits every receipt as a tx.receipt event WITH the raw TLV hex, so tests/receipt_verify.py replays the merge and compares SET-EXACTLY against the receiver's own rx.seq ledger. tests/arq_e2e_delivery.sh grows the RECEIPT_MS knob; ctest grows the receipt_roundtrip selftest. Measured on air, both regimes FRAME-EXACT: 126,594 frames clean (1,671 receipts) and 349,455 frames under 150 ms consumer stalls at 2.4k fps with the spsc-fat parking ring (1,330 receipts). Two bench-taught lessons are in the code: received bodies carry the trailing FCS, so the TLV parse is strict-prefix rather than exact-length (an ACK logging len 14 = 10+FCS was the tell); and the window must exceed the worst backlog drain in frames — a 2,048-bit window leaked 2,846 delivered frames out of coverage when a stalled pool drained ~3k frames inside one receipt interval, hence the 8192 default, the DEVOURER_RX_RECEIPT_WINDOW knob, and the sizing rule at the class comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoWindowed RX receipts for app-layer delivery truth (frame-exact vs rx.seq)
AI Description
Diagram
High-Level Assessment
Files changed (9)
|
Code Review by Qodo
1.
|
…n cap - ReceiptWindow bit addressing derived the word from idx % _w but the bit from idx % 64 — equivalent only when the window is a multiple of 64, and the size is caller-chosen. Both now derive from the slot; the selftest gains a 100-bit window crossing the ring seam. - ReceiptLedger::absorb bounds the bitmap (default cap 2^26 indices): base arrives over the air, and an unbounded resize would let one crafted TLV allocate half a gigabyte. Crafted-base rejection covered in the selftest, and the reject leaves the ledger untouched. Validated: 49/49 ctest incl. the new cases; on-air receipts smoke stays FRAME-EXACT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes #363.
The tier this completes
The ARQ campaign established the two lower tiers' limits: the hardware ACK's horizon is chip-FIFO admission (it can never confirm host delivery — #359), and per-frame CCX reports collapse against the fw's ~1.3 k reports/s emission ceiling (#368, sampled in #369). This PR adds the tier neither can reach: the receiving application counts what it consumed and mails it back.
src/cell/RxReceipt.h—ReceiptWindow(receiver: sliding ring bitmap over the last W frame indices) +ReceiptLedger(transmitter: idempotent merge of overlapping, versioned receipt TLVs; strict-prefix parse; absorbs only receipts naming its own TA). Same caller-side contract asUeRxAttribution: demos feed frames, RX loops untouched, no payload convention assumed.rx.seqpath, injects a receipt frame everyDEVOURER_RX_RECEIPT_MSon the feedback path (802.11 data at 6M; concurrentsend_packetcallers serialize on a demo mutex).DEVOURER_RX_RECEIPT_WINDOWsizes coverage (default 8192).DEVOURER_TX_RECEIPTSabsorbs and emits every receipt astx.receiptwith the raw TLV hex.tests/receipt_verify.py: replays the TLV merge and demands set-equality with the receiver's ownrx.seqledger — the issue's acceptance bar, literally.receipt_roundtripctest selftest;RECEIPT_MSknob in the arq harness; docs (logging.md rows, scheduled-mac.md tier paragraph).Measured (on air)
Two bench-taught lessons, in the code
len:14(10 + FCS) was the tell. The parse is strict-prefix now, with the TA-match keeping arbitrary payloads out.MISSINGin the verifier,late=0— eviction, not reordering). The sizing rule is at the class comment:window_bits > pool_bytes / min_frame_bytes + one encode interval of arrivals; the 8192 default clears this bench's worst case ~2.7×.Validation
ctest49/49 (new selftest included); four on-air runs (first-light smoke, full-scale clean, failed 2048-window stress — kept as the sizing lesson — and the passing 8192-window stress). The verifier'sNO-RECEIPTSandMISMATCHpaths were both exercised for real during bring-up.🤖 Generated with Claude Code