1use alloc::vec::Vec;
26
27use zerodds_security::crypto::{CryptoHandle, CryptographicPlugin};
28
29use crate::codec::{SRTPS_POSTFIX, SRTPS_PREFIX, SecurityRtpsError};
30
31pub const RTPS_HEADER_LEN: usize = 20;
33
34const FLAG_LE: u8 = 0x01;
35
36pub const PRE_SHARED_KEY_FLAG: u8 = 0x02;
49
50fn push_header(out: &mut Vec<u8>, id: u8, length: u16) {
51 out.push(id);
52 out.push(FLAG_LE);
53 out.extend_from_slice(&length.to_le_bytes());
54}
55
56fn push_header_with_flags(out: &mut Vec<u8>, id: u8, flags: u8, length: u16) {
57 out.push(id);
58 out.push(flags);
59 out.extend_from_slice(&length.to_le_bytes());
60}
61
62pub fn encode_secured_rtps_message_psk(
69 plugin: &dyn CryptographicPlugin,
70 local: CryptoHandle,
71 remote_list: &[CryptoHandle],
72 message: &[u8],
73) -> Result<Vec<u8>, SecurityRtpsError> {
74 if message.len() < RTPS_HEADER_LEN {
75 return Err(SecurityRtpsError::Truncated("rtps-message header"));
76 }
77 let (header, body) = message.split_at(RTPS_HEADER_LEN);
78
79 let mut aad_extension = Vec::with_capacity(4 + RTPS_HEADER_LEN);
84 aad_extension.extend_from_slice(&[0u8; 4]);
85 aad_extension.extend_from_slice(header);
86
87 let ciphertext = plugin
88 .encrypt_submessage(local, remote_list, body, &aad_extension)
89 .map_err(SecurityRtpsError::Crypto)?;
90
91 let body_len = u16::try_from(ciphertext.len())
92 .map_err(|_| SecurityRtpsError::Truncated("SRTPS body > u16"))?;
93
94 let mut out = Vec::with_capacity(RTPS_HEADER_LEN + 4 + 16 + 4 + ciphertext.len() + 4);
95 out.extend_from_slice(header);
96
97 push_header_with_flags(&mut out, SRTPS_PREFIX, FLAG_LE | PRE_SHARED_KEY_FLAG, 16);
99 out.extend_from_slice(&[0u8; 16]);
100
101 push_header(&mut out, crate::codec::SEC_BODY, body_len);
102 out.extend_from_slice(&ciphertext);
103
104 push_header(&mut out, SRTPS_POSTFIX, 0);
105
106 Ok(out)
107}
108
109#[must_use]
113pub fn srtps_psk_flag(wire: &[u8]) -> Option<bool> {
114 if wire.len() < RTPS_HEADER_LEN + 4 {
115 return None;
116 }
117 if wire[RTPS_HEADER_LEN] != SRTPS_PREFIX {
118 return None;
119 }
120 Some(wire[RTPS_HEADER_LEN + 1] & PRE_SHARED_KEY_FLAG != 0)
121}
122
123pub fn encode_secured_rtps_message(
134 plugin: &dyn CryptographicPlugin,
135 local: CryptoHandle,
136 remote_list: &[CryptoHandle],
137 message: &[u8],
138) -> Result<Vec<u8>, SecurityRtpsError> {
139 if message.len() < RTPS_HEADER_LEN {
140 return Err(SecurityRtpsError::Truncated("rtps-message header"));
141 }
142 let (header, body) = message.split_at(RTPS_HEADER_LEN);
143
144 let mut aad_extension = Vec::with_capacity(4 + RTPS_HEADER_LEN);
149 aad_extension.extend_from_slice(&[0u8; 4]);
150 aad_extension.extend_from_slice(header);
151
152 let ciphertext = plugin
153 .encrypt_submessage(local, remote_list, body, &aad_extension)
154 .map_err(SecurityRtpsError::Crypto)?;
155
156 let body_len = u16::try_from(ciphertext.len())
157 .map_err(|_| SecurityRtpsError::Truncated("SRTPS body > u16"))?;
158
159 let mut out = Vec::with_capacity(
160 RTPS_HEADER_LEN + 4 + 16 + 4 + ciphertext.len() + 4, );
165 out.extend_from_slice(header);
166
167 push_header(&mut out, SRTPS_PREFIX, 16);
169 out.extend_from_slice(&[0u8; 16]);
170
171 push_header(&mut out, crate::codec::SEC_BODY, body_len);
177 out.extend_from_slice(&ciphertext);
178
179 push_header(&mut out, SRTPS_POSTFIX, 0);
181
182 Ok(out)
183}
184
185pub fn decode_secured_rtps_message(
192 plugin: &dyn CryptographicPlugin,
193 local: CryptoHandle,
194 remote: CryptoHandle,
195 wire: &[u8],
196) -> Result<Vec<u8>, SecurityRtpsError> {
197 if wire.len() < RTPS_HEADER_LEN {
198 return Err(SecurityRtpsError::Truncated("rtps-message header"));
199 }
200 let header = &wire[..RTPS_HEADER_LEN];
201 let rest = &wire[RTPS_HEADER_LEN..];
202
203 if rest.len() < 4 + 16 {
205 return Err(SecurityRtpsError::Truncated("SRTPS_PREFIX"));
206 }
207 if rest[0] != SRTPS_PREFIX {
208 return Err(SecurityRtpsError::UnexpectedSubmessageId {
209 pos: 0,
210 expected: SRTPS_PREFIX,
211 got: rest[0],
212 });
213 }
214 if rest[1] & FLAG_LE == 0 {
215 return Err(SecurityRtpsError::BigEndianNotSupported);
216 }
217 let mut plen_b = [0u8; 2];
220 plen_b.copy_from_slice(&rest[2..4]);
221 let plen = u16::from_le_bytes(plen_b) as usize;
222 let after_prefix = 4 + plen;
223 if rest.len() < after_prefix {
224 return Err(SecurityRtpsError::Truncated("SRTPS_PREFIX body"));
225 }
226 let rest = &rest[after_prefix..];
227
228 if rest.len() < 4 {
230 return Err(SecurityRtpsError::Truncated("SRTPS body header"));
231 }
232 if rest[0] != crate::codec::SEC_BODY {
233 return Err(SecurityRtpsError::UnexpectedSubmessageId {
234 pos: 1,
235 expected: crate::codec::SEC_BODY,
236 got: rest[0],
237 });
238 }
239 if rest[1] & FLAG_LE == 0 {
240 return Err(SecurityRtpsError::BigEndianNotSupported);
241 }
242 let mut blen_b = [0u8; 2];
243 blen_b.copy_from_slice(&rest[2..4]);
244 let blen = u16::from_le_bytes(blen_b) as usize;
245 let after_body = 4 + blen;
246 if rest.len() < after_body {
247 return Err(SecurityRtpsError::Truncated("SRTPS body payload"));
248 }
249 let ciphertext = &rest[4..after_body];
250 let after_body_rest = &rest[after_body..];
251
252 if after_body_rest.len() < 4 {
254 return Err(SecurityRtpsError::Truncated("SRTPS_POSTFIX"));
255 }
256 if after_body_rest[0] != SRTPS_POSTFIX {
257 return Err(SecurityRtpsError::UnexpectedSubmessageId {
258 pos: 2,
259 expected: SRTPS_POSTFIX,
260 got: after_body_rest[0],
261 });
262 }
263
264 let mut aad_extension = Vec::with_capacity(4 + RTPS_HEADER_LEN);
266 aad_extension.extend_from_slice(&[0u8; 4]);
267 aad_extension.extend_from_slice(header);
268
269 let plain_body = plugin
270 .decrypt_submessage(local, remote, ciphertext, &aad_extension)
271 .map_err(SecurityRtpsError::Crypto)?;
272
273 let mut out = Vec::with_capacity(RTPS_HEADER_LEN + plain_body.len());
274 out.extend_from_slice(header);
275 out.extend_from_slice(&plain_body);
276 Ok(out)
277}
278
279#[cfg(test)]
280#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
281mod tests {
282 use super::*;
283 use zerodds_security::authentication::{IdentityHandle, SharedSecretHandle};
284 use zerodds_security::error::SecurityErrorKind;
285 use zerodds_security_crypto::AesGcmCryptoPlugin;
286
287 fn make_plugin() -> (AesGcmCryptoPlugin, CryptoHandle, CryptoHandle) {
288 let mut p = AesGcmCryptoPlugin::new();
289 let local = p
290 .register_local_participant(IdentityHandle(1), &[])
291 .unwrap();
292 let remote = p
293 .register_matched_remote_participant(local, IdentityHandle(2), SharedSecretHandle(1))
294 .unwrap();
295 (p, local, remote)
296 }
297
298 fn fake_rtps_message(body: &[u8]) -> Vec<u8> {
299 let mut m = Vec::with_capacity(RTPS_HEADER_LEN + body.len());
301 m.extend_from_slice(b"RTPS\x02\x05\x01\x02");
302 m.extend_from_slice(&[0u8; 12]); m.extend_from_slice(body);
304 m
305 }
306
307 #[test]
308 fn encode_keeps_header_in_plaintext() {
309 let (p, local, remote) = make_plugin();
310 let msg = fake_rtps_message(b"[DATA submessage plaintext]");
311 let wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
312 assert_eq!(&wire[..4], b"RTPS");
314 assert_eq!(&wire[..RTPS_HEADER_LEN], &msg[..RTPS_HEADER_LEN]);
316 assert_eq!(wire[RTPS_HEADER_LEN], SRTPS_PREFIX);
318 }
319
320 #[test]
321 fn encode_body_is_not_in_wire_plain() {
322 let (p, local, remote) = make_plugin();
323 let secret_body = b"TOP-SECRET submessage body";
324 let msg = fake_rtps_message(secret_body);
325 let wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
326 assert!(
327 !wire.windows(secret_body.len()).any(|w| w == secret_body),
328 "plaintext body muss verschluesselt sein"
329 );
330 }
331
332 #[test]
333 fn message_roundtrip_recovers_body() {
334 let (p, local, remote) = make_plugin();
335 let body = b"[HEARTBEAT][DATA][GAP]";
336 let msg = fake_rtps_message(body);
337 let wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
338 let back = decode_secured_rtps_message(&p, local, remote, &wire).unwrap();
339 assert_eq!(back, msg);
340 }
341
342 #[test]
343 fn message_too_short_rejected() {
344 let (p, local, remote) = make_plugin();
345 let err = encode_secured_rtps_message(&p, local, &[remote], &[0u8; 10]).unwrap_err();
346 assert!(matches!(err, SecurityRtpsError::Truncated(_)));
347 }
348
349 #[test]
350 fn tampered_ciphertext_fails_verify() {
351 let (p, local, remote) = make_plugin();
352 let msg = fake_rtps_message(b"secure submessage stream");
353 let mut wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
354 let flip_idx = RTPS_HEADER_LEN + 4 + 16 + 4 + 12;
356 wire[flip_idx] ^= 0x10;
357 let err = decode_secured_rtps_message(&p, local, remote, &wire).unwrap_err();
358 match err {
359 SecurityRtpsError::Crypto(e) => {
360 assert_eq!(e.kind, SecurityErrorKind::CryptoFailed);
361 }
362 other => panic!("expected Crypto error, got {other:?}"),
363 }
364 }
365
366 #[test]
367 fn missing_srtps_prefix_rejected() {
368 let (p, local, remote) = make_plugin();
369 let msg = fake_rtps_message(b"x");
370 let mut wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
371 wire[RTPS_HEADER_LEN] = 0x15; let err = decode_secured_rtps_message(&p, local, remote, &wire).unwrap_err();
373 assert!(matches!(
374 err,
375 SecurityRtpsError::UnexpectedSubmessageId {
376 pos: 0,
377 expected: SRTPS_PREFIX,
378 ..
379 }
380 ));
381 }
382
383 #[test]
384 fn psk_encode_sets_pre_shared_key_flag() {
385 let (p, local, remote) = make_plugin();
386 let msg = fake_rtps_message(b"psk-protected body");
387 let wire = encode_secured_rtps_message_psk(&p, local, &[remote], &msg).unwrap();
388 assert_eq!(wire[RTPS_HEADER_LEN], SRTPS_PREFIX);
389 let flags = wire[RTPS_HEADER_LEN + 1];
391 assert!(flags & FLAG_LE != 0);
392 assert!(flags & PRE_SHARED_KEY_FLAG != 0);
393 assert_eq!(srtps_psk_flag(&wire), Some(true));
394 }
395
396 #[test]
397 fn non_psk_encode_does_not_set_pre_shared_key_flag() {
398 let (p, local, remote) = make_plugin();
399 let msg = fake_rtps_message(b"non-psk body");
400 let wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
401 assert_eq!(srtps_psk_flag(&wire), Some(false));
402 }
403
404 #[test]
405 fn psk_encoded_message_decodes_with_classic_decoder() {
406 let (p, local, remote) = make_plugin();
410 let msg = fake_rtps_message(b"interop-test");
411 let wire = encode_secured_rtps_message_psk(&p, local, &[remote], &msg).unwrap();
412 let back = decode_secured_rtps_message(&p, local, remote, &wire).unwrap();
413 assert_eq!(back, msg);
414 }
415
416 #[test]
417 fn srtps_psk_flag_returns_none_for_non_srtps() {
418 assert_eq!(srtps_psk_flag(&[]), None);
419 assert_eq!(srtps_psk_flag(&[0u8; 30]), None);
420 }
421
422 #[test]
423 fn big_endian_srtps_rejected() {
424 let (p, local, remote) = make_plugin();
425 let msg = fake_rtps_message(b"x");
426 let mut wire = encode_secured_rtps_message(&p, local, &[remote], &msg).unwrap();
427 wire[RTPS_HEADER_LEN + 1] = 0x00;
429 let err = decode_secured_rtps_message(&p, local, remote, &wire).unwrap_err();
430 assert!(matches!(err, SecurityRtpsError::BigEndianNotSupported));
431 }
432}