Skip to main content

zerodds_xrce/
header.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! XRCE Message-Header (Spec §8.3.2).
5//!
6//! ```text
7//!   0                   1                   2                   3
8//!   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
9//!  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
10//!  |  session_id   |  stream_id    |        sequence_nr            |
11//!  +---------------+---------------+---------------+---------------+
12//!  |                                                               |
13//!  |              client_key (4 bytes, optional)                   |
14//!  +---------------+---------------+---------------+---------------+
15//! ```
16//!
17//! - `sequence_nr` ist Little-Endian (Spec §8.3.2.3).
18//! - `client_key` ist nur present, wenn `session_id <= 127`
19//!   (§8.3.2.1).
20//!
21//! Reservierte Werte:
22//! - `SESSION_ID_NONE_WITH_CLIENT_KEY = 0x00`
23//! - `SESSION_ID_NONE_WITHOUT_CLIENT_KEY = 0x80`
24
25use crate::error::XrceError;
26use crate::serial_number::SerialNumber16;
27
28/// Laenge des `ClientKey`-Feldes in Bytes.
29pub const CLIENT_KEY_LEN: usize = 4;
30
31/// Reservierte session_id ohne ProxyClient-Bindung, mit ClientKey
32/// im Header (Spec §8.3.2.1).
33pub const SESSION_ID_NONE_WITH_CLIENT_KEY: u8 = 0x00;
34
35/// Reservierte session_id ohne ProxyClient-Bindung, ohne ClientKey
36/// im Header — Authentifizierung via Source-Address (Spec §8.3.2.1).
37pub const SESSION_ID_NONE_WITHOUT_CLIENT_KEY: u8 = 0x80;
38
39/// `SessionId`. Werte 0..=127 → ClientKey im Header; 128..=255 →
40/// ohne ClientKey.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
42pub struct SessionId(pub u8);
43
44impl SessionId {
45    /// `true`, wenn der MessageHeader das `client_key`-Feld traegt.
46    #[must_use]
47    pub fn carries_client_key(self) -> bool {
48        self.0 <= 127
49    }
50}
51
52/// `StreamId`. Werte:
53/// - `0` = `STREAMID_NONE` (Spec §8.3.5.11/12: ACKNACK/HEARTBEAT/
54///   TIMESTAMP nutzen den NONE-Stream).
55/// - `1..=127` = best-effort.
56/// - `128..=255` = reliable.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
58pub struct StreamId(pub u8);
59
60impl StreamId {
61    /// `STREAMID_NONE`.
62    pub const NONE: Self = Self(0);
63    /// `STREAMID_BUILTIN_BEST_EFFORTS` (Spec §8.3.2.2).
64    pub const BUILTIN_BEST_EFFORT: Self = Self(0x01);
65    /// `STREAMID_BUILTIN_RELIABLE` (Spec §8.3.2.2).
66    pub const BUILTIN_RELIABLE: Self = Self(0x80);
67
68    /// `true` fuer reliable Streams (id >= 128).
69    #[must_use]
70    pub fn is_reliable(self) -> bool {
71        self.0 >= 128
72    }
73
74    /// `true` fuer best-effort Streams (1..=127).
75    #[must_use]
76    pub fn is_best_effort(self) -> bool {
77        self.0 >= 1 && self.0 <= 127
78    }
79
80    /// `true` fuer `STREAMID_NONE`.
81    #[must_use]
82    pub fn is_none(self) -> bool {
83        self.0 == 0
84    }
85}
86
87/// 4-Byte ClientKey (Spec §7.7 ClientKey).
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
89pub struct ClientKey(pub [u8; CLIENT_KEY_LEN]);
90
91impl ClientKey {
92    /// `CLIENTKEY_INVALID` per §7.8.2.2 — alle Bytes 0.
93    pub const INVALID: Self = Self([0; CLIENT_KEY_LEN]);
94
95    /// Roher Slice.
96    #[must_use]
97    pub fn as_bytes(&self) -> &[u8; CLIENT_KEY_LEN] {
98        &self.0
99    }
100}
101
102/// XRCE Message-Header.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
104pub struct MessageHeader {
105    /// Session-Identifier; entscheidet ob `client_key` present ist.
106    pub session_id: SessionId,
107    /// Stream-Identifier (best-effort/reliable/none).
108    pub stream_id: StreamId,
109    /// RFC-1982 16-bit Serial Number (Little-Endian on wire).
110    pub sequence_nr: SerialNumber16,
111    /// Optionaler ClientKey — nur bei `session_id <= 127`.
112    pub client_key: Option<ClientKey>,
113}
114
115impl MessageHeader {
116    /// Wire-Size **ohne** ClientKey: 4 Bytes.
117    pub const WIRE_SIZE_NO_KEY: usize = 4;
118    /// Wire-Size **mit** ClientKey: 8 Bytes.
119    pub const WIRE_SIZE_WITH_KEY: usize = 4 + CLIENT_KEY_LEN;
120
121    /// Konstruktor ohne ClientKey (session_id muss >=128 sein).
122    ///
123    /// # Errors
124    /// `ValueOutOfRange`, wenn `session_id <= 127` waere — der Header
125    /// muesste dann einen ClientKey tragen.
126    pub fn without_client_key(
127        session_id: SessionId,
128        stream_id: StreamId,
129        sequence_nr: SerialNumber16,
130    ) -> Result<Self, XrceError> {
131        if session_id.carries_client_key() {
132            return Err(XrceError::ValueOutOfRange {
133                message: "session_id <=127 requires client_key",
134            });
135        }
136        Ok(Self {
137            session_id,
138            stream_id,
139            sequence_nr,
140            client_key: None,
141        })
142    }
143
144    /// Konstruktor mit ClientKey (session_id muss <=127 sein).
145    ///
146    /// # Errors
147    /// `ValueOutOfRange`, wenn `session_id >= 128` waere.
148    pub fn with_client_key(
149        session_id: SessionId,
150        stream_id: StreamId,
151        sequence_nr: SerialNumber16,
152        client_key: ClientKey,
153    ) -> Result<Self, XrceError> {
154        if !session_id.carries_client_key() {
155            return Err(XrceError::ValueOutOfRange {
156                message: "session_id >=128 must not carry client_key",
157            });
158        }
159        Ok(Self {
160            session_id,
161            stream_id,
162            sequence_nr,
163            client_key: Some(client_key),
164        })
165    }
166
167    /// Wire-Size in Bytes (4 oder 8).
168    #[must_use]
169    pub fn wire_size(&self) -> usize {
170        if self.client_key.is_some() {
171            Self::WIRE_SIZE_WITH_KEY
172        } else {
173            Self::WIRE_SIZE_NO_KEY
174        }
175    }
176
177    /// Encodiert den Header in den gegebenen Buffer und liefert die
178    /// geschriebene Anzahl Bytes.
179    ///
180    /// # Errors
181    /// `WriteOverflow`, wenn `out.len() < wire_size()`.
182    pub fn write_to(&self, out: &mut [u8]) -> Result<usize, XrceError> {
183        let needed = self.wire_size();
184        if out.len() < needed {
185            return Err(XrceError::WriteOverflow {
186                needed,
187                available: out.len(),
188            });
189        }
190        out[0] = self.session_id.0;
191        out[1] = self.stream_id.0;
192        let seq = self.sequence_nr.raw().to_le_bytes();
193        out[2] = seq[0];
194        out[3] = seq[1];
195        if let Some(ck) = self.client_key {
196            out[4..8].copy_from_slice(&ck.0);
197        }
198        Ok(needed)
199    }
200
201    /// Decodiert einen Header aus einem Slice. Liefert
202    /// `(header, bytes_consumed)`.
203    ///
204    /// # Errors
205    /// `UnexpectedEof`, wenn `bytes` zu kurz fuer die je nach
206    /// `session_id` gewaehlte Variante ist.
207    pub fn read_from(bytes: &[u8]) -> Result<(Self, usize), XrceError> {
208        if bytes.len() < Self::WIRE_SIZE_NO_KEY {
209            return Err(XrceError::UnexpectedEof {
210                needed: Self::WIRE_SIZE_NO_KEY,
211                offset: 0,
212            });
213        }
214        let session_id = SessionId(bytes[0]);
215        let stream_id = StreamId(bytes[1]);
216        let mut seq_buf = [0u8; 2];
217        seq_buf.copy_from_slice(&bytes[2..4]);
218        let sequence_nr = SerialNumber16::new(u16::from_le_bytes(seq_buf));
219
220        if session_id.carries_client_key() {
221            if bytes.len() < Self::WIRE_SIZE_WITH_KEY {
222                return Err(XrceError::UnexpectedEof {
223                    needed: Self::WIRE_SIZE_WITH_KEY,
224                    offset: bytes.len(),
225                });
226            }
227            let mut ck = [0u8; CLIENT_KEY_LEN];
228            ck.copy_from_slice(&bytes[4..8]);
229            Ok((
230                Self {
231                    session_id,
232                    stream_id,
233                    sequence_nr,
234                    client_key: Some(ClientKey(ck)),
235                },
236                Self::WIRE_SIZE_WITH_KEY,
237            ))
238        } else {
239            Ok((
240                Self {
241                    session_id,
242                    stream_id,
243                    sequence_nr,
244                    client_key: None,
245                },
246                Self::WIRE_SIZE_NO_KEY,
247            ))
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    #![allow(clippy::expect_used, clippy::unwrap_used)]
255    use super::*;
256
257    #[test]
258    fn session_id_127_carries_client_key() {
259        assert!(SessionId(0).carries_client_key());
260        assert!(SessionId(127).carries_client_key());
261        assert!(!SessionId(128).carries_client_key());
262        assert!(!SessionId(255).carries_client_key());
263    }
264
265    #[test]
266    fn stream_id_classification() {
267        assert!(StreamId::NONE.is_none());
268        assert!(StreamId::BUILTIN_BEST_EFFORT.is_best_effort());
269        assert!(StreamId::BUILTIN_RELIABLE.is_reliable());
270        assert!(StreamId(127).is_best_effort());
271        assert!(StreamId(128).is_reliable());
272        assert!(StreamId(255).is_reliable());
273    }
274
275    #[test]
276    fn header_no_client_key_wire_size_4() {
277        let h = MessageHeader::without_client_key(
278            SessionId(SESSION_ID_NONE_WITHOUT_CLIENT_KEY),
279            StreamId::NONE,
280            SerialNumber16::new(0),
281        )
282        .unwrap();
283        assert_eq!(h.wire_size(), 4);
284    }
285
286    #[test]
287    fn header_with_client_key_wire_size_8() {
288        let h = MessageHeader::with_client_key(
289            SessionId(0),
290            StreamId::BUILTIN_RELIABLE,
291            SerialNumber16::new(0),
292            ClientKey([1, 2, 3, 4]),
293        )
294        .unwrap();
295        assert_eq!(h.wire_size(), 8);
296    }
297
298    #[test]
299    fn header_constructor_rejects_inconsistent_no_key() {
300        // session_id=10 (<=127) waere mit client_key — without_client_key muss fail
301        let res = MessageHeader::without_client_key(
302            SessionId(10),
303            StreamId::NONE,
304            SerialNumber16::new(0),
305        );
306        assert!(res.is_err());
307    }
308
309    #[test]
310    fn header_constructor_rejects_inconsistent_with_key() {
311        // session_id=200 (>=128) waere ohne client_key — with_client_key muss fail
312        let res = MessageHeader::with_client_key(
313            SessionId(200),
314            StreamId::NONE,
315            SerialNumber16::new(0),
316            ClientKey::INVALID,
317        );
318        assert!(res.is_err());
319    }
320
321    #[test]
322    fn header_layout_bytes_no_key() {
323        let h = MessageHeader::without_client_key(
324            SessionId(0x80),
325            StreamId(0x42),
326            SerialNumber16::new(0x1234),
327        )
328        .unwrap();
329        let mut buf = [0u8; 4];
330        let n = h.write_to(&mut buf).unwrap();
331        assert_eq!(n, 4);
332        assert_eq!(buf[0], 0x80);
333        assert_eq!(buf[1], 0x42);
334        // sequence_nr LE
335        assert_eq!(buf[2], 0x34);
336        assert_eq!(buf[3], 0x12);
337    }
338
339    #[test]
340    fn header_layout_bytes_with_key() {
341        let h = MessageHeader::with_client_key(
342            SessionId(0x10),
343            StreamId(0x80),
344            SerialNumber16::new(0xABCD),
345            ClientKey([0xAA, 0xBB, 0xCC, 0xDD]),
346        )
347        .unwrap();
348        let mut buf = [0u8; 8];
349        h.write_to(&mut buf).unwrap();
350        assert_eq!(buf[0], 0x10);
351        assert_eq!(buf[1], 0x80);
352        assert_eq!(buf[2], 0xCD);
353        assert_eq!(buf[3], 0xAB);
354        assert_eq!(&buf[4..8], &[0xAA, 0xBB, 0xCC, 0xDD]);
355    }
356
357    #[test]
358    fn header_roundtrip_no_key() {
359        let h = MessageHeader::without_client_key(
360            SessionId(SESSION_ID_NONE_WITHOUT_CLIENT_KEY),
361            StreamId::BUILTIN_BEST_EFFORT,
362            SerialNumber16::new(7),
363        )
364        .unwrap();
365        let mut buf = [0u8; 4];
366        h.write_to(&mut buf).unwrap();
367        let (decoded, n) = MessageHeader::read_from(&buf).unwrap();
368        assert_eq!(n, 4);
369        assert_eq!(decoded, h);
370    }
371
372    #[test]
373    fn header_roundtrip_with_key() {
374        let h = MessageHeader::with_client_key(
375            SessionId(0x55),
376            StreamId::BUILTIN_RELIABLE,
377            SerialNumber16::new(0xFFFE),
378            ClientKey([9, 8, 7, 6]),
379        )
380        .unwrap();
381        let mut buf = [0u8; 8];
382        h.write_to(&mut buf).unwrap();
383        let (decoded, n) = MessageHeader::read_from(&buf).unwrap();
384        assert_eq!(n, 8);
385        assert_eq!(decoded, h);
386    }
387
388    #[test]
389    fn header_decode_truncated_no_key() {
390        let buf = [0x80u8, 0x01, 0x00]; // 3 Byte
391        let res = MessageHeader::read_from(&buf);
392        assert!(matches!(
393            res,
394            Err(XrceError::UnexpectedEof { needed: 4, .. })
395        ));
396    }
397
398    #[test]
399    fn header_decode_truncated_with_key() {
400        // session_id=0 (<=127) → braucht 8 Byte
401        let buf = [0x00u8, 0x01, 0x00, 0x00, 0xAA]; // 5 Byte
402        let res = MessageHeader::read_from(&buf);
403        assert!(matches!(
404            res,
405            Err(XrceError::UnexpectedEof { needed: 8, .. })
406        ));
407    }
408
409    #[test]
410    fn header_write_overflow_when_buffer_too_small() {
411        let h = MessageHeader::with_client_key(
412            SessionId(0),
413            StreamId::NONE,
414            SerialNumber16::new(0),
415            ClientKey::INVALID,
416        )
417        .unwrap();
418        let mut buf = [0u8; 4]; // braucht 8
419        let res = h.write_to(&mut buf);
420        assert!(matches!(
421            res,
422            Err(XrceError::WriteOverflow {
423                needed: 8,
424                available: 4
425            })
426        ));
427    }
428
429    #[test]
430    fn header_extra_trailing_bytes_are_ignored() {
431        let h = MessageHeader::without_client_key(
432            SessionId(0xFF),
433            StreamId(2),
434            SerialNumber16::new(42),
435        )
436        .unwrap();
437        let mut buf = [0u8; 16];
438        h.write_to(&mut buf).unwrap();
439        let (decoded, n) = MessageHeader::read_from(&buf).unwrap();
440        assert_eq!(decoded, h);
441        assert_eq!(n, 4);
442    }
443}