zerodds_transport/lib.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Crate `zerodds-transport`. Safety classification: **SAFE**.
4//!
5//! Transport-Trait + Locator-Re-Export + abstrakte send/receive-
6//! Schnittstelle. Pure-Rust no_std + alloc, `forbid(unsafe_code)`.
7//!
8//! ## Spec
9//!
10//! - **DDSI-RTPS 2.5** §8.3.2 — Locator (re-exportiert aus
11//! `zerodds-rtps::wire_types::Locator`).
12//! - ZeroDDS-eigenes [`Transport`]-Trait — abstrakte Schicht zwischen
13//! RTPS-State-Machines und konkreten Wire-Protokollen.
14//!
15//! ## Schichten-Position
16//!
17//! Layer 2 — Wire (Trait-Crate). Implementations: `zerodds-transport-udp`,
18//! `zerodds-transport-tcp`, `zerodds-transport-shm`, `zerodds-transport-uds`,
19//! `zerodds-transport-tsn`. Direkte Konsumenten: `zerodds-dcps`,
20//! `zerodds-discovery`.
21//!
22//! ## Public API (Stand 1.0.0-rc.1)
23//!
24//! - [`Transport`] — Trait für send/receive-Operationen mit Locator-
25//! Adressierung.
26//! - [`SendError`] / [`RecvError`] — typisierte Fehler.
27//! - [`ReceivedDatagram`] — Empfangenes Datagramm + Source-Locator.
28//! - `Locator` — re-exportiert aus `zerodds-rtps::wire_types`
29//! (Spec-Anker DDSI-RTPS §8.3.2).
30//!
31//! ## Architektur-Hinweis
32//!
33//! `Locator` lebt **bewusst** in `zerodds-rtps` (DDSI-RTPS-Spec definiert
34//! das Wire-Format dort) und wird via `pub use` re-exportiert. Die
35//! resultierende `transport → rtps` Crate-Dep ist ZeroDDS-deliberat
36//! und stellt keinen Layer-Inversion-Bug dar — RTPS-Wire-Format-Types
37//! gehören in die rtps-Crate, das Transport-Trait abstrahiert das
38//! send/receive darüber.
39
40#![no_std]
41#![forbid(unsafe_code)]
42#![warn(missing_docs)]
43
44#[cfg(feature = "alloc")]
45extern crate alloc;
46
47#[cfg(feature = "std")]
48extern crate std;
49
50use core::fmt;
51
52pub use zerodds_rtps::wire_types::Locator;
53
54#[cfg(feature = "alloc")]
55use alloc::vec::Vec;
56
57/// Fehler beim Senden ueber einen Transport.
58#[derive(Debug, Clone, PartialEq, Eq)]
59#[non_exhaustive]
60pub enum SendError {
61 /// Datagram zu gross fuer den Pfad-MTU.
62 PayloadTooLarge {
63 /// Tatsaechliche Datagram-Laenge.
64 size: usize,
65 /// Konfiguriertes Limit.
66 limit: usize,
67 },
68 /// Locator kann von diesem Transport nicht bedient werden
69 /// (z.B. UDPv6-Locator an einen UDPv4-only-Transport).
70 UnsupportedLocator,
71 /// I/O-Fehler bei der zugrundeliegenden Operation. Detail-Text
72 /// transport-spezifisch.
73 Io {
74 /// Beschreibung.
75 message: &'static str,
76 },
77}
78
79impl fmt::Display for SendError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::PayloadTooLarge { size, limit } => {
83 write!(f, "transport payload too large: {size} > {limit}")
84 }
85 Self::UnsupportedLocator => f.write_str("transport: unsupported locator"),
86 Self::Io { message } => write!(f, "transport I/O error: {message}"),
87 }
88 }
89}
90
91#[cfg(feature = "std")]
92impl std::error::Error for SendError {}
93
94/// Fehler beim Empfangen.
95#[derive(Debug, Clone, PartialEq, Eq)]
96#[non_exhaustive]
97pub enum RecvError {
98 /// Recv-Operation lief in Timeout.
99 Timeout,
100 /// Empfangs-Buffer war zu klein. Datagramm wurde getruncated oder
101 /// verworfen — transport-spezifisch.
102 BufferTooSmall {
103 /// Datagram-Groesse, die nicht passte.
104 datagram_size: usize,
105 /// Buffer-Kapazitaet.
106 buffer_size: usize,
107 },
108 /// I/O-Fehler.
109 Io {
110 /// Beschreibung.
111 message: &'static str,
112 },
113}
114
115impl fmt::Display for RecvError {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self {
118 Self::Timeout => f.write_str("transport recv timeout"),
119 Self::BufferTooSmall {
120 datagram_size,
121 buffer_size,
122 } => {
123 write!(
124 f,
125 "recv buffer too small: datagram {datagram_size} > buffer {buffer_size}"
126 )
127 }
128 Self::Io { message } => write!(f, "transport I/O error: {message}"),
129 }
130 }
131}
132
133#[cfg(feature = "std")]
134impl std::error::Error for RecvError {}
135
136/// Empfangenes Datagram + Quell-Locator.
137#[cfg(feature = "alloc")]
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct ReceivedDatagram {
140 /// Quell-Locator (z.B. UDP-Quelladresse).
141 pub source: Locator,
142 /// Datagram-Bytes.
143 pub data: Vec<u8>,
144}
145
146/// Abstrakter Transport: sendet/empfaengt Datagramme zu/von Locatoren.
147///
148/// Implementations sind synchron und blocking. Async-Wrapper koennen
149/// in einem hoeheren Layer aufgesetzt werden.
150#[cfg(feature = "alloc")]
151pub trait Transport {
152 /// Sendet ein Datagram an den gegebenen Locator.
153 ///
154 /// # Errors
155 /// [`SendError`].
156 fn send(&self, dest: &Locator, data: &[u8]) -> Result<(), SendError>;
157
158 /// Empfaengt das naechste Datagram. Blocking; konkrete
159 /// Implementations koennen Timeout via internem Setting konfigurieren.
160 ///
161 /// # Errors
162 /// [`RecvError`].
163 fn recv(&self) -> Result<ReceivedDatagram, RecvError>;
164
165 /// Lokaler Locator (an dem dieser Transport empfangt).
166 fn local_locator(&self) -> Locator;
167}
168
169#[cfg(test)]
170mod tests {
171 #[cfg(feature = "alloc")]
172 extern crate alloc;
173 #[cfg(feature = "alloc")]
174 use alloc::format;
175
176 use super::*;
177
178 #[cfg(feature = "alloc")]
179 #[test]
180 fn send_error_display_payload_too_large() {
181 let e = SendError::PayloadTooLarge {
182 size: 70_000,
183 limit: 65_535,
184 };
185 let s = format!("{e}");
186 assert!(s.contains("70000"));
187 assert!(s.contains("65535"));
188 }
189
190 #[cfg(feature = "alloc")]
191 #[test]
192 fn send_error_display_unsupported_locator() {
193 let s = format!("{}", SendError::UnsupportedLocator);
194 assert!(s.contains("unsupported"));
195 }
196
197 #[cfg(feature = "alloc")]
198 #[test]
199 fn send_error_display_io() {
200 let s = format!(
201 "{}",
202 SendError::Io {
203 message: "ENETDOWN"
204 }
205 );
206 assert!(s.contains("ENETDOWN"));
207 }
208
209 #[cfg(feature = "alloc")]
210 #[test]
211 fn recv_error_display_timeout() {
212 let s = format!("{}", RecvError::Timeout);
213 assert!(s.contains("timeout"));
214 }
215
216 #[cfg(feature = "alloc")]
217 #[test]
218 fn recv_error_display_buffer_too_small() {
219 let s = format!(
220 "{}",
221 RecvError::BufferTooSmall {
222 datagram_size: 1500,
223 buffer_size: 1024
224 }
225 );
226 assert!(s.contains("1500"));
227 assert!(s.contains("1024"));
228 }
229
230 #[cfg(feature = "alloc")]
231 #[test]
232 fn received_datagram_is_constructable() {
233 let r = ReceivedDatagram {
234 source: Locator::INVALID,
235 data: alloc::vec![1, 2, 3],
236 };
237 assert_eq!(r.data, alloc::vec![1, 2, 3]);
238 }
239}