Skip to main content

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}