zerodds-endpoint-nim 1.0 — Spec-Coverage
Source: docs/specs/zerodds-endpoint-nim-1.0.md — the ZeroDDS Nim
endpoint SDK spec (XRCE framing, sync Client, async AsyncReader, reliable
stream).
Implementation:
endpoints/nim/zerodds.nim—Writer/Reader(XCDR2, alignment cap 4),writeFrame/readFrame(XRCE best-effort stream 0x01),Client(sync),AsyncReader(async).endpoints/nim/reliable.nim— sender/receiver state machine + HEARTBEAT/ ACKNACK frame codec (reliable stream 0x80).endpoints/nim/reliable_app.nim— Channel-based drain thread (producer enqueues, a drain thread owns the socket + sender state).endpoints/nim/example_reliable.nim— in-process demo of loss recovery.crates/endpoint-e2e/tests/nim.rs,crates/endpoint-e2e/tests/nim_reliable.rs— live E2E against the Rust XRCE peer.
§1 XRCE framing
Spec: §1 — XRCE WRITE_DATA frame: an 8-byte header (session, stream, seq
LE, submessage id, flags, length LE) + payload, byte-identical to
crates/xrce and every other endpoint SDK.
Repo: endpoints/nim/zerodds.nim::writeFrame/readFrame (best-effort,
session 0x80, stream 0x01); endpoints/nim/reliable.nim::reliableWriteFrame
(reliable, stream 0x80) — identical byte layout, different stream id.
Tests: endpoints/nim/test.nim — the marks "sync loopback: 5 samples OK",
"async loopback: 5 samples OK" and "negative frame vectors: OK"
(byte identity of the wire core, CI job endpoints-nim); the frame roundtrip live in §2/§3/§4.
Status: done.
§2 Sync Client
Spec: §2 — A non-blocking poll client: write frames+sends, poll reads
non-blocking and returns Option[seq[byte]].
Repo: endpoints/nim/zerodds.nim::Client/newClient/write/poll
(monotonic seqNo, wraps at 0xffff).
Tests: nim_endpoint_sync (crates/endpoint-e2e/tests/nim.rs) — a live
poll loop against the Rust peer: an app built from generated idlc types
(gen.nim) plus the endpoint SDK exchanges a typed sample with the shared
Rust XRCE peer over a real UDP socket
(crates/endpoint-e2e/tests/nim.rs::build_nim_endpoint compiles gen.nim + a
copy of zerodds.nim + main.nim, spawns the app).
Status: done.
§3 Async Reader
Spec: §3 — The idiomatic Nim async/await path: recv returns a
Future[seq[byte]] that resolves only once the deframed sample is ready.
Repo: endpoints/nim/zerodds.nim::AsyncReader/newAsyncReader/recv
(an {.async.} proc, polls via sleepAsync(1) until a frame is pending).
Tests: nim_endpoint_async (crates/endpoint-e2e/tests/nim.rs) —
waitFor fut.withTimeout(10000) against the same generated app; Client.write
sends in both modes (the SDK only has a sync writer), the async path exercises
purely the AsyncReader Future.
Tests: (Linux) nim_endpoint_sync, nim_endpoint_async — both green.
Status: done.
§4 Reliable stream
Spec: §4 — XRCE reliable stream (stream_id >= 128, §8.4.10/§8.4.11),
mirrored from the reference crates/xrce/src/reliable.rs: sender submit/
pendingHeartbeat/recvAcknack/getInFlight; receiver recvData/
drainInOrder/pendingAcknack/reset. Window 16, receiver buffer 64,
heartbeat 500 ms, payload <= 65535, RFC-1982 16-bit sequence numbers. The
async writer is built as a Channel-drain thread: the producer enqueues
lock-based (Nim stdlib Channel, not a wait-free ring), a dedicated drain
thread owns the socket and the Sender state (sends WRITE_DATA, periodic
HEARTBEAT, receives ACKNACK, retransmits from inFlight).
Repo: endpoints/nim/reliable.nim (state machine + frame codec);
endpoints/nim/reliable_app.nim::drain (drain thread over gChan.tryRecv);
endpoints/nim/reliable_test.nim (unit + byte-golden);
endpoints/nim/example_reliable.nim (in-process demo, no socket).
Tests: (Linux)
nim_reliable_loss_recovery(crates/endpoint-e2e/tests/nim_reliable.rs) — 12 samples, the peer drops every 3rd datagram; the app retransmits on ACKNACK; all 12 delivered gap-free in order.nim_reliable_no_loss— the same run without injected loss (baseline).nim_reliable_unit_and_golden— compiles and runsreliable_test.nim; 22 mirrored state-machine checks (sender: monotonic seq, payload-too-large, window-full, heartbeat first/silence/after-period/empty, ACKNACK clear partial/full; receiver: in-order, reorder block+delivery, duplicate-drop, buffer-full, ACKNACK bitmap, reset; plus 3 end-to-end loss-recovery checks)- 4 byte-golden checks (
byte_golden_heartbeat,byte_golden_acknackagainstgolden_heartbeat_le.bin/golden_acknack_le.bin, plus a parse round-trip of both goldens) —ALL OK.
- 4 byte-golden checks (
nim_reliable_producer_latency— the bench mode ofreliable_app.nim: producerenqueue->return via Channel vs. inlinesendtoon the same thread (measurement in §5).
Status: done.
Honestly noted: Nim’s stdlib Channel is lock-based (mutex + copy), not a
wait-free SPSC ring; the decoupling from the send syscall is real, a true ring
would push the enqueue figure lower still (comment in reliable_app.nim). The
reference spec (§4) does not mandate the concrete queue implementation.
§5 Latency
Spec: §4 — A measurement that shows the producer decoupling from the
socket syscall (reliable-endpoint §5 item 4, latency bench).
Repo: endpoints/nim/reliable_app.nim::runBench — 20000 iterations per
path; inline: sock.send directly on the producer thread; decoupled:
gChan.send into a Channel drained by a separate thread.
Tests: (Linux) nim_reliable_producer_latency — enqueue 192 ns vs.
inline sendto 3926 ns (~20x).
Status: done — enqueue 192 ns against inline sendto at 3926 ns, which is
the decoupling §4 asks for. The spec does not prescribe the queue
implementation; a wait-free ring would be an optimisation and is not built.
Audit-Status
5 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test-run (Linux, verified): cargo test -p zerodds-endpoint-e2e --test nim -> nim_endpoint_sync + nim_endpoint_async 2/2 green; --test nim_reliable -> nim_reliable_loss_recovery, nim_reliable_no_loss,
nim_reliable_unit_and_golden, nim_reliable_producer_latency 4/4 green;
latency measurement 192 ns (enqueue) vs. 3926 ns (inline sendto), ~20x. No
GitLab CI job runs zerodds-endpoint-e2e — these are manual Linux runs,
not wired into CI (the endpoints-nim job covers only the wire core/examples,
not reliable/E2E).
Open items: the zerodds-endpoint-e2e tests (ping-pong + reliable) are not
wired into GitLab CI — they run manually on the Linux test host only. A true wait-free
SPSC ring instead of the lock-based Channel remains a possible further
latency reduction (not an open spec item, see §4).
zerodds-endpoint-nim 1.0 — Spec-Coverage
Quelle: docs/specs/zerodds-endpoint-nim-1.0.md — ZeroDDS Nim Endpoint-SDK-Spec
(XRCE-Framing, sync Client, async AsyncReader, Reliable Stream).
Implementation:
endpoints/nim/zerodds.nim—Writer/Reader(XCDR2, Alignment cap 4),writeFrame/readFrame(XRCE best-effort Stream 0x01),Client(sync),AsyncReader(async).endpoints/nim/reliable.nim— Sender/Receiver-State-Machine + HEARTBEAT/ACKNACK- Frame-Codec (reliable Stream 0x80).endpoints/nim/reliable_app.nim— Channel-basierter Drain-Thread (Producer enqueued, Drain-Thread besitzt Socket + Sender-State).endpoints/nim/example_reliable.nim— In-Process-Demo der Loss-Recovery.crates/endpoint-e2e/tests/nim.rs,crates/endpoint-e2e/tests/nim_reliable.rs— Live-E2E gegen den Rust-XRCE-Peer.
§1 XRCE-Framing
Spec: §1 — XRCE WRITE_DATA-Rahmen: 8-Byte-Header (Session, Stream, Seq LE,
Submessage-Id, Flags, Länge LE) + Payload, byte-identisch zu crates/xrce und
den übrigen Endpoint-SDKs.
Repo: endpoints/nim/zerodds.nim::writeFrame/readFrame (best-effort,
Session 0x80, Stream 0x01); endpoints/nim/reliable.nim::reliableWriteFrame
(reliable, Stream 0x80) — identische Byte-Struktur, andere Stream-Id.
Tests: endpoints/nim/test.nim — die Marken "sync loopback: 5 samples OK",
"async loopback: 5 samples OK" und "negative frame vectors: OK"
(Byte-Identität des Wire-Cores, CI-Job endpoints-nim); der Frame-Roundtrip
live in §2/§3/§4.
Status: done.
§2 Sync-Client
Spec: §2 — Ein blockierungsfreier Poll-Client: write framet+sendet, poll
liest non-blocking und liefert Option[seq[byte]].
Repo: endpoints/nim/zerodds.nim::Client/newClient/write/poll
(monotone seqNo, wrap bei 0xffff).
Tests: nim_endpoint_sync (crates/endpoint-e2e/tests/nim.rs) — Live-Poll-
Loop gegen den Rust-Peer: eine App aus generierten idlc-Typen (gen.nim) + dem
Endpoint-SDK tauscht über einen echten UDP-Socket ein typisiertes Sample mit dem
gemeinsamen Rust-XRCE-Peer aus (crates/endpoint-e2e/tests/nim.rs::build_nim_endpoint
kompiliert gen.nim + eine Kopie von endpoints/nim/zerodds.nim + main.nim,
spawnt die App).
Status: done.
§3 Async-Reader
Spec: §3 — Der idiomatische Nim-async/await-Pfad: recv liefert ein
Future[seq[byte]], das erst mit dem entframten Sample resolved.
Repo: endpoints/nim/zerodds.nim::AsyncReader/newAsyncReader/recv
({.async.}-Proc, pollt via sleepAsync(1) bis ein Frame ansteht).
Tests: nim_endpoint_async (crates/endpoint-e2e/tests/nim.rs) — waitFor fut.withTimeout(10000) gegen dieselbe generierte App; Client.write sendet in
beiden Modi (das SDK hat nur einen sync Writer), der async-Pfad prüft allein das
AsyncReader-Future.
Tests: (Linux) nim_endpoint_sync, nim_endpoint_async — beide grün.
Status: done.
§4 Reliable Stream
Spec: §4 — XRCE Reliable Stream (stream_id ≥ 128, §8.4.10/§8.4.11),
gespiegelt aus der Referenz crates/xrce/src/reliable.rs: Sender submit/
pendingHeartbeat/recvAcknack/getInFlight; Receiver recvData/
drainInOrder/pendingAcknack/reset. Fenster 16, Receiver-Buffer 64,
Heartbeat 500 ms, Payload ≤ 65535, RFC-1982-16-Bit-Sequenznummern. Der
Async-Writer ist als Channel-Drain-Thread gebaut: der Producer enqueued
lock-basiert (Nim-stdlib Channel, kein wait-free Ring), ein dedizierter
Drain-Thread besitzt den Socket und den Sender-State (WRITE_DATA senden,
periodisches HEARTBEAT, ACKNACK-Empfang, Retransmit aus inFlight).
Repo: endpoints/nim/reliable.nim (State-Machine + Frame-Codec);
endpoints/nim/reliable_app.nim::drain (Drain-Thread über gChan.tryRecv);
endpoints/nim/reliable_test.nim (Unit + Byte-Golden); endpoints/nim/example_reliable.nim
(In-Process-Demo, kein Socket).
Tests: (Linux)
nim_reliable_loss_recovery(crates/endpoint-e2e/tests/nim_reliable.rs) — 12 Samples, Peer droppt jedes 3. Datagramm; die App retransmittet auf ACKNACK; alle 12 lückenlos in Reihenfolge zugestellt.nim_reliable_no_loss— identischer Lauf ohne injizierten Loss (Baseline).nim_reliable_unit_and_golden— kompiliert und läuftreliable_test.nim; 22 gespiegelte State-Machine-Checks (Sender: monotone Seq, Payload-zu-groß, Window-voll, Heartbeat first/silence/after-period/leer, ACKNACK-Clear partial/full; Receiver: in-order, Reorder-Block+Delivery, Duplikat-Drop, Buffer-voll, ACKNACK-Bitmap, Reset; plus 3 End-to-End-Loss-Recovery-Checks)- 4 Byte-Golden-Checks (
byte_golden_heartbeat,byte_golden_acknackgegengolden_heartbeat_le.bin/golden_acknack_le.bin, plus Parse-Roundtrip beider Goldens) —ALL OK.
- 4 Byte-Golden-Checks (
nim_reliable_producer_latency— Bench-Modus vonreliable_app.nim: Producerenqueue→Return via Channel vs. inlinesendtoim selben Thread (Messwert siehe §5).
Status: done.
Ehrlich vermerkt: Nims stdlib-Channel ist lock-basiert (Mutex+Copy), kein
wait-free SPSC-Ring; die Entkopplung vom Send-Syscall ist real, ein echter Ring
würde den Enqueue-Wert weiter senken (Kommentar in reliable_app.nim). Die
Referenz-Spec (§4) schreibt die konkrete Queue-Implementierung nicht vor.
§5 Latenz
Spec: §4 — Ein Messwert, der die Producer-Entkopplung vom Socket-Syscall
zeigt (reliable-endpoint §5 Punkt 4, Latenz-Bench).
Repo: endpoints/nim/reliable_app.nim::runBench — 20000 Iterationen je
Pfad; inline: sock.send direkt im Producer-Thread; decoupled: gChan.send
in einen von einem Drain-Thread geleerten Channel.
Tests: (Linux) nim_reliable_producer_latency — Enqueue 192 ns vs.
inline sendto 3926 ns (~20×).
Status: done — Enqueue 192 ns gegen inline sendto 3926 ns, also die
Entkopplung, die §4 verlangt. Die Spec schreibt die Queue-Implementierung
nicht vor; ein wait-free Ring wäre eine Optimierung und ist nicht gebaut.
Audit-Status
5 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test-Lauf: Linux, verifiziert — cargo test -p zerodds-endpoint-e2e --test nim → nim_endpoint_sync + nim_endpoint_async 2/2 grün; --test nim_reliable → nim_reliable_loss_recovery, nim_reliable_no_loss,
nim_reliable_unit_and_golden, nim_reliable_producer_latency 4/4 grün;
Latenz-Messwert 192 ns (enqueue) vs. 3926 ns (inline sendto), ~20×. Kein
GitLab-CI-Job führt zerodds-endpoint-e2e aus — die Nachweise sind
manuelle Linux-Läufe, nicht in CI verdrahtet (endpoints-nim-Job deckt nur
Wire-Core/Beispiele ab, kein reliable/E2E).
Offene Punkte: zerodds-endpoint-e2e-Tests (Ping-Pong + reliable) sind nicht
in die GitLab-CI verdrahtet — laufen nur manuell auf dem Linux-Testhost. Ein echter
wait-free SPSC-Ring statt des lock-basierten Channel bleibt eine mögliche
weitere Latenzsenkung (kein offener Spec-Punkt, siehe §4).