Skip to main content

zerodds_discovery/security/
codec.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Wire-Codec fuer `ParticipantGenericMessage` (Spec §7.5.5).
4//!
5//! Identische Semantik wie `zerodds_security_runtime::builtin_topics`: 4-Byte
6//! CDR-LE-Encapsulation-Header gefolgt vom XCDR1-Body. Wir reimplementieren
7//! es hier inline, damit `zerodds-discovery` nicht von `zerodds-security-runtime`
8//! abhaengt (Cargo-Zyklus mit den Dev-Dep-Tests in security-runtime).
9
10extern crate alloc;
11use alloc::vec::Vec;
12
13use zerodds_security::error::{SecurityError, SecurityErrorKind, SecurityResult};
14use zerodds_security::generic_message::ParticipantGenericMessage;
15
16/// CDR-LE-Encapsulation-Kind (Spec RTPS 2.5 §10.2).
17pub const ENCAPSULATION_CDR_LE: [u8; 2] = [0x00, 0x01];
18
19/// Encapsulation-Header-Laenge (Spec §10.1: 2 byte kind + 2 byte options).
20pub const ENCAPSULATION_HEADER_LEN: usize = 4;
21
22/// Encoded eine `ParticipantGenericMessage` als `serialized_payload`-
23/// Bytes fuer eine DATA-Submessage.
24#[must_use]
25pub fn encode_generic_message(msg: &ParticipantGenericMessage) -> Vec<u8> {
26    let body = msg.to_cdr_le();
27    let mut out = Vec::with_capacity(ENCAPSULATION_HEADER_LEN + body.len());
28    out.extend_from_slice(&ENCAPSULATION_CDR_LE);
29    out.extend_from_slice(&[0, 0]); // options (Spec: must be 0)
30    out.extend_from_slice(&body);
31    out
32}
33
34/// Decoded eine `ParticipantGenericMessage` aus `serialized_payload`-
35/// Bytes (mit 4-byte Encapsulation-Header).
36///
37/// # Errors
38/// `BadArgument` wenn der Encapsulation-Header fehlt oder ein anderes
39/// Kind als CDR_LE / CDR_BE traegt; CDR-Decode-Fehler werden
40/// durchgereicht.
41pub fn decode_generic_message(bytes: &[u8]) -> SecurityResult<ParticipantGenericMessage> {
42    if bytes.len() < ENCAPSULATION_HEADER_LEN {
43        return Err(SecurityError::new(
44            SecurityErrorKind::BadArgument,
45            "generic_message: encapsulation-header truncated",
46        ));
47    }
48    let kind = [bytes[0], bytes[1]];
49    if kind != ENCAPSULATION_CDR_LE && kind != [0x00, 0x00] {
50        return Err(SecurityError::new(
51            SecurityErrorKind::BadArgument,
52            "generic_message: only CDR_LE encapsulation supported",
53        ));
54    }
55    ParticipantGenericMessage::from_cdr_le(&bytes[ENCAPSULATION_HEADER_LEN..])
56}
57
58#[cfg(test)]
59#[allow(clippy::expect_used, clippy::unwrap_used)]
60mod tests {
61    use super::*;
62    use zerodds_security::generic_message::{MessageIdentity, class_id};
63    use zerodds_security::token::DataHolder;
64
65    fn sample() -> ParticipantGenericMessage {
66        ParticipantGenericMessage {
67            message_identity: MessageIdentity {
68                source_guid: [0xAA; 16],
69                sequence_number: 7,
70            },
71            related_message_identity: MessageIdentity::default(),
72            destination_participant_key: [0xBB; 16],
73            destination_endpoint_key: [0; 16],
74            source_endpoint_key: [0xCC; 16],
75            message_class_id: class_id::AUTH_REQUEST.into(),
76            message_data: alloc::vec![DataHolder::new("DDS:Auth:PKI-DH:1.2+AuthReq")],
77        }
78    }
79
80    #[test]
81    fn roundtrip_preserves_message() {
82        let msg = sample();
83        let bytes = encode_generic_message(&msg);
84        let back = decode_generic_message(&bytes).unwrap();
85        assert_eq!(msg, back);
86    }
87
88    #[test]
89    fn encode_starts_with_cdr_le_encapsulation() {
90        let bytes = encode_generic_message(&sample());
91        assert_eq!(&bytes[..4], &[0x00, 0x01, 0x00, 0x00]);
92    }
93
94    #[test]
95    fn decode_rejects_truncated_header() {
96        let err = decode_generic_message(&[0x00, 0x01]).unwrap_err();
97        assert_eq!(err.kind, SecurityErrorKind::BadArgument);
98    }
99
100    #[test]
101    fn decode_rejects_unknown_encapsulation() {
102        let err = decode_generic_message(&[0x00, 0x99, 0, 0, 0, 0, 0, 0]).unwrap_err();
103        assert_eq!(err.kind, SecurityErrorKind::BadArgument);
104    }
105}