Skip to main content

zerodds_qos/policies/
reliability.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! ReliabilityQosPolicy (DDS 1.4 §2.2.3.14).
4//!
5//! Wire-Format (DDSI-RTPS §9.6.3.2): u32 kind + Duration max_blocking_time
6//! = 4 + 8 = 12 byte.
7
8use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
9
10use crate::duration::Duration;
11
12/// Reliability-Kind (DDS 1.4 §2.2.3.14).
13///
14/// Reihenfolge gem. §2.2.3 Table "QoS compatibility":
15/// `BestEffort < Reliable`. `offered.kind >= requested.kind`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17#[repr(u32)]
18pub enum ReliabilityKind {
19    /// BestEffort (DDSI-RTPS §8.4.2).
20    BestEffort = 1,
21    /// Reliable (mit HB+ACKNACK-Flow).
22    Reliable = 2,
23}
24
25impl Default for ReliabilityKind {
26    /// DDS 1.4 §2.2.3.14.3: Default je nach Entity — Reader: BestEffort,
27    /// Writer: Reliable. Wir whalen hier den Reader-Default; Writer setzt
28    /// `ReliabilityKind::Reliable` explizit.
29    fn default() -> Self {
30        Self::BestEffort
31    }
32}
33
34impl ReliabilityKind {
35    /// Strikter Mapper.
36    #[must_use]
37    pub const fn try_from_u32(v: u32) -> Option<Self> {
38        match v {
39            1 => Some(Self::BestEffort),
40            2 => Some(Self::Reliable),
41            _ => None,
42        }
43    }
44
45    /// Forward-kompatibler Mapper (unbekannt → BestEffort).
46    #[must_use]
47    pub const fn from_u32(v: u32) -> Self {
48        match v {
49            2 => Self::Reliable,
50            _ => Self::BestEffort,
51        }
52    }
53}
54
55/// ReliabilityQosPolicy.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct ReliabilityQosPolicy {
58    /// Kind.
59    pub kind: ReliabilityKind,
60    /// Max-Blocking-Time — **Writer-only semantic**. Auf Reader-Seite
61    /// wird der Wert zwar serialisiert (Spec-Wire-Format), aber ignoriert;
62    /// DDS-Reader muessen Peer-Wert nicht interpretieren. Cyclone und
63    /// Fast-DDS verhalten sich gleich — byte-wise identisch, semantisch
64    /// Reader-no-op.
65    pub max_blocking_time: Duration,
66}
67
68impl Default for ReliabilityQosPolicy {
69    fn default() -> Self {
70        Self {
71            kind: ReliabilityKind::BestEffort,
72            // Spec-default fuer Reliable-Writer: 100ms.
73            max_blocking_time: Duration::from_millis(100),
74        }
75    }
76}
77
78impl ReliabilityQosPolicy {
79    /// Wire-Encoding: u32 kind + i32 seconds + u32 fraction = 12 byte.
80    ///
81    /// # Errors
82    /// Buffer-Overflow.
83    pub fn encode_into(self, w: &mut BufferWriter) -> Result<(), EncodeError> {
84        w.write_u32(self.kind as u32)?;
85        self.max_blocking_time.encode_into(w)
86    }
87
88    /// Wire-Decoding (strict). Unbekannter Discriminator → `InvalidEnum`.
89    ///
90    /// # Errors
91    /// Buffer-Underflow oder unbekannter Kind-Wert.
92    pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
93        let v = r.read_u32()?;
94        let kind = ReliabilityKind::try_from_u32(v).ok_or(DecodeError::InvalidEnum {
95            kind: "ReliabilityKind",
96            value: v,
97        })?;
98        let max_blocking_time = Duration::decode_from(r)?;
99        Ok(Self {
100            kind,
101            max_blocking_time,
102        })
103    }
104}
105
106#[cfg(test)]
107#[allow(clippy::unwrap_used)]
108mod tests {
109    use super::*;
110    use zerodds_cdr::Endianness;
111
112    #[test]
113    fn best_effort_lt_reliable() {
114        assert!(ReliabilityKind::BestEffort < ReliabilityKind::Reliable);
115    }
116
117    #[test]
118    fn try_from_u32_is_strict() {
119        assert_eq!(ReliabilityKind::try_from_u32(0), None);
120        assert_eq!(
121            ReliabilityKind::try_from_u32(1),
122            Some(ReliabilityKind::BestEffort)
123        );
124        assert_eq!(
125            ReliabilityKind::try_from_u32(2),
126            Some(ReliabilityKind::Reliable)
127        );
128        assert_eq!(ReliabilityKind::try_from_u32(3), None);
129    }
130
131    #[test]
132    fn encode_decode_roundtrip() {
133        let p = ReliabilityQosPolicy {
134            kind: ReliabilityKind::Reliable,
135            max_blocking_time: Duration::from_millis(250),
136        };
137        let mut w = BufferWriter::new(Endianness::Little);
138        p.encode_into(&mut w).unwrap();
139        let bytes = w.into_bytes();
140        assert_eq!(bytes.len(), 12);
141        let mut r = BufferReader::new(&bytes, Endianness::Little);
142        assert_eq!(ReliabilityQosPolicy::decode_from(&mut r).unwrap(), p);
143    }
144
145    #[test]
146    fn default_is_best_effort_100ms() {
147        let d = ReliabilityQosPolicy::default();
148        assert_eq!(d.kind, ReliabilityKind::BestEffort);
149        assert_eq!(d.max_blocking_time, Duration::from_millis(100));
150    }
151
152    /// Forward-kompatibler Mapper: unbekannt -> `BestEffort` (defensive).
153    #[test]
154    fn from_u32_forward_compatible() {
155        assert_eq!(ReliabilityKind::from_u32(1), ReliabilityKind::BestEffort);
156        assert_eq!(ReliabilityKind::from_u32(2), ReliabilityKind::Reliable);
157        // 0 ist NICHT Best-Effort auf Wire-Ebene (DDSI-RTPS nutzt 1/2),
158        // forward-compat collapiert aber auf BestEffort.
159        assert_eq!(ReliabilityKind::from_u32(0), ReliabilityKind::BestEffort);
160        assert_eq!(ReliabilityKind::from_u32(99), ReliabilityKind::BestEffort);
161    }
162
163    /// Roundtrip der BestEffort-Variante inkl. non-default
164    /// max_blocking_time (Report: Variante bisher ungetestet).
165    #[test]
166    fn best_effort_roundtrip_with_custom_blocking() {
167        let p = ReliabilityQosPolicy {
168            kind: ReliabilityKind::BestEffort,
169            max_blocking_time: Duration::from_millis(2500),
170        };
171        let mut w = BufferWriter::new(Endianness::Little);
172        p.encode_into(&mut w).unwrap();
173        let bytes = w.into_bytes();
174        assert_eq!(bytes.len(), 12);
175        // Wire-Format-Spec-Check: erster u32 ist kind=1 (little-endian).
176        assert_eq!(&bytes[0..4], &[1u8, 0, 0, 0]);
177        let mut r = BufferReader::new(&bytes, Endianness::Little);
178        assert_eq!(ReliabilityQosPolicy::decode_from(&mut r).unwrap(), p);
179    }
180
181    /// Debug-Format enthaelt beide Varianten und die Duration.
182    #[test]
183    fn debug_and_clone_work() {
184        let p = ReliabilityQosPolicy {
185            kind: ReliabilityKind::Reliable,
186            max_blocking_time: Duration::from_secs(1),
187        };
188        #[allow(clippy::clone_on_copy)]
189        let p2 = p.clone();
190        assert_eq!(p, p2);
191        let dbg = alloc::format!("{p:?}");
192        assert!(dbg.contains("Reliable"), "debug: {dbg}");
193    }
194
195    /// Decode mit unbekanntem kind-Discriminator → `InvalidEnum` Fehler.
196    #[test]
197    fn decode_unknown_kind_errors() {
198        let mut w = BufferWriter::new(Endianness::Little);
199        w.write_u32(5).unwrap();
200        Duration::ZERO.encode_into(&mut w).unwrap();
201        let bytes = w.into_bytes();
202        let mut r = BufferReader::new(&bytes, Endianness::Little);
203        let err = ReliabilityQosPolicy::decode_from(&mut r).unwrap_err();
204        assert!(matches!(
205            err,
206            zerodds_cdr::DecodeError::InvalidEnum {
207                kind: "ReliabilityKind",
208                value: 5
209            }
210        ));
211    }
212
213    /// Decode bei kurzem Buffer (kind=Reliable, keine blocking-time) →
214    /// short-read error auf Duration-Decode.
215    #[test]
216    fn decode_short_buffer_no_duration_errors() {
217        let mut w = BufferWriter::new(Endianness::Little);
218        w.write_u32(2).unwrap(); // kind=Reliable
219        let bytes = w.into_bytes();
220        let mut r = BufferReader::new(&bytes, Endianness::Little);
221        assert!(ReliabilityQosPolicy::decode_from(&mut r).is_err());
222    }
223
224    /// Spec-Ordering: BestEffort < Reliable → `offered >= requested`
225    /// Compatibility-Logik baut darauf auf.
226    #[test]
227    fn ordering_reliable_higher_than_besteffort() {
228        assert!(ReliabilityKind::Reliable > ReliabilityKind::BestEffort);
229    }
230}