ZeroDDS UDS Transport 1.0 — Spec Coverage

Source: this document is also the spec. For Unix domain sockets OMG standardizes nothing beyond the vendor-reserved locator kind (DDSI-RTPS 2.5 §9.4.5); path resolution, wire format and cleanup are vendor-specific. Cyclone DDS and FastDDS have no official UDS transport. The normative requirements live in the ## sections §2 through §6; every ### item below carries a spec citation, a repo anchor, a test and a status.

Implementation: crates/transport-uds/src/lib.rs — SOCK_DGRAM over filesystem sockets; crates/transport-uds/src/abstract_dgram.rs — the Linux abstract namespace, #[cfg(target_os = "linux")].

Open items: zerodds-uds-transport-1.0.open.en.md.

§1 Scope and spec status

§1.1 Vendor-reserved locator kind

Spec: DDSI-RTPS 2.5 §9.3.2.4 — 0x00000003 to 0x01ffffff are reserved for vendor-specific locator kinds. ZeroDDS takes 0x01F0_0002 for UDS. The port field is meaningless and MUST be 0; the 16 address bytes carry the endpoint id.

Repo: crates/rtps/src/wire_types.rs (LocatorKind::Uds, Locator::uds); bound in crates/transport-uds/src/lib.rs (UdsTransport::local_locator, the kind check in Transport::send).

Tests: crates/transport-uds/src/lib.rs::tests::locator_kind_is_the_vendor_reserved_uds_value (asserts the numeric value 0x01F0_0002, the position in the range 0x00000003 to 0x01ffffff and port == 0), crates/transport-uds/src/lib.rs::tests::local_locator_reflects_bind_id, crates/transport-uds/src/lib.rs::tests::send_rejects_non_uds_locator, crates/rtps/src/wire_types.rs::tests::locator_uds_layout, crates/rtps/src/wire_types.rs::tests::locator_kind_roundtrip.

Status: done

§1.2 What OMG does not standardize for UDS

Spec: DDSI-RTPS 2.5 knows no path scheme, no UDS wire format and no cleanup protocol. Cyclone DDS and FastDDS have no official UDS transport; they cover the same use case through iceoryx or POSIX SHM. The section records the scope and states no implementation requirement.

Repo: —

Tests: —

Status: n/a (informative) — scope statement; the requirements are the items from §2 on.

§2 Path resolution

§2.1 Filesystem mode (default)

Spec: §2 — the 16-byte locator address resolves to <base_dir>/<lowercase-hex32>.sock. The default base_dir is /tmp/zerodds/uds. The hex string is the address in 32 lowercase characters, without separators.

Repo: crates/transport-uds/src/lib.rs (socket_path, DEFAULT_BASE_DIR, parse_hex_id, source_locator as the reverse direction).

Tests: crates/transport-uds/src/lib.rs::tests::socket_path_is_hex_under_base_dir (checks prefix and .sock suffix on an address with edge bytes set), crates/transport-uds/src/lib.rs::tests::parse_hex_id_roundtrip, crates/transport-uds/src/lib.rs::tests::parse_hex_id_rejects_wrong_length, crates/transport-uds/src/lib.rs::tests::parse_hex_id_rejects_non_hex.

Status: done

§2.2 Base directory: lazy, user-private, TOCTOU-safe

Spec: §2 — the base directory is created on bind when it is missing and then carries mode 0700. An existing path that is a symlink or not a directory MUST be refused rather than chmodded — otherwise a planted symlink could set foreign paths to 0700. No mode is set on an existing foreign directory.

Repo: crates/transport-uds/src/lib.rs (ensure_base_dir — symlink_metadata instead of exists, refusal of symlink and non-directory, create_dir_all plus set_permissions(0o700) with a re-check).

Tests: crates/transport-uds/src/lib.rs::tests::ensure_base_dir_creates_dir_with_mode_0700 (reads the mode back and compares it against 0o700), crates/transport-uds/src/lib.rs::tests::ensure_base_dir_rejects_symlink, crates/transport-uds/src/lib.rs::tests::ensure_base_dir_rejects_file_path, crates/transport-uds/src/lib.rs::tests::ensure_base_dir_creates_missing_dir.

Status: done

§2.3 Abstract-namespace mode (Linux)

Spec: §2 — on Linux there is the alternative without a filesystem inode: the socket name starts with a null byte, followed by <prefix>-<lowercase-hex32>. No cleanup needed, no inode permissions, no cross-mount volume. The name fits into sun_path (108 bytes, byte 0 is the null prefix).

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAbstractDgramTransport, UdsAddress::Abstract, MAX_ABSTRACT_NAME = 107, UdsAddress::abstract_default); the module is declared in crates/transport-uds/src/lib.rs under #[cfg(target_os = "linux")].

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::abstract_bind_does_not_create_file, crates/transport-uds/src/abstract_dgram.rs::tests::abstract_send_recv_roundtrip, crates/transport-uds/src/abstract_dgram.rs::tests::abstract_preserves_message_boundaries, crates/transport-uds/src/abstract_dgram.rs::tests::local_locator_reflects_bind_id. These tests run 0 cases on macOS: the module sits behind #[cfg(target_os = "linux")] and is not compiled there. They were measured on the Linux test host against the same, unchanged abstract_dgram.rs (command cargo test -p zerodds-transport-uds) — 11 cases from abstract_dgram::tests green, 0 failed.

Status: done

§2.4 Filesystem mode inside the abstract transport (A/B comparison)

Spec: §2 — the same transport MUST also do filesystem addressing so abstract and filesystem mode can be measured against each other with identical socket code. The difference is one config line.

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAddress::Filesystem, UdsAddress::filesystem_default, fs_path).

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_bind_creates_socket_file (the socket file appears on bind and is gone after drop), crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_send_recv_roundtrip. These tests too are #[cfg(target_os = "linux")]-gated and run 0 cases on macOS; measured on the Linux test host in the same run as §2.3.

Status: done

§3 Wire format

§3.1 SOCK_DGRAM with preserved message boundaries

Spec: §3 — one datagram message per RTPS frame. The kernel preserves the message boundaries, unlike a TCP stream; no framing layer on top is needed. Two sends MUST arrive as two receives with the original lengths.

Repo: crates/transport-uds/src/lib.rs (UdsTransport over std::os::unix::net::UnixDatagram, Transport::send with send_to, Transport::recv with recv_from).

Tests: crates/transport-uds/src/lib.rs::tests::preserves_message_boundaries_two_datagrams (two sends of different lengths, two receives with exactly those lengths), crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process, crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (the same delivery across a real process boundary).

Status: done

§3.2 Sender locator from the socket address

Spec: §3 — the receiver MUST recover the locator from the sender address so RTPS can answer. If the address is unnamed (an anonymous client that did not bind) or unresolvable, Locator::INVALID MUST be returned — not a guess.

Repo: crates/transport-uds/src/lib.rs (source_locator, parse_hex_id, Locator::INVALID).

Tests: crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process (checks got.source against the sender’s locator), crates/transport-uds/src/lib.rs::tests::preserves_message_boundaries_two_datagrams, crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary.

Status: done

§3.3 Datagram upper bound

Spec: §3 — max_datagram (default 65 536) bounds what is sent and what is read into the receive buffer. A larger payload MUST be refused with PayloadTooLarge; a silent truncation is not acceptable, because at the RTPS level it would be indistinguishable from a short message.

Repo: crates/transport-uds/src/lib.rs (DEFAULT_MAX_DATAGRAM, the length check in Transport::send, the buffer size in Transport::recv).

Tests: crates/transport-uds/src/lib.rs::tests::send_rejects_oversize_payload (a payload above the configured limit is refused with PayloadTooLarge), crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates (1 KiB arrives untruncated; at max_datagram either the delivery is whole or the error names EMSGSIZE — and then nothing partial is in the queue).

Status: done

§3.4 Kernel bound versus configured bound

Spec: §3 — the real per-message bound is the kernel’s: Linux via wmem_max (default 212 992), macOS via net.local.dgram.maxdgram (default 2048). The transport MUST reconcile the configured bound with the operating system’s, surface it at bind time or size the socket buffers accordingly — otherwise a sample between the two bounds fails only at runtime.

Repo: crates/transport-uds/src/lib.rs (size_buffers: send buffer at twice, receive buffer at eight times max_datagram, called in UdsTransport::bind and UdsAbstractDgramTransport::bind).

Tests: crates/transport-uds/src/lib.rs::tests::a_datagram_near_the_configured_limit_arrives_whole (four datagrams of max_datagram − 512 bytes arrive whole; measured on macOS 26, where anything above 2048 bytes failed with EMSGSIZE before), crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates.

Status: done

§4 Cleanup semantics

§4.1 Filesystem mode: cleanup on drop, takeover on bind

Spec: §4 — bind creates the socket file, drop removes it best-effort (an unmounted container volume must not make the drop fail). If a bind finds a leftover file from a crashed predecessor, it MUST remove it and rebind: UnixDatagram::bind cannot bind over an existing file and otherwise reports EADDRINUSE.

Repo: crates/transport-uds/src/lib.rs (UdsTransport::bind with remove_file on an existing path, Drop for UdsTransport).

Tests: crates/transport-uds/src/lib.rs::tests::bind_creates_socket_file (the file exists after bind and is gone after drop), crates/transport-uds/src/lib.rs::tests::bind_reuses_path_after_stale_leftover (a leftover file with foreign content does not block the bind).

Status: done

§4.2 Abstract mode: no cleanup needed

Spec: §4 — the abstract namespace has no inode; Linux releases the name once the last file descriptor closes. A crash-cleanup path is therefore not required, and a bind must not create a file.

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAbstractDgramTransport::bind in the Abstract branch without filesystem access, Drop removing only in the Filesystem branch).

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::abstract_bind_does_not_create_file, crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_bind_creates_socket_file (the counter-check: in the filesystem branch the file appears and disappears). Both #[cfg(target_os = "linux")]-gated, 0 cases on macOS; measured on the Linux test host as in §2.3.

Status: done

§4.3 Differentiated send-error classification

Spec: §4 — a send error MUST name its cause: a missing destination socket, a denied permission, a full kernel buffer, a refused connection and an exceeded message bound are different operational states. A catch-all Io makes them indistinguishable.

Repo: crates/transport-uds/src/lib.rs (classify_send_error — NotFound, PermissionDenied, WouldBlock, ConnectionRefused, EMSGSIZE via raw_os_error).

Tests: crates/transport-uds/src/lib.rs::tests::classify_send_error_maps_kinds (every named io::ErrorKind value against its message), crates/transport-uds/src/lib.rs::tests::send_to_missing_peer_is_io_error, crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates (the EMSGSIZE branch, where the kernel refuses the message).

Status: done

§4.4 Idle behaviour of recv

Spec: §4 — with recv_timeout set, an empty socket MUST yield RecvError::Timeout after it elapses, not an Io error. Without a timeout recv blocks.

Repo: crates/transport-uds/src/lib.rs (UdsConfig::recv_timeout, set_read_timeout in bind, the WouldBlock/TimedOut branch in Transport::recv).

Tests: crates/transport-uds/src/lib.rs::tests::recv_times_out_when_idle, crates/transport-uds/src/abstract_dgram.rs::tests::recv_times_out_when_idle (the same evidence in the abstract transport, Linux-gated).

Status: done

§5 Container use case

§5.1 Volume mount as the permission boundary

Spec: §5 — the Docker and Kubernetes pattern is a shared mounted volume under base_dir; every container binds its own 16-byte locator as a socket file and the kernel routes between the file descriptors. UDS is the choice there when multicast is blocked by a cluster network policy and POSIX SHM is impractical cross-container (UID mapping, /dev/shm visibility, SELinux profiles).

Repo: crates/transport-uds/src/lib.rs (UdsConfig::base_dir as a free path, socket_path below it; the drop cleanup is best-effort so an unmounted volume does not make the drop fail).

Tests: crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (two OS processes over a shared directory — the same topology as two containers over a shared volume, without the container-runtime part), crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process.

Status: partial — the process boundary is shown, the container boundary is not: no test starts two containers with a mounted volume and actually exercises the UID-mapping and SELinux questions. The missing evidence is carried as open item §5.2.

§5.2 Evidence across a real container boundary

Spec: §5 — that the pattern of §5.1 holds under a container runtime MUST be shown by a test with two containers and one mounted volume: different UID mappings on both sides, the socket file on the volume, a datagram in each direction.

Repo: —

Tests: —

Status: open — no test starts two containers with a mounted volume and different UID mappings.

§6 Platform support

§6.1 Linux and macOS

Spec: §6 — Linux is the primary platform with both modes (filesystem and abstract namespace), macOS supports the filesystem mode. The crate as a whole is #[cfg(unix)].

Repo: crates/transport-uds/src/lib.rs (#![cfg(unix)], #[cfg(target_os = "linux")] pub mod abstract_dgram).

Tests: crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process and crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (measured on both platforms: macOS in this audit, Linux on the Linux test host), crates/transport-uds/src/abstract_dgram.rs::tests::abstract_send_recv_roundtrip (Linux only).

Status: done

§6.2 Windows

Spec: §6 — Unix domain sockets are Unix-specific. On Windows the crate compiles to an empty library; consumers gate their UDS usage with #[cfg(unix)]. The obvious analogy would be named pipes — a different protocol with different addressing, and not part of this spec.

Repo: —

Tests: —

Status: open — on Windows the crate compiles to an empty library (crates/transport-uds/src/lib.rs: #![cfg(unix)]). Windows offers sockets of the family AF_UNIX since Windows 10 (build 17063) (Microsoft’s statement, not checked here); the transport does not use them.

§6.3 Cross-vendor interop

Spec: §6 — UDS is intra-container and intra-host IPC. Cross-vendor interop with Cyclone DDS or FastDDS stays in the UDP, TCP and SHM domains; neither vendor has a UDS transport a wire comparison could run against. The section records the scope and states no implementation requirement.

Repo: —

Tests: —

Status: n/a (informative) — neither Cyclone DDS nor FastDDS has a UDS transport against which a wire comparison would be possible.


Audit-Status

14 done / 1 partial / 2 open / 0 blocked / 2 n/a (informative).

Test run: cargo test -p zerodds-transport-uds — on macOS 15 (Rust host aarch64-apple-darwin) 20 tests (lib) + 1 test (tests/l1_cross_process.rs) green, 0 failed. The #[cfg(target_os = "linux")]-gated abstract_dgram module runs 0 cases there; the same command on the Linux test host against the unchanged abstract_dgram.rs — 11 cases from abstract_dgram::tests green, 0 failed.

Open items: zerodds-uds-transport-1.0.open.en.md.

ZeroDDS-UDS-Transport 1.0 — Spec-Coverage

Quelle: dieses Dokument ist zugleich die Spec. Für Unix-Domain-Sockets normiert OMG nichts außer dem vendor-reservierten Locator-Kind (DDSI-RTPS 2.5 §9.4.5); Pfad-Auflösung, Wire-Format und Cleanup sind vendor-spezifisch. Cyclone DDS und FastDDS haben keinen offiziellen UDS-Transport. Die normativen Anforderungen stehen in den ##-Sektionen §2 bis §6; jedes ###-Item darunter trägt Spec-Zitat, Repo-Anker, Test und Status.

Implementation: crates/transport-uds/src/lib.rs — SOCK_DGRAM über Filesystem-Sockets; crates/transport-uds/src/abstract_dgram.rs — Linux-Abstract-Namespace, #[cfg(target_os = "linux")].

Offene Punkte: zerodds-uds-transport-1.0.open.md.

§1 Scope und Spec-Status

§1.1 Vendor-reservierter Locator-Kind

Spec: DDSI-RTPS 2.5 §9.3.2.4 — 0x00000003 bis 0x01ffffff sind für vendor-spezifische Locator-Kinds reserviert. ZeroDDS belegt 0x01F0_0002 für UDS. Das Port-Feld ist bedeutungslos und MUSS 0 sein; die 16 Adress-Bytes tragen die Endpoint-ID.

Repo: crates/rtps/src/wire_types.rs (LocatorKind::Uds, Locator::uds); gebunden in crates/transport-uds/src/lib.rs (UdsTransport::local_locator, Kind-Prüfung in Transport::send).

Tests: crates/transport-uds/src/lib.rs::tests::locator_kind_is_the_vendor_reserved_uds_value (prüft den Zahlwert 0x01F0_0002, die Lage im Bereich 0x00000003 bis 0x01ffffff und port == 0), crates/transport-uds/src/lib.rs::tests::local_locator_reflects_bind_id, crates/transport-uds/src/lib.rs::tests::send_rejects_non_uds_locator, crates/rtps/src/wire_types.rs::tests::locator_uds_layout, crates/rtps/src/wire_types.rs::tests::locator_kind_roundtrip.

Status: done

§1.2 Was OMG für UDS nicht normiert

Spec: DDSI-RTPS 2.5 kennt kein Pfad-Schema, kein UDS-Wire-Format und kein Cleanup-Protokoll. Cyclone DDS und FastDDS haben keinen offiziellen UDS-Transport; sie decken denselben Anwendungsfall über iceoryx oder POSIX-SHM ab. Die Sektion hält den Scope fest und stellt keine Implementierungs-Anforderung.

Repo: —

Tests: —

Status: n/a (informative) — Abgrenzung des Geltungsbereichs; die Anforderungen stehen in den Items ab §2.

§2 Pfad-Auflösung

§2.1 Filesystem-Modus (Default)

Spec: §2 — die 16-Byte-Locator-Adresse wird als <base_dir>/<lowercase-hex32>.sock aufgelöst. Default-base_dir ist /tmp/zerodds/uds. Der Hex-String ist die Adresse in 32 Kleinbuchstaben, ohne Trenner.

Repo: crates/transport-uds/src/lib.rs (socket_path, DEFAULT_BASE_DIR, parse_hex_id, source_locator als Rückrichtung).

Tests: crates/transport-uds/src/lib.rs::tests::socket_path_is_hex_under_base_dir (prüft Präfix und .sock-Endung an einer Adresse mit gesetzten Randbytes), crates/transport-uds/src/lib.rs::tests::parse_hex_id_roundtrip, crates/transport-uds/src/lib.rs::tests::parse_hex_id_rejects_wrong_length, crates/transport-uds/src/lib.rs::tests::parse_hex_id_rejects_non_hex.

Status: done

§2.2 Basis-Verzeichnis: lazy, user-privat, TOCTOU-fest

Spec: §2 — das Basis-Verzeichnis wird beim Bind angelegt, wenn es fehlt, und trägt dann Mode 0700. Ein bestehender Pfad, der ein Symlink oder keine Verzeichnis ist, MUSS abgelehnt werden, statt ihn zu chmodden — sonst könnte ein untergeschobener Symlink fremde Pfade auf 0700 setzen. Auf ein bestehendes fremdes Verzeichnis wird kein Mode gesetzt.

Repo: crates/transport-uds/src/lib.rs (ensure_base_dir — symlink_metadata statt exists, Ablehnung von Symlink und Nicht-Verzeichnis, create_dir_all plus set_permissions(0o700) mit Wiederholungs-Prüfung).

Tests: crates/transport-uds/src/lib.rs::tests::ensure_base_dir_creates_dir_with_mode_0700 (liest den Mode zurück und vergleicht ihn gegen 0o700), crates/transport-uds/src/lib.rs::tests::ensure_base_dir_rejects_symlink, crates/transport-uds/src/lib.rs::tests::ensure_base_dir_rejects_file_path, crates/transport-uds/src/lib.rs::tests::ensure_base_dir_creates_missing_dir.

Status: done

§2.3 Abstract-Namespace-Modus (Linux)

Spec: §2 — auf Linux gibt es die Alternative ohne Filesystem-Inode: der Socket-Name beginnt mit einem Null-Byte, gefolgt von <prefix>-<lowercase-hex32>. Kein Cleanup nötig, keine Inode-Permissions, kein Cross-Mount-Volume. Der Name passt in sun_path (108 Byte, Byte 0 ist das Null-Präfix).

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAbstractDgramTransport, UdsAddress::Abstract, MAX_ABSTRACT_NAME = 107, UdsAddress::abstract_default); Modul deklariert in crates/transport-uds/src/lib.rs unter #[cfg(target_os = "linux")].

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::abstract_bind_does_not_create_file, crates/transport-uds/src/abstract_dgram.rs::tests::abstract_send_recv_roundtrip, crates/transport-uds/src/abstract_dgram.rs::tests::abstract_preserves_message_boundaries, crates/transport-uds/src/abstract_dgram.rs::tests::local_locator_reflects_bind_id. Diese Tests fahren auf macOS 0 Fälle: das Modul steht hinter #[cfg(target_os = "linux")] und wird dort nicht übersetzt. Gemessen wurden sie auf dem Linux-Testhost gegen dieselbe, unveränderte abstract_dgram.rs (Kommando cargo test -p zerodds-transport-uds) — 11 Fälle aus abstract_dgram::tests grün, 0 failed.

Status: done

§2.4 Filesystem-Modus im Abstract-Transport (A/B-Vergleich)

Spec: §2 — derselbe Transport MUSS auch Filesystem-Adressierung können, damit Abstract- und Filesystem-Modus mit identischem Socket-Code gegeneinander gemessen werden können. Der Unterschied ist eine Config-Zeile.

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAddress::Filesystem, UdsAddress::filesystem_default, fs_path).

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_bind_creates_socket_file (Socket-Datei entsteht beim Bind und verschwindet beim Drop), crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_send_recv_roundtrip. Auch diese Tests sind #[cfg(target_os = "linux")]-gated und fahren auf macOS 0 Fälle; gemessen auf dem Linux-Testhost im selben Lauf wie §2.3.

Status: done

§3 Wire-Format

§3.1 SOCK_DGRAM mit erhaltenen Message-Grenzen

Spec: §3 — eine Datagram-Message pro RTPS-Frame. Der Kernel erhält die Message-Grenzen, anders als bei einem TCP-Stream; es braucht deshalb kein Framing-Layer darüber. Zwei Sends MÜSSEN als zwei Receives mit den ursprünglichen Längen ankommen.

Repo: crates/transport-uds/src/lib.rs (UdsTransport über std::os::unix::net::UnixDatagram, Transport::send mit send_to, Transport::recv mit recv_from).

Tests: crates/transport-uds/src/lib.rs::tests::preserves_message_boundaries_two_datagrams (zwei Sends unterschiedlicher Länge, zwei Receives mit genau diesen Längen), crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process, crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (dieselbe Zustellung über eine echte Prozessgrenze).

Status: done

§3.2 Absender-Locator aus der Socket-Adresse

Spec: §3 — der Empfänger MUSS aus der Absender-Adresse den Locator zurückgewinnen, damit RTPS antworten kann. Ist die Adresse unbenannt (anonymer Client ohne Bind) oder nicht auflösbar, MUSS Locator::INVALID zurückgegeben werden — nicht geraten.

Repo: crates/transport-uds/src/lib.rs (source_locator, parse_hex_id, Locator::INVALID).

Tests: crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process (prüft got.source gegen den Locator des Senders), crates/transport-uds/src/lib.rs::tests::preserves_message_boundaries_two_datagrams, crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary.

Status: done

§3.3 Datagram-Obergrenze

Spec: §3 — max_datagram (Default 65 536) begrenzt, was gesendet und was in den Empfangspuffer gelesen wird. Eine größere Nutzlast MUSS mit PayloadTooLarge abgelehnt werden; eine stille Kürzung ist nicht zulässig, weil sie auf RTPS-Ebene von einer kurzen Nachricht nicht zu unterscheiden wäre.

Repo: crates/transport-uds/src/lib.rs (DEFAULT_MAX_DATAGRAM, Längenprüfung in Transport::send, Puffergröße in Transport::recv).

Tests: crates/transport-uds/src/lib.rs::tests::send_rejects_oversize_payload (Nutzlast über dem konfigurierten Limit wird mit PayloadTooLarge abgelehnt), crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates (1 KiB kommt unverkürzt an; bei max_datagram gilt entweder unverkürzte Zustellung oder ein Fehler, der EMSGSIZE benennt — und dann liegt nichts Teilweises in der Queue).

Status: done

§3.4 Kernel-Obergrenze gegen konfigurierte Obergrenze

Spec: §3 — die tatsächliche Obergrenze pro Message setzt der Kernel: Linux über wmem_max (Default 212 992), macOS über net.local.dgram.maxdgram (Default 2048). Der Transport MUSS die konfigurierte Grenze gegen die des Betriebssystems abgleichen, sie beim Bind sichtbar machen oder die Socket-Puffer entsprechend setzen — sonst scheitert ein Sample zwischen beiden Grenzen erst zur Laufzeit.

Repo: crates/transport-uds/src/lib.rs (size_buffers: Sendepuffer auf das Doppelte, Empfangspuffer auf das Achtfache von max_datagram, aufgerufen in UdsTransport::bind und UdsAbstractDgramTransport::bind).

Tests: crates/transport-uds/src/lib.rs::tests::a_datagram_near_the_configured_limit_arrives_whole (vier Datagramme zu max_datagram − 512 Byte kommen ganz an; auf macOS 26 gemessen, vorher dort EMSGSIZE über 2048 Byte), crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates.

Status: done

§4 Cleanup-Semantik

§4.1 Filesystem-Modus: Cleanup beim Drop, Übernahme beim Bind

Spec: §4 — der Bind legt die Socket-Datei an, der Drop entfernt sie best-effort (ein abgehängtes Container-Volume darf den Drop nicht scheitern lassen). Findet ein Bind eine Restdatei eines abgestürzten Vorgängers, MUSS er sie entfernen und neu binden: UnixDatagram::bind kann nicht über eine bestehende Datei binden und meldet sonst EADDRINUSE.

Repo: crates/transport-uds/src/lib.rs (UdsTransport::bind mit remove_file bei bestehendem Pfad, Drop for UdsTransport).

Tests: crates/transport-uds/src/lib.rs::tests::bind_creates_socket_file (Datei existiert nach dem Bind und ist nach dem Drop weg), crates/transport-uds/src/lib.rs::tests::bind_reuses_path_after_stale_leftover (eine Restdatei mit fremdem Inhalt blockiert den Bind nicht).

Status: done

§4.2 Abstract-Modus: kein Cleanup nötig

Spec: §4 — im Abstract-Namespace gibt es keinen Inode; Linux gibt den Namen frei, sobald der letzte Filedeskriptor schließt. Ein Crash-Cleanup-Pfad ist deshalb nicht erforderlich, und ein Bind darf keine Datei anlegen.

Repo: crates/transport-uds/src/abstract_dgram.rs (UdsAbstractDgramTransport::bind im Abstract-Zweig ohne Filesystem-Zugriff, Drop entfernt nur im Filesystem-Zweig).

Tests: crates/transport-uds/src/abstract_dgram.rs::tests::abstract_bind_does_not_create_file, crates/transport-uds/src/abstract_dgram.rs::tests::filesystem_bind_creates_socket_file (die Gegenprobe: im Filesystem-Zweig entsteht und verschwindet die Datei). Beide #[cfg(target_os = "linux")]-gated, auf macOS 0 Fälle; gemessen auf dem Linux-Testhost wie §2.3.

Status: done

§4.3 Differenzierte Fehlerklassifikation beim Senden

Spec: §4 — ein Sendefehler MUSS seine Ursache benennen: fehlender Ziel-Socket, verweigerte Permission, voller Kernel-Puffer, abgelehnte Verbindung und überschrittene Message-Grenze sind unterschiedliche Betriebszustände. Ein Sammel-Io macht sie ununterscheidbar.

Repo: crates/transport-uds/src/lib.rs (classify_send_error — NotFound, PermissionDenied, WouldBlock, ConnectionRefused, EMSGSIZE über raw_os_error).

Tests: crates/transport-uds/src/lib.rs::tests::classify_send_error_maps_kinds (alle benannten io::ErrorKind-Werte auf ihre Meldung), crates/transport-uds/src/lib.rs::tests::send_to_missing_peer_is_io_error, crates/transport-uds/src/lib.rs::tests::oversized_for_the_kernel_fails_loudly_and_never_truncates (der EMSGSIZE-Zweig, sofern der Kernel die Message ablehnt).

Status: done

§4.4 Leerlauf-Verhalten von recv

Spec: §4 — mit gesetztem recv_timeout MUSS ein leerer Socket nach Ablauf RecvError::Timeout liefern, nicht einen Io-Fehler. Ohne Timeout blockiert recv.

Repo: crates/transport-uds/src/lib.rs (UdsConfig::recv_timeout, set_read_timeout im Bind, WouldBlock/TimedOut-Zweig in Transport::recv).

Tests: crates/transport-uds/src/lib.rs::tests::recv_times_out_when_idle, crates/transport-uds/src/abstract_dgram.rs::tests::recv_times_out_when_idle (derselbe Nachweis im Abstract-Transport, Linux-gated).

Status: done

§5 Container-Anwendungsfall

§5.1 Volume-Mount als Permission-Grenze

Spec: §5 — das Muster für Docker und Kubernetes ist ein gemeinsam gemountetes Volume unter base_dir; jeder Container bindet seinen eigenen 16-Byte-Locator als Socket-Datei, der Kernel routet zwischen den Filedeskriptoren. UDS ist dort die Wahl, wenn Multicast durch eine Cluster-Network-Policy blockiert ist und POSIX-SHM cross-Container unpraktisch ist (UID-Mapping, /dev/shm-Sichtbarkeit, SELinux-Profile).

Repo: crates/transport-uds/src/lib.rs (UdsConfig::base_dir als freier Pfad, socket_path unterhalb davon; Drop-Cleanup ist best-effort, damit ein abgehängtes Volume den Drop nicht scheitern lässt).

Tests: crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (zwei OS-Prozesse über ein gemeinsames Verzeichnis — dieselbe Topologie wie zwei Container über ein gemeinsames Volume, ohne den Container-Runtime-Anteil), crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process.

Status: partial — die Prozessgrenze ist belegt, die Container-Grenze nicht: es gibt keinen Test, der zwei Container mit gemountetem Volume startet und die UID-Mapping- und SELinux-Fragen tatsächlich durchspielt. Der fehlende Nachweis ist als §5.2 offen geführt.

§5.2 Nachweis über eine echte Container-Grenze

Spec: §5 — dass das Muster aus §5.1 unter einer Container-Runtime trägt, MUSS ein Test mit zwei Containern und einem gemounteten Volume belegen: unterschiedliche UID-Mappings auf beiden Seiten, Socket-Datei auf dem Volume, Datagram in beide Richtungen.

Repo: —

Tests: —

Status: open — kein Test startet zwei Container mit gemountetem Volume und unterschiedlichen UID-Mappings.

§6 Plattform-Support

§6.1 Linux und macOS

Spec: §6 — Linux ist die primäre Plattform mit beiden Modi (Filesystem und Abstract-Namespace), macOS unterstützt den Filesystem-Modus. Der Crate ist als Ganzes #[cfg(unix)].

Repo: crates/transport-uds/src/lib.rs (#![cfg(unix)], #[cfg(target_os = "linux")] pub mod abstract_dgram).

Tests: crates/transport-uds/src/lib.rs::tests::send_and_recv_roundtrip_same_process und crates/transport-uds/tests/l1_cross_process.rs::l1_datagram_crosses_process_boundary (auf beiden Plattformen gemessen: macOS in diesem Audit, Linux auf Linux-Testhost), crates/transport-uds/src/abstract_dgram.rs::tests::abstract_send_recv_roundtrip (nur Linux).

Status: done

§6.2 Windows

Spec: §6 — Unix-Domain-Sockets sind Unix-spezifisch. Auf Windows übersetzt der Crate zu einer leeren Bibliothek; Konsumenten schalten ihre UDS-Nutzung mit #[cfg(unix)]. Die naheliegende Analogie wären Named Pipes — ein anderes Protokoll mit anderer Adressierung, das nicht Teil dieser Spec ist.

Repo: —

Tests: —

Status: open — auf Windows übersetzt der Crate zu einer leeren Bibliothek (crates/transport-uds/src/lib.rs: #![cfg(unix)]). Windows bietet seit Windows 10 (Build 17063) Sockets der Familie AF_UNIX an (Angabe von Microsoft, hier nicht geprüft); der Transport nutzt sie nicht.

§6.3 Cross-Vendor-Interop

Spec: §6 — UDS ist Intra-Container- und Intra-Host-IPC. Cross-Vendor- Interop mit Cyclone DDS oder FastDDS bleibt in der UDP-, TCP- und SHM-Domäne; beide Vendoren haben keinen UDS-Transport, mit dem sich ein Wire-Vergleich führen ließe. Die Sektion hält den Scope fest und stellt keine Implementierungs-Anforderung.

Repo: —

Tests: —

Status: n/a (informative) — weder Cyclone DDS noch FastDDS hat einen UDS-Transport, gegen den ein Wire-Vergleich möglich wäre.


Audit-Status

14 done / 1 partial / 2 open / 0 blocked / 2 n/a (informative).

Test-Lauf: cargo test -p zerodds-transport-uds — auf macOS 15 (Rust-Host aarch64-apple-darwin) 20 Tests (lib) + 1 Test (tests/l1_cross_process.rs) grün, 0 failed. Der #[cfg(target_os = "linux")]-gated abstract_dgram-Modul fährt dort 0 Fälle; derselbe Befehl auf dem Linux-Testhost gegen die unveränderte abstract_dgram.rs — 11 abstract_dgram::tests-Fälle grün, 0 failed.

Offene Punkte: zerodds-uds-transport-1.0.open.md.