zerodds_security_runtime/
builtin_topics.rs1use alloc::vec::Vec;
22
23use zerodds_qos::{
24 DurabilityKind, DurabilityQosPolicy, HistoryKind, HistoryQosPolicy, ReliabilityKind,
25 ReliabilityQosPolicy,
26};
27use zerodds_security::error::{SecurityError, SecurityErrorKind, SecurityResult};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct BuiltinTopicQos {
34 pub reliability: ReliabilityQosPolicy,
36 pub durability: DurabilityQosPolicy,
38 pub history: HistoryQosPolicy,
40}
41use zerodds_security::generic_message::ParticipantGenericMessage;
42
43pub const ENCAPSULATION_CDR_LE: [u8; 2] = [0x00, 0x01];
48
49pub const ENCAPSULATION_HEADER_LEN: usize = 4;
51
52#[must_use]
56pub fn encode_generic_message(msg: &ParticipantGenericMessage) -> Vec<u8> {
57 let body = msg.to_cdr_le();
58 let mut out = Vec::with_capacity(ENCAPSULATION_HEADER_LEN + body.len());
59 out.extend_from_slice(&ENCAPSULATION_CDR_LE);
60 out.extend_from_slice(&[0, 0]); out.extend_from_slice(&body);
62 out
63}
64
65pub fn decode_generic_message(bytes: &[u8]) -> SecurityResult<ParticipantGenericMessage> {
73 if bytes.len() < ENCAPSULATION_HEADER_LEN {
74 return Err(SecurityError::new(
75 SecurityErrorKind::BadArgument,
76 "generic_message: encapsulation-header truncated",
77 ));
78 }
79 let kind = [bytes[0], bytes[1]];
80 if kind != ENCAPSULATION_CDR_LE && kind != [0x00, 0x00] {
81 return Err(SecurityError::new(
82 SecurityErrorKind::BadArgument,
83 "generic_message: only CDR_LE encapsulation supported",
84 ));
85 }
86 ParticipantGenericMessage::from_cdr_le(&bytes[ENCAPSULATION_HEADER_LEN..])
88}
89
90#[must_use]
94pub fn stateless_message_qos() -> BuiltinTopicQos {
95 BuiltinTopicQos {
96 reliability: ReliabilityQosPolicy {
97 kind: ReliabilityKind::BestEffort,
98 ..ReliabilityQosPolicy::default()
99 },
100 durability: DurabilityQosPolicy {
101 kind: DurabilityKind::Volatile,
102 },
103 history: HistoryQosPolicy {
104 kind: HistoryKind::KeepAll,
105 depth: 0,
106 },
107 }
108}
109
110#[must_use]
113pub fn volatile_secure_qos() -> BuiltinTopicQos {
114 BuiltinTopicQos {
115 reliability: ReliabilityQosPolicy {
116 kind: ReliabilityKind::Reliable,
117 ..ReliabilityQosPolicy::default()
118 },
119 durability: DurabilityQosPolicy {
120 kind: DurabilityKind::Volatile,
121 },
122 history: HistoryQosPolicy {
123 kind: HistoryKind::KeepAll,
124 depth: 0,
125 },
126 }
127}
128
129#[cfg(test)]
130#[allow(clippy::expect_used, clippy::unwrap_used)]
131mod tests {
132 use super::*;
133 use zerodds_security::generic_message::{MessageIdentity, class_id};
134 use zerodds_security::token::DataHolder;
135
136 fn sample_msg() -> ParticipantGenericMessage {
137 ParticipantGenericMessage {
138 message_identity: MessageIdentity {
139 source_guid: [0xAA; 16],
140 sequence_number: 1,
141 },
142 related_message_identity: MessageIdentity::default(),
143 destination_participant_key: [0xBB; 16],
144 destination_endpoint_key: [0; 16],
145 source_endpoint_key: [0xCC; 16],
146 message_class_id: class_id::AUTH_REQUEST.to_string(),
147 message_data: vec![DataHolder::new("DDS:Auth:PKI-DH:1.2+AuthReq")],
148 }
149 }
150
151 #[test]
152 fn encode_starts_with_cdr_le_encapsulation() {
153 let msg = sample_msg();
154 let bytes = encode_generic_message(&msg);
155 assert_eq!(&bytes[..4], &[0x00, 0x01, 0x00, 0x00]);
156 }
157
158 #[test]
159 fn encode_decode_roundtrip() {
160 let msg = sample_msg();
161 let bytes = encode_generic_message(&msg);
162 let back = decode_generic_message(&bytes).unwrap();
163 assert_eq!(msg, back);
164 }
165
166 #[test]
167 fn decode_rejects_unknown_encapsulation() {
168 let bytes = vec![0x00, 0x99, 0, 0, 0, 0, 0, 0];
169 let err = decode_generic_message(&bytes).unwrap_err();
170 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
171 }
172
173 #[test]
174 fn decode_rejects_truncated() {
175 let bytes = vec![0x00, 0x01];
176 let err = decode_generic_message(&bytes).unwrap_err();
177 assert_eq!(err.kind, SecurityErrorKind::BadArgument);
178 }
179
180 #[test]
181 fn stateless_qos_is_best_effort_volatile_keep_all() {
182 let q = stateless_message_qos();
183 assert_eq!(q.reliability.kind, ReliabilityKind::BestEffort);
184 assert_eq!(q.durability.kind, DurabilityKind::Volatile);
185 assert_eq!(q.history.kind, HistoryKind::KeepAll);
186 }
187
188 #[test]
189 fn volatile_secure_qos_is_reliable_volatile_keep_all() {
190 let q = volatile_secure_qos();
191 assert_eq!(q.reliability.kind, ReliabilityKind::Reliable);
192 assert_eq!(q.durability.kind, DurabilityKind::Volatile);
193 assert_eq!(q.history.kind, HistoryKind::KeepAll);
194 }
195
196 #[test]
197 fn stateless_and_volatile_qos_differ() {
198 assert_ne!(
201 stateless_message_qos().reliability.kind,
202 volatile_secure_qos().reliability.kind
203 );
204 }
205
206 #[test]
207 fn full_handshake_token_through_bridge() {
208 let token = DataHolder::new("DDS:Auth:PKI-DH:1.2+AuthReq")
212 .with_property("c.dsign_algo", "ECDSA-SHA256")
213 .with_binary_property("c.id", vec![0x30, 0x82, 0x01, 0x23]);
214 let msg = ParticipantGenericMessage {
215 message_identity: MessageIdentity {
216 source_guid: [0xAA; 16],
217 sequence_number: 1,
218 },
219 destination_participant_key: [0xBB; 16],
220 source_endpoint_key: [0xCC; 16],
221 message_class_id: class_id::AUTH_REQUEST.to_string(),
222 message_data: vec![token.clone()],
223 ..ParticipantGenericMessage::default()
224 };
225 let wire = encode_generic_message(&msg);
226 let decoded = decode_generic_message(&wire).unwrap();
227 assert_eq!(decoded.message_data.len(), 1);
228 assert_eq!(decoded.message_data[0], token);
229 }
230}