zerodds-endpoint-c 1.0 — Spec Coverage
Source: docs/specs/zerodds-endpoint-c-1.0.md — the native C endpoint SDK
(XRCE framing, sync/async, reliable stream). The reliable-stream contract
details live in docs/spec-coverage/reliable-endpoint-1.0.en.md.
Covers the C ENDPOINT stack (separate from the C codegen spec
matrix/zerodds-c/): XRCE framing, sync send/recv, async reader/writer
(including the live ping-pong E2E obligation) and the reliable stream.
Implementation:
endpoints/c/include/zerodds_endpoint.h/endpoints/c/src/zerodds_endpoint.c— frame hook, XRCE framing, serial framing after Annex C, reliable state machine (C89).endpoints/c/include/zerodds_async.h/endpoints/c/src/zerodds_async.c— async reactor (C11).endpoints/c/include/zerodds_reliable_async.h/endpoints/c/src/zerodds_reliable_async.c— SPSC ring plus a pthread drain thread for the decoupled reliable writer (C11).crates/endpoint-e2e/tests/c.rsandcrates/endpoint-e2e/tests/c_reliable.rs— live UDP E2E against the Rust reference peer.
How the evidence runs
Two separate runs, both executed in this audit:
- C side. The files under
endpoints/c/test/andendpoints/c/examples/are C programs. They are not part ofcargo test; a pure Rust run executes none of them. They are built and run throughendpoints/c/Makefile:make -C endpoints/c test GOLDEN_DIR=<dir>andmake -C endpoints/c examples, where<dir>holds the byte goldens fromcargo run -p zerodds-endpoint-golden -- <dir>. In the pipeline the jobendpoints-gate(ci/full.yml, columncoftools/endpoints-gate) runs exactly those two commands. - Rust side.
crates/endpoint-e2e/tests/c.rsandcrates/endpoint-e2e/tests/c_reliable.rsrun in the defaultcargo test -p zerodds-endpoint-e2e— no feature gate. Every case first checks whether a C compiler is on thePATH, and otherwise returns with aSKIPline on stderr; the case then counts as passed without having checked anything. The pipeline jobendpoint-e2ein.gitlab-ci.ymlgreps for those lines and fails the job when a language other than Swift skips. On this hostccandgccwere present; none of the six cases skipped.
Every item below names which of the two runs carries its evidence.
Open items: none. Decision records: none.
§1 XRCE framing
§1.1 WRITE_DATA frame (session/stream/sequence + sample body)
Spec: §1.1 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0
§8.3.2.3/§8.3.4) — message header (session_id, stream_id, sequence_nr
little-endian) plus the WRITE_DATA submessage header (id = 7, flags, length
little-endian) plus the XCDR sample body. Best-effort, no ClientKey
(session_id >= 128).
Repo: endpoints/c/include/zerodds_endpoint.h /
endpoints/c/src/zerodds_endpoint.c (functions zdw_xrce_write_frame and
zdw_xrce_read_frame, constants ZDW_XRCE_SESSION_NOKEY and
ZDW_XRCE_STREAM_BEST_EFFORT).
Tests: C side — endpoints/c/test/test_xrce_frame.c, run through the
Makefile (make -C endpoints/c test); builds the frame and compares it byte
for byte against golden_xrce_le.bin, then a round trip through the frame
hook (encode, frame, transport, receive, unwrap, decode). Output measured in
this audit: XRCE WRITE_DATA frame 48 bytes byte-identical to crates/xrce.
Rust side — crates/endpoint-e2e/tests/c.rs::c_endpoint_sync (the Rust peer
unframes the WRITE_DATA the C program sent live over UDP and checks the 23
octets of the Ping). The reference the golden is generated from:
crates/xrce/src/submessages/write_data.rs::tests::write_data_flags_are_e_le_and_format_data,
crates/xrce/src/submessages/write_data.rs::tests::write_data_base_object_request_precedes_the_sample,
crates/xrce/src/submessages/write_data.rs::tests::write_data_matches_the_annex_b_example.
Status: done
§1.2 Receive path (DATA message from the agent)
Spec: §1.2 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0 §8.3.4)
— the DATA submessage (id = 9) is the agent → client direction. The endpoint
MUST accept it; WRITE_DATA in that direction would be wrong.
Repo: endpoints/c/src/zerodds_endpoint.c (zdw_xrce_read_frame, the
shared unwrap function for WRITE_DATA and DATA).
Tests: C side — endpoints/c/test/test_receive.c against
golden_data_le.bin (Makefile run make -C endpoints/c test). Output
measured in this audit: received DATA frame, 40-byte sample body / agent DATA
decoded. Rust side — crates/endpoint-e2e/tests/c.rs::c_endpoint_sync and
crates/endpoint-e2e/tests/c.rs::c_endpoint_async: the peer answers with
id = 9 only, so the C program has to read the pong out of it. The reference
for the golden: crates/xrce/src/submessages/data.rs::tests::data_uses_id_9,
crates/xrce/src/submessages/data.rs::tests::data_base_object_request_precedes_the_sample.
Status: done
§1.3 Serial framing (Annex C)
Spec: §1.3 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0 Annex C.1)
— 7E | sourceAddress | remoteAddress | payloadLength (LE) | payload | frameCheck (LE); stuffing of 0x7E and 0x7D after the flag; frameCheck
the FCS-16 of RFC 1662 over addresses, length and payload; refusal of a wrong
frameCheck and of 0x7E before the end of the frame (C.1.3).
Repo: endpoints/c/src/zerodds_endpoint.c (zdw_fcs16,
zdw_serial_frame, zdw_serial_deframe).
Tests: C side — endpoints/c/test/test_serial_frame.c against
golden_serial_le.bin (tools/endpoints-gate, run on the Linux test host at
8bcb1d00161). Measured output: byte-stuffing ok / fcs16 0x906E, check and
flag refusals ok / XRCE serial frame 59 bytes byte-identical to crates/xrce /
serial round-trip ok. There is no live E2E case for this clause: the Rust peer
speaks UDP, not a serial link. The reference the golden stands against byte
for byte:
crates/xrce/src/transport_serial.rs::tests::the_frame_check_is_the_fcs16_of_rfc_1662,
crates/xrce/src/transport_serial.rs::tests::a_frame_carries_the_addresses_the_length_and_a_little_endian_check,
crates/xrce/src/transport_serial.rs::tests::flag_and_escape_octets_are_stuffed_everywhere_after_the_flag,
crates/xrce/src/transport_serial.rs::tests::a_frame_with_a_wrong_check_is_refused,
crates/xrce/src/transport_serial.rs::tests::a_begin_flag_inside_a_frame_drops_it_and_begins_the_next;
endpoints/golden-gen/src/main.rs produces golden_serial_le.bin with
encode_frame(1, 0, …) from that implementation.
Status: done
§2 sync send/recv
§2.1 Frame-hook contract (zdw_transport)
Spec: §2.1 (docs/specs/zerodds-endpoint-c-1.0.md, ADR 0013 invariant 5)
— the endpoint is transport-opaque: it hands a fully framed, encoded message
to the transport the integrator fills in (ctx plus function pointers for
deliver and receive) and gets complete frames back. The endpoint opens no
socket of its own.
Repo: endpoints/c/include/zerodds_endpoint.h (the zdw_transport
struct, zdw_endpoint_send, zdw_endpoint_recv).
Tests: C side — endpoints/c/test/test_endpoint_loopback.c with an
in-memory loopback transport (encode, send, receive, decode, field compare;
Makefile run make -C endpoints/c test). Output measured in this audit:
frame-hook 40-byte frame delivered + received + decoded ok. Rust side —
crates/endpoint-e2e/tests/c.rs::c_endpoint_sync plugs a real POSIX UDP
socket into that same frame hook and thereby shows the contract holds beyond
the test stub.
Status: done
§2.2 Poll-loop idiom (C89)
Spec: §2.2 (docs/specs/zerodds-endpoint-c-1.0.md) — sync is the C89
baseline use of the frame hook: the integrator owns the run loop and calls
zdw_endpoint_recv itself. There MUST be a runnable example, not a stub.
Repo: endpoints/c/examples/example_sync.c (a C89 poll loop over the
transport, compiled with -std=c89 -pedantic -Wall -Wextra).
Tests: C side — the example run (make -C endpoints/c examples, target
example_sync). Output measured in this audit: five decoded Reading
samples (id 0x1000 through 0x1004) and ALL OK, 0 compiler warnings. Rust side
— crates/endpoint-e2e/tests/c.rs::c_endpoint_sync runs the same idiom in a
purpose-built program live against the Rust peer.
Status: done
§3 async reader/writer
§3.1 Event-driven reactor (zdw_async_reader / zdw_async_writer)
Spec: §3.1 (docs/specs/zerodds-endpoint-c-1.0.md, ADR 0013) — additive
to the C89 core, a latency-decoupled, callback-driven alternative to the poll
loop (C11, no malloc).
Repo: endpoints/c/include/zerodds_async.h /
endpoints/c/src/zerodds_async.c (zdw_async_reader_init binds an
on_sample callback, zdw_async_run drains the transport and dispatches
every decoded sample; plus zdw_async_writer_init and zdw_async_write).
Tests: C side — endpoints/c/test/test_async_loopback.c over an
in-memory FIFO transport (Makefile run make -C endpoints/c test). Output
measured in this audit: async loopback 5 samples dispatched + decoded in
order. Rust side — crates/endpoint-e2e/tests/c.rs::c_endpoint_async
(zdw_async_write frames and delivers, zdw_async_poll unwraps and calls
the callback, live over UDP).
Status: done
§3.2 Live UDP reactor
Spec: §3.2 (docs/specs/zerodds-endpoint-c-1.0.md) — evidence that the
reactor drives real datagram I/O, not only an in-memory queue.
Repo: the same reactor functions from endpoints/c/src/zerodds_async.c,
wired to a non-blocking POSIX UDP socket in
endpoints/c/test/test_async_udp.c.
Tests: C side — endpoints/c/test/test_async_udp.c (Makefile run
make -C endpoints/c test). Output measured in this audit: async UDP 5/5
samples received + decoded via reactor. Rust side —
crates/endpoint-e2e/tests/c.rs::c_endpoint_async (the same reactor against
a foreign process rather than a second socket in the same program),
crates/endpoint-e2e/tests/c.rs::c_raw_udp (the counter-check without the
XRCE envelope).
Status: done
§3.3 Async deep example
Spec: §3.3 (docs/specs/zerodds-endpoint-c-1.0.md) — a runnable example
for the reactor, not a stub.
Repo: endpoints/c/examples/example_async.c (a C11 reactor plus
callback, compiled with -std=c11 -pedantic -Wall -Wextra).
Tests: C side — the example run (make -C endpoints/c examples, target
example_async). Output measured in this audit: five decoded readings (id
0x2000 through 0x2004) and ALL OK, 0 compiler warnings. Rust side —
crates/endpoint-e2e/tests/c.rs::c_endpoint_async shows the same callback
path against a real peer.
Status: done
§3.4 Test obligation: live ping-pong E2E
Spec: §3.4 (docs/specs/zerodds-endpoint-c-1.0.md) — in addition to the
isolated unit and loopback tests of §1 through §3, every language MUST
provide a live E2E file following the crates/endpoint-e2e/tests/<lang>.rs
pattern (reference: crates/endpoint-e2e/tests/cpp.rs), so that framing
(§1), sync (§2) and async (§3) are shown working together and not only
individually.
Repo: crates/endpoint-e2e/tests/c.rs — three cases over the C endpoint
surface: the generated codec straight on a POSIX UDP socket without the XRCE
envelope; the same probe inside an XRCE WRITE_DATA via zdw_endpoint_send
and zdw_endpoint_recv; the event-driven surface via zdw_async_write and
zdw_async_poll. The wire runtime runtime/c is built into a static library
through its own Makefile because its CFLAGS are a precondition; the include
path puts runtime/c/include before endpoints/c/include, because
runtime/c/include/zerodds_wire.h and endpoints/c/include/zerodds_wire.h
carry the same include guard.
Tests: Rust side, default run — crates/endpoint-e2e/tests/c.rs::c_raw_udp,
crates/endpoint-e2e/tests/c.rs::c_endpoint_sync,
crates/endpoint-e2e/tests/c.rs::c_endpoint_async. Measured (command
cargo test -p zerodds-endpoint-e2e --test c): 3 passed, 0 failed, no SKIP
line. The three cases sit behind no feature gate but return with a SKIP
line when no C compiler is on the PATH, and then count as passed without
having checked anything; the pipeline job endpoint-e2e fails on such a
line. The Rust peer checks the exact 23 octets of the Ping and the program
checks the answer line carrying reply=pong:hello from app.
Status: done
§4 reliable stream
§4.1 C89 state machine (zdw_reliable)
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §3 — a reliable sender and
receiver, mirrored from crates/xrce/src/reliable.rs; fixed storage, no
malloc: window 16, receive buffer 64, RFC 1982 16-bit sequence numbers.
Repo: endpoints/c/include/zerodds_endpoint.h (declaration) and
endpoints/c/src/zerodds_endpoint.c (C89 implementation) — sender side
zdw_reliable_submit, zdw_reliable_pending_heartbeat,
zdw_reliable_recv_acknack, zdw_reliable_get_in_flight; receiver side
zdw_reliable_recv_data, zdw_reliable_drain,
zdw_reliable_pending_acknack; plus zdw_reliable_reset.
Tests: C side — endpoints/c/test/test_reliable_sm.c (Makefile run
make -C endpoints/c test): 14 check functions, 13 of them mirroring the
reference tests and one a byte-golden check. Output measured in this audit:
test_reliable_sm ALL OK. The mirrored reference is
crates/xrce/src/reliable.rs::tests::submit_assigns_monotonic_seqnrs,
crates/xrce/src/reliable.rs::tests::submit_rejects_when_window_full,
crates/xrce/src/reliable.rs::tests::pending_heartbeat_fires_first_time,
crates/xrce/src/reliable.rs::tests::pending_heartbeat_silenced_until_period_elapsed,
crates/xrce/src/reliable.rs::tests::recv_acknack_clears_acked_seqnrs,
crates/xrce/src/reliable.rs::tests::recv_acknack_full_clear_when_no_bits_set,
crates/xrce/src/reliable.rs::tests::recv_data_buffers_in_order,
crates/xrce/src/reliable.rs::tests::recv_data_reorders_out_of_order,
crates/xrce/src/reliable.rs::tests::recv_data_drops_duplicates,
crates/xrce/src/reliable.rs::tests::recv_data_rejects_when_buffer_full,
crates/xrce/src/reliable.rs::tests::pending_acknack_marks_missing_slots,
crates/xrce/src/reliable.rs::tests::reset_clears_state_completely,
crates/xrce/src/reliable.rs::tests::end_to_end_sender_receiver_with_loss_recovery.
Status: done
§4.2 Byte-golden HEARTBEAT and ACKNACK
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §4 — ACKNACK carries
first_unacked_seq_num as an i16, nack_bitmap as two little-endian bytes
and stream_id as one byte; HEARTBEAT carries first_unacked_seq_nr and
last_unacked_seq_nr as i16 plus stream_id.
Repo: endpoints/c/src/zerodds_endpoint.c (the codec halves of
zdw_reliable_pending_heartbeat and zdw_reliable_pending_acknack), checked
by the byte-golden function in endpoints/c/test/test_reliable_sm.c and by
the pure wire test endpoints/c/test/test_reliable.c.
Tests: C side — endpoints/c/test/test_reliable.c and
endpoints/c/test/test_reliable_sm.c against golden_heartbeat_le.bin and
golden_acknack_le.bin (Makefile run make -C endpoints/c test). Output
measured in this audit: HEARTBEAT parsed first=1 last=3 stream=0x80 / ACKNACK
13 bytes byte-identical to crates/xrce. The reference both goldens come from:
crates/xrce/src/submessages/heartbeat.rs::tests::heartbeat_le_roundtrip,
crates/xrce/src/submessages/heartbeat.rs::tests::heartbeat_roundtrip_via_submessage,
crates/xrce/src/submessages/acknack.rs::tests::acknack_le_roundtrip,
crates/xrce/src/submessages/acknack.rs::tests::acknack_roundtrip_via_submessage.
Status: done
§4.3 pthread SPSC ring as the async writer (latency decoupling)
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §2 — the producer must never
enter the kernel; the component carries the reliable sender state at the same
time.
Repo: endpoints/c/include/zerodds_reliable_async.h /
endpoints/c/src/zerodds_reliable_async.c (C11 plus pthread):
zdw_async_ring_start starts the drain thread, zdw_async_ring_enqueue is a
wait-free enqueue without a syscall, and the drain thread holds the
zdw_reliable state and does the framing and the I/O.
Tests: C side — endpoints/c/test/test_reliable_async_overflow.c
(Makefile run make -C endpoints/c test): checks the behaviour at a full
send window and that the drain thread does not acknowledge itself. Output
measured in this audit: ALL OK, delivered 24/24, no self-ack, no hang. Plus
endpoints/c/test/bench_reliable_async.c as the latency comparison (see
§4.6). Rust side —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_latency_bench builds and
runs the same ring out of the default run.
Status: done
§4.4 Loss-recovery example
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §5 point 5 — a runnable
example, not a stub: N samples, injected loss, gap-free delivery after
retransmit.
Repo: endpoints/c/examples/example_reliable.c — 12 samples submitted,
every fourth dropped on the first pass, recovery rounds until all 12 have
arrived in the sequence 0 through 11.
Tests: C side — the example run (make -C endpoints/c examples, target
example_reliable; also in the Makefile target test). Output measured in
this audit: 3/12 delivered before recovery, delivered contiguous 0..11
expected=12, ALL 12 samples recovered gap-free. Rust side —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_loss_recovery shows the
same sequence live over UDP against a foreign process instead of in-process.
Status: done
§4.5 Live E2E against the Rust reference peer
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §5 point 3 — live against
the Rust reliable peer, stream_id >= 128, with an injected drop; assert:
every sample delivered gap-free and in order despite the loss.
Repo: endpoints/c/test/reliable_udp_app.c — the C app plays the
reliable sender (submit, WRITE_DATA, HEARTBEAT, ACKNACK-driven retransmit)
against the shared Rust peer in crates/endpoint-e2e/src/lib.rs, which
injects the loss. The file declares _POSIX_C_SOURCE 200809L and
additionally _DARWIN_C_SOURCE 1, because INADDR_LOOPBACK is a BSD socket
extension under Apple’s libc and would otherwise be invisible; glibc does not
have that restriction.
Tests: Rust side, default run —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_loss_recovery
(12 samples, injected drop, assert on gap-freeness) and
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_no_loss (12 samples
without a drop). Measured (command cargo test -p zerodds-endpoint-e2e --test c_reliable): 3 passed, 0 failed, no SKIP line. The compiler pre-check from
the head of this document applies here too — without a compiler on the search
path the cases skip loudly and the pipeline job fails on it.
Status: done
§4.6 Latency: inline send versus decoupled ring enqueue
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) together with
docs/spec-coverage/reliable-endpoint-1.0.en.md §5 point 4 — the producer
latency from write to return in the decoupled path MUST be measured
against the inline deliver; one figure that shows the decoupling.
Repo: endpoints/c/test/bench_reliable_async.c — measures
inline_ns_per_op (framing plus the send() syscall per sample against a
bound but unread loopback socket) against decoupled_enqueue_ns_per_op
(a bare enqueue into the ring, no kernel). The file carried the same
INADDR_LOOPBACK cause as §4.5 and therefore declares _DARWIN_C_SOURCE 1
as well.
Tests: Rust side, default run —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_latency_bench (builds
the bench program with gcc -O2 -std=c11, runs it and fails the case when
the latency line is missing or the decoupled enqueue is not below the inline
send). C side — the same program in the Makefile target (test). Measured in
this audit on macOS 15 (Apple Clang): inline_ns_per_op=2585 against
decoupled_enqueue_ns_per_op=13 over 5000 delivered samples. The absolute
inline figure varies with host load; the assert checks the ordering, not the
magnitude.
Status: done
Audit-Status
15 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test run, C side: cargo run -p zerodds-endpoint-golden -- <dir> followed by
make -C endpoints/c test GOLDEN_DIR=<dir> and
make -C endpoints/c examples — all 20 binaries build with
-std=c89 -pedantic -Wall -Wextra or -std=c11 -pedantic -Wall -Wextra
without a warning and run through, 0 failed.
Test run, Rust side: cargo test -p zerodds-endpoint-e2e --test c — 3
passed, 0 failed; cargo test -p zerodds-endpoint-e2e --test c_reliable — 3
passed, 0 failed. None of the six cases skipped.
Both runs measured on macOS 15, where cc and gcc are Apple Clang.
Open items: none.
zerodds-endpoint-c 1.0 — Spec-Coverage
Quelle: docs/specs/zerodds-endpoint-c-1.0.md — natives C-Endpoint-SDK
(XRCE-Framing, Sync/Async, Reliable Stream). Die Reliable-Stream-Kontraktdetails
stehen in docs/spec-coverage/reliable-endpoint-1.0.md.
Deckt den C-ENDPOINT-Stack ab (getrennt von der C-Codegen-Spec
matrix/zerodds-c/): XRCE-Framing, sync send/recv, async Reader/Writer
(inklusive Live-Ping-Pong-E2E-Pflicht) und den reliable Stream.
Implementation:
endpoints/c/include/zerodds_endpoint.h/endpoints/c/src/zerodds_endpoint.c— Frame-Hook, XRCE-Framing, serielles Framing nach Annex C, reliable State-Machine (C89).endpoints/c/include/zerodds_async.h/endpoints/c/src/zerodds_async.c— Async-Reactor (C11).endpoints/c/include/zerodds_reliable_async.h/endpoints/c/src/zerodds_reliable_async.c— SPSC-Ring plus pthread-Drain-Thread für den entkoppelten reliable Writer (C11).crates/endpoint-e2e/tests/c.rsundcrates/endpoint-e2e/tests/c_reliable.rs— Live-UDP-E2E gegen den Rust-Referenz-Peer.
Wie die Belege laufen
Zwei getrennte Läufe, beide in diesem Audit gefahren:
- C-Seite. Die Dateien unter
endpoints/c/test/undendpoints/c/examples/sind C-Programme. Sie sind nicht Teil voncargo test; ein reiner Rust-Lauf führt null von ihnen aus. Gebaut und gefahren werden sie überendpoints/c/Makefile:make -C endpoints/c test GOLDEN_DIR=<dir>undmake -C endpoints/c examples, wobei<dir>die Byte-Goldens auscargo run -p zerodds-endpoint-golden -- <dir>enthält. In der Pipeline fährt der Jobendpoints-gate(ci/full.yml, Spaltecvontools/endpoints-gate) genau diese beiden Kommandos. - Rust-Seite.
crates/endpoint-e2e/tests/c.rsundcrates/endpoint-e2e/tests/c_reliable.rslaufen im Default-Lauf voncargo test -p zerodds-endpoint-e2e— kein Feature-Gate. Jeder Fall prüft aber zuerst, ob ein C-Compiler auf demPATHliegt, und kehrt sonst mit einerSKIP-Zeile auf stderr zurück; der Fall zählt dann als bestanden, ohne etwas geprüft zu haben. Der Pipeline-Jobendpoint-e2ein.gitlab-ci.ymlgreppt nach diesen Zeilen und lässt den Job scheitern, wenn eine andere Sprache als Swift skippt. Auf diesem Host lagccbzw.gccvor; keiner der sechs Fälle hat geskippt.
Jedes Item nennt unten, welcher der beiden Läufe seinen Beleg trägt.
Offene Punkte: keine. Decision-Records: keine.
§1 XRCE-Framing
§1.1 WRITE_DATA-Frame (Session/Stream/Sequence + Sample-Body)
Spec: §1.1 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0
§8.3.2.3/§8.3.4) — Message-Header (session_id, stream_id, sequence_nr
little-endian) plus WRITE_DATA-Submessage-Header (id = 7, Flags, Länge
little-endian) plus XCDR-Sample-Body. Best-effort, kein ClientKey
(session_id >= 128).
Repo: endpoints/c/include/zerodds_endpoint.h /
endpoints/c/src/zerodds_endpoint.c (Funktionen zdw_xrce_write_frame und
zdw_xrce_read_frame, Konstanten ZDW_XRCE_SESSION_NOKEY und
ZDW_XRCE_STREAM_BEST_EFFORT).
Tests: C-Seite — endpoints/c/test/test_xrce_frame.c, gefahren über den
Makefile-Lauf (make -C endpoints/c test); baut den Frame und vergleicht ihn byte-genau
gegen golden_xrce_le.bin, dann Round-Trip durch den Frame-Hook (encode,
Frame, Transport, receive, unwrap, decode). Gemessene Ausgabe dieses Audits:
XRCE WRITE_DATA frame 48 bytes byte-identical to crates/xrce.
Rust-Seite — crates/endpoint-e2e/tests/c.rs::c_endpoint_sync (der
Rust-Peer entrahmt den vom C-Programm gesendeten WRITE_DATA-Frame live über
UDP und prüft die 23 Oktetts des Ping). Die Referenz, aus der das Golden
entsteht: crates/xrce/src/submessages/write_data.rs::tests::write_data_flags_are_e_le_and_format_data,
crates/xrce/src/submessages/write_data.rs::tests::write_data_base_object_request_precedes_the_sample,
crates/xrce/src/submessages/write_data.rs::tests::write_data_matches_the_annex_b_example.
Status: done
§1.2 Empfangspfad (DATA-Message vom Agenten)
Spec: §1.2 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0 §8.3.4)
— die DATA-Submessage (id = 9) ist die Richtung Agent → Client. Der Endpoint
MUSS sie annehmen; WRITE_DATA in dieser Richtung wäre falsch.
Repo: endpoints/c/src/zerodds_endpoint.c (zdw_xrce_read_frame, die
gemeinsame Unwrap-Funktion für WRITE_DATA und DATA).
Tests: C-Seite — endpoints/c/test/test_receive.c gegen
golden_data_le.bin (Makefile-Lauf make -C endpoints/c test). Gemessene
Ausgabe dieses Audits: received DATA frame, 40-byte sample body / agent DATA
decoded. Rust-Seite — crates/endpoint-e2e/tests/c.rs::c_endpoint_sync und
crates/endpoint-e2e/tests/c.rs::c_endpoint_async: der Peer antwortet
ausschließlich mit id = 9, das C-Programm muss den Pong daraus lesen. Die
Referenz für das Golden: crates/xrce/src/submessages/data.rs::tests::data_uses_id_9,
crates/xrce/src/submessages/data.rs::tests::data_base_object_request_precedes_the_sample.
Status: done
§1.3 Serielles Framing (Annex C)
Spec: §1.3 (docs/specs/zerodds-endpoint-c-1.0.md, DDS-XRCE 1.0 Annex C.1)
— 7E | sourceAddress | remoteAddress | payloadLength (LE) | payload | frameCheck (LE); Stuffing von 0x7E und 0x7D nach dem Flag; frameCheck
die FCS-16 nach RFC 1662 über Adressen, Länge und Payload; Abweisung bei
falschem frameCheck und bei 0x7E vor dem Rahmenende (C.1.3).
Repo: endpoints/c/src/zerodds_endpoint.c (zdw_fcs16,
zdw_serial_frame, zdw_serial_deframe).
Tests: C-Seite — endpoints/c/test/test_serial_frame.c gegen
golden_serial_le.bin (tools/endpoints-gate, Lauf auf dem Linux-Testhost am
Stand 8bcb1d00161). Gemessene Ausgabe: byte-stuffing ok / fcs16 0x906E,
check and flag refusals ok / XRCE serial frame 59 bytes byte-identical to
crates/xrce / serial round-trip ok. Für diese Klausel gibt es keinen Live-E2E-Fall:
der Rust-Peer spricht UDP, keinen seriellen Link. Die Referenz, gegen die das
Golden byte-genau steht:
crates/xrce/src/transport_serial.rs::tests::the_frame_check_is_the_fcs16_of_rfc_1662,
crates/xrce/src/transport_serial.rs::tests::a_frame_carries_the_addresses_the_length_and_a_little_endian_check,
crates/xrce/src/transport_serial.rs::tests::flag_and_escape_octets_are_stuffed_everywhere_after_the_flag,
crates/xrce/src/transport_serial.rs::tests::a_frame_with_a_wrong_check_is_refused,
crates/xrce/src/transport_serial.rs::tests::a_begin_flag_inside_a_frame_drops_it_and_begins_the_next;
endpoints/golden-gen/src/main.rs erzeugt golden_serial_le.bin mit
encode_frame(1, 0, …) aus dieser Implementierung.
Status: done
§2 sync send/recv
§2.1 Frame-Hook-Vertrag (zdw_transport)
Spec: §2.1 (docs/specs/zerodds-endpoint-c-1.0.md, ADR 0013 Invariante 5)
— der Endpoint ist transport-opak: er reicht ein vollständig gerahmtes,
encodiertes Message an den vom Integrator gefüllten Transport (ctx plus
Funktionspointer für Deliver und Receive) und erhält vollständige Frames
zurück. Der Endpoint öffnet selbst keinen Socket.
Repo: endpoints/c/include/zerodds_endpoint.h (Struktur zdw_transport,
zdw_endpoint_send, zdw_endpoint_recv).
Tests: C-Seite — endpoints/c/test/test_endpoint_loopback.c mit einem
In-Memory-Loopback-Transport (Encode, Send, Receive, Decode, Feldvergleich;
Makefile-Lauf make -C endpoints/c test). Gemessene Ausgabe dieses Audits:
frame-hook 40-byte frame delivered + received + decoded ok. Rust-Seite —
crates/endpoint-e2e/tests/c.rs::c_endpoint_sync steckt in denselben
Frame-Hook einen echten POSIX-UDP-Socket und belegt damit, dass der Vertrag
nicht nur gegen den Test-Stub trägt.
Status: done
§2.2 Poll-Loop-Idiom (C89)
Spec: §2.2 (docs/specs/zerodds-endpoint-c-1.0.md) — sync ist die
C89-Baseline-Nutzung des Frame-Hooks: der Integrator besitzt den Run-Loop und
ruft zdw_endpoint_recv selbst. Es MUSS ein lauffähiges Beispiel geben, kein
Stub.
Repo: endpoints/c/examples/example_sync.c (C89-Poll-Loop über den
Transport, übersetzt mit -std=c89 -pedantic -Wall -Wextra).
Tests: C-Seite — der Beispiel-Lauf (make -C endpoints/c examples, Ziel example_sync).
Gemessene Ausgabe dieses Audits: fünf dekodierte Reading-Samples
(id 0x1000 bis 0x1004) und ALL OK, 0 Compiler-Warnungen. Rust-Seite —
crates/endpoint-e2e/tests/c.rs::c_endpoint_sync fährt dasselbe Idiom in
einem eigens gebauten Programm live gegen den Rust-Peer.
Status: done
§3 async Reader/Writer
§3.1 Event-driven Reactor (zdw_async_reader / zdw_async_writer)
Spec: §3.1 (docs/specs/zerodds-endpoint-c-1.0.md, ADR 0013) — additiv
zum C89-Kern eine latenz-entkoppelte, callback-getriebene Alternative zum
Poll-Loop (C11, kein malloc).
Repo: endpoints/c/include/zerodds_async.h /
endpoints/c/src/zerodds_async.c (zdw_async_reader_init bindet einen
on_sample-Callback, zdw_async_run drained den Transport und dispatcht
jedes dekodierte Sample; dazu zdw_async_writer_init und zdw_async_write).
Tests: C-Seite — endpoints/c/test/test_async_loopback.c über einen
In-Memory-FIFO-Transport (Makefile-Lauf make -C endpoints/c test). Gemessene
Ausgabe dieses Audits: async loopback 5 samples dispatched + decoded in
order. Rust-Seite — crates/endpoint-e2e/tests/c.rs::c_endpoint_async
(zdw_async_write rahmt und liefert, zdw_async_poll entpackt und ruft den
Callback, live über UDP).
Status: done
§3.2 Live-UDP-Reactor
Spec: §3.2 (docs/specs/zerodds-endpoint-c-1.0.md) — Nachweis, dass der
Reactor echte Datagramm-I/O treibt und nicht nur eine In-Memory-Queue.
Repo: dieselben Reactor-Funktionen aus endpoints/c/src/zerodds_async.c,
angebunden an einen nicht-blockierenden POSIX-UDP-Socket in
endpoints/c/test/test_async_udp.c.
Tests: C-Seite — endpoints/c/test/test_async_udp.c (Makefile-Lauf make -C endpoints/c test). Gemessene Ausgabe dieses Audits: async UDP 5/5
samples received + decoded via reactor. Rust-Seite —
crates/endpoint-e2e/tests/c.rs::c_endpoint_async (derselbe Reactor gegen
einen fremden Prozess statt gegen einen zweiten Socket im selben Programm),
crates/endpoint-e2e/tests/c.rs::c_raw_udp (die Gegenprobe ohne
XRCE-Hülle).
Status: done
§3.3 Async-Deep-Example
Spec: §3.3 (docs/specs/zerodds-endpoint-c-1.0.md) — lauffähiges
Beispiel für den Reactor, kein Stub.
Repo: endpoints/c/examples/example_async.c (C11-Reactor plus Callback,
übersetzt mit -std=c11 -pedantic -Wall -Wextra).
Tests: C-Seite — der Beispiel-Lauf (make -C endpoints/c examples, Ziel example_async).
Gemessene Ausgabe dieses Audits: fünf dekodierte Readings (id 0x2000 bis
0x2004) und ALL OK, 0 Compiler-Warnungen. Rust-Seite —
crates/endpoint-e2e/tests/c.rs::c_endpoint_async belegt denselben
Callback-Pfad gegen einen echten Peer.
Status: done
§3.4 Test-Pflicht: Live-Ping-Pong-E2E
Spec: §3.4 (docs/specs/zerodds-endpoint-c-1.0.md) — zusätzlich zu den
isolierten Unit- und Loopback-Tests aus §1 bis §3 MUSS jede Sprache eine
Live-E2E-Datei nach dem Muster crates/endpoint-e2e/tests/<lang>.rs
bereitstellen (Referenz: crates/endpoint-e2e/tests/cpp.rs), damit Framing
(§1), sync (§2) und async (§3) im Zusammenspiel belegt sind und nicht nur
einzeln.
Repo: crates/endpoint-e2e/tests/c.rs — drei Fälle über die
C-Endpoint-Oberfläche: der generierte Codec direkt auf einem POSIX-UDP-Socket
ohne XRCE-Hülle; dieselbe Probe in einem XRCE-WRITE_DATA über
zdw_endpoint_send und zdw_endpoint_recv; die ereignisgetriebene
Oberfläche über zdw_async_write und zdw_async_poll. Das Wire-Runtime
runtime/c wird über sein eigenes Makefile zur statischen Bibliothek
gebaut, weil dessen CFLAGS Voraussetzung sind; der Include-Pfad führt
runtime/c/include vor endpoints/c/include, weil runtime/c/include/zerodds_wire.h und
endpoints/c/include/zerodds_wire.h denselben Include-Guard führen.
Tests: Rust-Seite, Default-Lauf — crates/endpoint-e2e/tests/c.rs::c_raw_udp,
crates/endpoint-e2e/tests/c.rs::c_endpoint_sync,
crates/endpoint-e2e/tests/c.rs::c_endpoint_async. Gemessen (Kommando
cargo test -p zerodds-endpoint-e2e --test c): 3 passed, 0 failed, keine
SKIP-Zeile. Die drei Fälle stehen hinter keinem Feature-Gate, kehren aber
ohne C-Compiler auf dem PATH mit einer SKIP-Zeile zurück und zählen dann
als bestanden, ohne etwas geprüft zu haben; der Pipeline-Job endpoint-e2e
lässt den Job bei einer solchen Zeile scheitern. Der Rust-Peer prüft die
exakten 23 Oktetts des Ping und das Programm die Antwortzeile mit
reply=pong:hello from app.
Status: done
§4 reliable Stream
§4.1 C89-State-Machine (zdw_reliable)
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §3 — reliabler Sender und
Receiver, gespiegelt von crates/xrce/src/reliable.rs; fester Storage, kein
malloc: Fenster 16, Empfangspuffer 64, RFC-1982-16-Bit-Sequenznummern.
Repo: endpoints/c/include/zerodds_endpoint.h (Deklaration) und
endpoints/c/src/zerodds_endpoint.c (Implementierung in C89) — Sender-Seite
zdw_reliable_submit, zdw_reliable_pending_heartbeat,
zdw_reliable_recv_acknack, zdw_reliable_get_in_flight; Receiver-Seite
zdw_reliable_recv_data, zdw_reliable_drain,
zdw_reliable_pending_acknack; dazu zdw_reliable_reset.
Tests: C-Seite — endpoints/c/test/test_reliable_sm.c, (Makefile-Lauf make -C endpoints/c test): 14 Prüf-Funktionen, davon 13 als Spiegel der
Referenztests und eine als Byte-Golden-Check. Gemessene Ausgabe dieses
Audits: test_reliable_sm ALL OK. Die gespiegelte Referenz ist
crates/xrce/src/reliable.rs::tests::submit_assigns_monotonic_seqnrs,
crates/xrce/src/reliable.rs::tests::submit_rejects_when_window_full,
crates/xrce/src/reliable.rs::tests::pending_heartbeat_fires_first_time,
crates/xrce/src/reliable.rs::tests::pending_heartbeat_silenced_until_period_elapsed,
crates/xrce/src/reliable.rs::tests::recv_acknack_clears_acked_seqnrs,
crates/xrce/src/reliable.rs::tests::recv_acknack_full_clear_when_no_bits_set,
crates/xrce/src/reliable.rs::tests::recv_data_buffers_in_order,
crates/xrce/src/reliable.rs::tests::recv_data_reorders_out_of_order,
crates/xrce/src/reliable.rs::tests::recv_data_drops_duplicates,
crates/xrce/src/reliable.rs::tests::recv_data_rejects_when_buffer_full,
crates/xrce/src/reliable.rs::tests::pending_acknack_marks_missing_slots,
crates/xrce/src/reliable.rs::tests::reset_clears_state_completely,
crates/xrce/src/reliable.rs::tests::end_to_end_sender_receiver_with_loss_recovery.
Status: done
§4.2 Byte-golden HEARTBEAT und ACKNACK
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §4 — ACKNACK trägt
first_unacked_seq_num als i16, nack_bitmap als zwei Bytes
little-endian und stream_id als ein Byte; HEARTBEAT trägt
first_unacked_seq_nr und last_unacked_seq_nr als i16 plus stream_id.
Repo: endpoints/c/src/zerodds_endpoint.c (die Codec-Hälften von
zdw_reliable_pending_heartbeat und zdw_reliable_pending_acknack), geprüft
von der Byte-Golden-Funktion in endpoints/c/test/test_reliable_sm.c und vom
reinen Wire-Test endpoints/c/test/test_reliable.c.
Tests: C-Seite — endpoints/c/test/test_reliable.c und
endpoints/c/test/test_reliable_sm.c gegen golden_heartbeat_le.bin und
golden_acknack_le.bin (Makefile-Lauf make -C endpoints/c test). Gemessene
Ausgabe dieses Audits: HEARTBEAT parsed first=1 last=3 stream=0x80 / ACKNACK
13 bytes byte-identical to crates/xrce. Die Referenz, aus der beide Goldens
entstehen: crates/xrce/src/submessages/heartbeat.rs::tests::heartbeat_le_roundtrip,
crates/xrce/src/submessages/heartbeat.rs::tests::heartbeat_roundtrip_via_submessage,
crates/xrce/src/submessages/acknack.rs::tests::acknack_le_roundtrip,
crates/xrce/src/submessages/acknack.rs::tests::acknack_roundtrip_via_submessage.
Status: done
§4.3 pthread-SPSC-Ring als async Writer (Latenz-Entkopplung)
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §2 — der Producer darf nie in
den Kernel eintreten; das Bauteil trägt zugleich den reliable Sender-State.
Repo: endpoints/c/include/zerodds_reliable_async.h /
endpoints/c/src/zerodds_reliable_async.c (C11 plus pthread):
zdw_async_ring_start startet den Drain-Thread, zdw_async_ring_enqueue ist
ein wait-freies Enqueue ohne Syscall, der Drain-Thread hält den
zdw_reliable-State und übernimmt Framing und I/O.
Tests: C-Seite — endpoints/c/test/test_reliable_async_overflow.c,
(Makefile-Lauf make -C endpoints/c test): prüft das Verhalten bei vollem
Sende-Fenster und dass der Drain-Thread sich nicht selbst bestätigt.
Gemessene Ausgabe dieses Audits: ALL OK, delivered 24/24, no self-ack, no
hang. Dazu endpoints/c/test/bench_reliable_async.c als Latenzvergleich
(siehe §4.6). Rust-Seite —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_latency_bench baut und
fährt denselben Ring aus dem Default-Lauf heraus.
Status: done
§4.4 Loss-Recovery-Example
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §5 Punkt 5 — lauffähiges
Beispiel, kein Stub: N Samples, injizierter Verlust, lückenlose Zustellung
nach Retransmit.
Repo: endpoints/c/examples/example_reliable.c — 12 Samples submitted,
jedes vierte beim ersten Durchlauf verworfen, Recovery-Runden bis alle 12 in
der Folge 0 bis 11 angekommen sind.
Tests: C-Seite — der Beispiel-Lauf (make -C endpoints/c examples, Ziel
example_reliable; zusätzlich im Makefile-Ziel test). Gemessene Ausgabe dieses Audits: 3/12 delivered
before recovery, delivered contiguous 0..11 expected=12, ALL 12 samples
recovered gap-free. Rust-Seite —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_loss_recovery belegt
denselben Ablauf live über UDP gegen einen fremden Prozess statt in-process.
Status: done
§4.5 Live-E2E gegen den Rust-Referenz-Peer
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §5 Punkt 3 — live gegen den
Rust reliable Peer, stream_id >= 128, mit injiziertem Drop; Assert: alle
Samples lückenlos und in Reihenfolge zugestellt trotz Verlust.
Repo: endpoints/c/test/reliable_udp_app.c — die C-App spielt den
reliablen Sender (submit, WRITE_DATA, HEARTBEAT, ACKNACK-getriebener
Retransmit) gegen den geteilten Rust-Peer aus
crates/endpoint-e2e/src/lib.rs, der den Verlust injiziert. Die Datei
deklariert _POSIX_C_SOURCE 200809L und zusätzlich _DARWIN_C_SOURCE 1,
weil INADDR_LOOPBACK unter Apples libc eine BSD-Socket-Erweiterung ist und
sonst nicht sichtbar wäre; unter glibc gilt die Einschränkung nicht.
Tests: Rust-Seite, Default-Lauf —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_loss_recovery
(12 Samples, injizierter Drop, Assert auf Lückenlosigkeit) und
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_no_loss (12 Samples
ohne Drop). Gemessen (Kommando cargo test -p zerodds-endpoint-e2e --test c_reliable): 3 passed, 0 failed, keine SKIP-Zeile. Auch hier gilt die
Compiler-Vorprüfung aus dem Kopf dieses Dokuments — ohne Compiler auf dem
Suchpfad skippen die Fälle laut und der Pipeline-Job scheitert daran.
Status: done
§4.6 Latenz: inline Send gegen entkoppeltes Ring-Enqueue
Spec: §4 (docs/specs/zerodds-endpoint-c-1.0.md) in Verbindung mit
docs/spec-coverage/reliable-endpoint-1.0.md §5 Punkt 4 — die
Producer-Latenz von write bis return im entkoppelten Pfad MUSS gegen den
inline Deliver gemessen werden; ein Messwert, der die Entkopplung zeigt.
Repo: endpoints/c/test/bench_reliable_async.c — misst
inline_ns_per_op (Framing plus send()-Syscall pro Sample gegen einen
gebundenen, aber ungelesenen Loopback-Socket) gegen
decoupled_enqueue_ns_per_op (reines Enqueue in den Ring, kein Kernel). Die
Datei trug dieselbe INADDR_LOOPBACK-Ursache wie §4.5 und deklariert
deshalb ebenfalls _DARWIN_C_SOURCE 1.
Tests: Rust-Seite, Default-Lauf —
crates/endpoint-e2e/tests/c_reliable.rs::c_reliable_latency_bench (baut das
Bench-Programm mit gcc -O2 -std=c11, fährt es und lässt den Fall
scheitern, wenn die Latenzzeile fehlt oder das entkoppelte Enqueue nicht
unter dem inline Send liegt). C-Seite — dasselbe Programm im Makefile-Ziel
(test). Gemessen in diesem Audit auf macOS 15 (Apple-Clang):
inline_ns_per_op=2585 gegen decoupled_enqueue_ns_per_op=13 über 5000
zugestellte Samples. Der absolute Wert für die inline-Zahl schwankt mit der
Host-Last; der Assert prüft die Ordnung, nicht den Betrag.
Status: done
Audit-Status
15 done / 0 partial / 0 open / 0 blocked / 0 n/a (informative).
Test-Lauf: C-Seite — cargo run -p zerodds-endpoint-golden -- <dir> gefolgt
von make -C endpoints/c test GOLDEN_DIR=<dir> und
make -C endpoints/c examples — alle 20 Binaries bauen mit
-std=c89 -pedantic -Wall -Wextra beziehungsweise
-std=c11 -pedantic -Wall -Wextra ohne Warnung und laufen durch, 0 failed.
Test-Lauf: Rust-Seite — cargo test -p zerodds-endpoint-e2e --test c — 3
passed, 0 failed; cargo test -p zerodds-endpoint-e2e --test c_reliable — 3
passed, 0 failed. Keiner der sechs Fälle hat geskippt.
Beide Läufe gemessen auf macOS 15, cc und gcc sind dort Apple-Clang.
Offene Punkte: keine.