1extern crate alloc;
34use alloc::string::{String, ToString};
35use alloc::vec::Vec;
36
37use crate::error::WireError;
38
39pub const MAX_PROPERTIES: usize = 1_024;
43
44pub const MAX_PROPERTY_STRING_LEN: usize = 64 * 1024;
47
48#[derive(Debug, Clone, PartialEq, Eq, Default)]
52pub struct WireProperty {
53 pub name: String,
55 pub value: String,
57}
58
59impl WireProperty {
60 #[must_use]
62 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
63 Self {
64 name: name.into(),
65 value: value.into(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct WirePropertyList {
75 pub entries: Vec<WireProperty>,
77}
78
79impl WirePropertyList {
80 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 #[must_use]
88 pub fn is_empty(&self) -> bool {
89 self.entries.is_empty()
90 }
91
92 #[must_use]
94 pub fn len(&self) -> usize {
95 self.entries.len()
96 }
97
98 pub fn push(&mut self, prop: WireProperty) {
101 self.entries.push(prop);
102 }
103
104 #[must_use]
106 pub fn with(mut self, prop: WireProperty) -> Self {
107 self.push(prop);
108 self
109 }
110
111 #[must_use]
114 pub fn get(&self, name: &str) -> Option<&str> {
115 self.entries
116 .iter()
117 .rev()
118 .find(|p| p.name == name)
119 .map(|p| p.value.as_str())
120 }
121
122 pub fn encode(&self, little_endian: bool) -> Result<Vec<u8>, WireError> {
132 if self.entries.len() > MAX_PROPERTIES {
133 return Err(WireError::ValueOutOfRange {
134 message: "PropertyList: exceeds MAX_PROPERTIES",
135 });
136 }
137 let mut out = Vec::new();
138 let n = u32::try_from(self.entries.len()).map_err(|_| WireError::ValueOutOfRange {
139 message: "PropertyList: entry count exceeds u32",
140 })?;
141 write_u32(&mut out, n, little_endian);
142 for p in &self.entries {
143 check_len(&p.name)?;
144 check_len(&p.value)?;
145 write_cdr_string(&mut out, &p.name, little_endian)?;
146 align4(&mut out);
147 write_cdr_string(&mut out, &p.value, little_endian)?;
148 align4(&mut out);
149 }
150 write_u32(&mut out, 0, little_endian);
152 Ok(out)
153 }
154
155 pub fn decode(bytes: &[u8], little_endian: bool) -> Result<Self, WireError> {
162 let mut pos = 0usize;
163 let n_props = read_u32(bytes, &mut pos, little_endian)? as usize;
164 if n_props > MAX_PROPERTIES {
165 return Err(WireError::ValueOutOfRange {
166 message: "PropertyList: count exceeds MAX_PROPERTIES",
167 });
168 }
169 let mut entries = Vec::with_capacity(n_props);
170 for _ in 0..n_props {
171 let name = read_cdr_string(bytes, &mut pos, little_endian)?;
172 align4_read(&mut pos);
173 let value = read_cdr_string(bytes, &mut pos, little_endian)?;
174 align4_read(&mut pos);
175 entries.push(WireProperty { name, value });
176 }
177 let n_binary = read_u32(bytes, &mut pos, little_endian)? as usize;
181 if n_binary > MAX_PROPERTIES {
182 return Err(WireError::ValueOutOfRange {
183 message: "PropertyList: binary count exceeds MAX_PROPERTIES",
184 });
185 }
186 for _ in 0..n_binary {
187 let _ = read_cdr_string(bytes, &mut pos, little_endian)?;
189 align4_read(&mut pos);
190 let vlen = read_u32(bytes, &mut pos, little_endian)? as usize;
191 if vlen > MAX_PROPERTY_STRING_LEN {
192 return Err(WireError::ValueOutOfRange {
193 message: "PropertyList: binary value exceeds cap",
194 });
195 }
196 if bytes.len() < pos.saturating_add(vlen) {
197 return Err(WireError::UnexpectedEof {
198 needed: vlen,
199 offset: pos,
200 });
201 }
202 pos += vlen;
203 align4_read(&mut pos);
204 }
205 Ok(Self { entries })
206 }
207}
208
209fn check_len(s: &str) -> Result<(), WireError> {
214 if s.len() > MAX_PROPERTY_STRING_LEN {
215 return Err(WireError::ValueOutOfRange {
216 message: "PropertyList: string exceeds MAX_PROPERTY_STRING_LEN",
217 });
218 }
219 if s.as_bytes().contains(&0) {
220 return Err(WireError::ValueOutOfRange {
221 message: "PropertyList: string contains inner null byte",
222 });
223 }
224 Ok(())
225}
226
227fn align4(out: &mut Vec<u8>) {
228 while out.len() % 4 != 0 {
229 out.push(0);
230 }
231}
232
233fn align4_read(pos: &mut usize) {
234 *pos = pos.wrapping_add((4 - (*pos % 4)) % 4);
235}
236
237fn write_u32(out: &mut Vec<u8>, v: u32, le: bool) {
238 let bytes = if le { v.to_le_bytes() } else { v.to_be_bytes() };
239 out.extend_from_slice(&bytes);
240}
241
242fn read_u32(bytes: &[u8], pos: &mut usize, le: bool) -> Result<u32, WireError> {
243 if bytes.len() < pos.saturating_add(4) {
244 return Err(WireError::UnexpectedEof {
245 needed: 4,
246 offset: *pos,
247 });
248 }
249 let mut b = [0u8; 4];
250 b.copy_from_slice(&bytes[*pos..*pos + 4]);
251 *pos += 4;
252 Ok(if le {
253 u32::from_le_bytes(b)
254 } else {
255 u32::from_be_bytes(b)
256 })
257}
258
259fn write_cdr_string(out: &mut Vec<u8>, s: &str, le: bool) -> Result<(), WireError> {
260 let bytes = s.as_bytes();
261 let len =
262 u32::try_from(bytes.len().saturating_add(1)).map_err(|_| WireError::ValueOutOfRange {
263 message: "CDR string length exceeds u32::MAX",
264 })?;
265 write_u32(out, len, le);
266 out.extend_from_slice(bytes);
267 out.push(0);
268 Ok(())
269}
270
271fn read_cdr_string(bytes: &[u8], pos: &mut usize, le: bool) -> Result<String, WireError> {
272 let len = read_u32(bytes, pos, le)? as usize;
273 if len == 0 {
274 return Err(WireError::ValueOutOfRange {
275 message: "CDR string length 0 (missing null terminator)",
276 });
277 }
278 if len > MAX_PROPERTY_STRING_LEN {
279 return Err(WireError::ValueOutOfRange {
280 message: "CDR string exceeds MAX_PROPERTY_STRING_LEN",
281 });
282 }
283 if bytes.len() < pos.saturating_add(len) {
284 return Err(WireError::UnexpectedEof {
285 needed: len,
286 offset: *pos,
287 });
288 }
289 let text_bytes = &bytes[*pos..*pos + len - 1];
291 if bytes[*pos + len - 1] != 0 {
292 return Err(WireError::ValueOutOfRange {
293 message: "CDR string missing null terminator",
294 });
295 }
296 let s = core::str::from_utf8(text_bytes)
297 .map_err(|_| WireError::ValueOutOfRange {
298 message: "CDR string is not valid UTF-8",
299 })?
300 .to_string();
301 *pos += len;
302 Ok(s)
303}
304
305#[cfg(test)]
310mod tests {
311 #![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
312 use super::*;
313
314 #[test]
315 fn empty_list_roundtrip_le() {
316 let list = WirePropertyList::new();
317 let bytes = list.encode(true).unwrap();
318 assert_eq!(bytes, [0u8; 8]);
320 let decoded = WirePropertyList::decode(&bytes, true).unwrap();
321 assert_eq!(decoded, list);
322 }
323
324 #[test]
325 fn single_property_roundtrip_le() {
326 let list = WirePropertyList::new().with(WireProperty::new(
327 "dds.sec.auth.plugin_class",
328 "DDS:Auth:PKI-DH:1.2",
329 ));
330 let bytes = list.encode(true).unwrap();
331 let decoded = WirePropertyList::decode(&bytes, true).unwrap();
332 assert_eq!(decoded, list);
333 }
334
335 #[test]
336 fn multi_property_roundtrip_le() {
337 let list = WirePropertyList::new()
338 .with(WireProperty::new(
339 "dds.sec.auth.plugin_class",
340 "DDS:Auth:PKI-DH:1.2",
341 ))
342 .with(WireProperty::new(
343 "dds.sec.crypto.plugin_class",
344 "DDS:Crypto:AES-GCM-GMAC:1.2",
345 ))
346 .with(WireProperty::new(
347 "zerodds.sec.supported_suites",
348 "AES_128_GCM,AES_256_GCM,HMAC_SHA256",
349 ))
350 .with(WireProperty::new(
351 "zerodds.sec.offered_protection",
352 "ENCRYPT",
353 ));
354 let bytes = list.encode(true).unwrap();
355 let decoded = WirePropertyList::decode(&bytes, true).unwrap();
356 assert_eq!(decoded, list);
357 }
358
359 #[test]
360 fn multi_property_roundtrip_be() {
361 let list = WirePropertyList::new()
362 .with(WireProperty::new("a", "b"))
363 .with(WireProperty::new("cc", "dd"));
364 let bytes = list.encode(false).unwrap();
365 let decoded = WirePropertyList::decode(&bytes, false).unwrap();
366 assert_eq!(decoded, list);
367 }
368
369 #[test]
370 fn encode_pads_strings_to_4_between_name_and_value() {
371 let list = WirePropertyList::new().with(WireProperty::new("ab", "cd"));
372 let bytes = list.encode(true).unwrap();
373 assert_eq!(bytes.len(), 4 + 4 + 4 + 4 + 4 + 4);
381 }
382
383 #[test]
384 fn get_returns_last_value_for_duplicate_names() {
385 let list = WirePropertyList::new()
386 .with(WireProperty::new("dup", "first"))
387 .with(WireProperty::new("dup", "second"));
388 assert_eq!(list.get("dup"), Some("second"));
389 }
390
391 #[test]
392 fn get_none_for_unknown_name() {
393 let list = WirePropertyList::new().with(WireProperty::new("a", "b"));
394 assert!(list.get("x").is_none());
395 }
396
397 #[test]
398 fn decode_rejects_truncated_count() {
399 let err = WirePropertyList::decode(&[0, 0], true).unwrap_err();
400 assert!(matches!(err, WireError::UnexpectedEof { .. }));
401 }
402
403 #[test]
404 fn decode_rejects_count_above_cap() {
405 let mut bytes = Vec::new();
407 bytes.extend_from_slice(&u32::MAX.to_le_bytes());
408 let err = WirePropertyList::decode(&bytes, true).unwrap_err();
409 assert!(matches!(err, WireError::ValueOutOfRange { .. }));
410 }
411
412 #[test]
413 fn decode_rejects_truncated_value_string() {
414 let mut bytes = Vec::new();
415 bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(b"ab\0\0"); bytes.extend_from_slice(&10u32.to_le_bytes()); let err = WirePropertyList::decode(&bytes, true).unwrap_err();
420 assert!(matches!(err, WireError::UnexpectedEof { .. }));
421 }
422
423 #[test]
424 fn decode_accepts_nonzero_binary_sequence() {
425 let mut bytes = Vec::new();
427 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(b"bin\0"); bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0x00]); let decoded = WirePropertyList::decode(&bytes, true).unwrap();
434 assert!(decoded.is_empty(), "binary seq wird still konsumiert");
435 }
436
437 #[test]
438 fn encode_rejects_inner_null_byte_in_name() {
439 let list = WirePropertyList::new().with(WireProperty::new("a\0b", "v"));
440 assert!(list.encode(true).is_err());
441 }
442
443 #[test]
444 fn encode_rejects_inner_null_byte_in_value() {
445 let list = WirePropertyList::new().with(WireProperty::new("a", "v\0v"));
446 assert!(list.encode(true).is_err());
447 }
448
449 #[test]
450 fn encode_enforces_max_properties() {
451 let mut list = WirePropertyList::new();
452 for _ in 0..(MAX_PROPERTIES + 1) {
453 list.push(WireProperty::new("k", "v"));
454 }
455 assert!(list.encode(true).is_err());
456 }
457
458 #[test]
459 fn decode_rejects_missing_null_terminator() {
460 let mut bytes = Vec::new();
462 bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(b"abcd"); bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(b"v\0\0\0"); bytes.extend_from_slice(&0u32.to_le_bytes()); let err = WirePropertyList::decode(&bytes, true).unwrap_err();
469 assert!(matches!(err, WireError::ValueOutOfRange { .. }));
470 }
471
472 #[test]
473 fn decode_rejects_non_utf8_name() {
474 let mut bytes = Vec::new();
475 bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x00]); bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(b"v\0\0\0");
480 bytes.extend_from_slice(&0u32.to_le_bytes());
481 let err = WirePropertyList::decode(&bytes, true).unwrap_err();
482 assert!(matches!(err, WireError::ValueOutOfRange { .. }));
483 }
484
485 #[test]
486 fn len_and_is_empty_consistency() {
487 let mut list = WirePropertyList::new();
488 assert_eq!(list.len(), 0);
489 assert!(list.is_empty());
490 list.push(WireProperty::new("a", "b"));
491 assert_eq!(list.len(), 1);
492 assert!(!list.is_empty());
493 }
494
495 #[test]
496 fn encode_rejects_name_above_max_string_len() {
497 let big = "x".repeat(MAX_PROPERTY_STRING_LEN + 1);
498 let list = WirePropertyList::new().with(WireProperty::new(big, "v"));
499 assert!(list.encode(true).is_err());
500 }
501
502 #[test]
503 fn decode_rejects_binary_count_above_cap() {
504 let mut bytes = Vec::new();
505 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&u32::MAX.to_le_bytes()); let err = WirePropertyList::decode(&bytes, true).unwrap_err();
508 assert!(matches!(err, WireError::ValueOutOfRange { .. }));
509 }
510
511 #[test]
512 fn decode_rejects_binary_value_above_cap() {
513 let mut bytes = Vec::new();
514 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(b"bin\0"); bytes.extend_from_slice(&(MAX_PROPERTY_STRING_LEN as u32 + 1).to_le_bytes());
519 let err = WirePropertyList::decode(&bytes, true).unwrap_err();
521 assert!(matches!(err, WireError::ValueOutOfRange { .. }));
522 }
523
524 #[test]
525 fn decode_rejects_truncated_binary_value() {
526 let mut bytes = Vec::new();
527 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&4u32.to_le_bytes()); bytes.extend_from_slice(b"bin\0");
531 bytes.extend_from_slice(&8u32.to_le_bytes()); bytes.extend_from_slice(&[0xAA, 0xBB]); let err = WirePropertyList::decode(&bytes, true).unwrap_err();
534 assert!(matches!(err, WireError::UnexpectedEof { .. }));
535 }
536
537 #[test]
538 fn csv_suites_value_roundtrips() {
539 let list = WirePropertyList::new().with(WireProperty::new(
540 "zerodds.sec.supported_suites",
541 "AES_128_GCM,AES_256_GCM,HMAC_SHA256",
542 ));
543 let bytes = list.encode(true).unwrap();
544 let decoded = WirePropertyList::decode(&bytes, true).unwrap();
545 assert_eq!(
546 decoded.get("zerodds.sec.supported_suites"),
547 Some("AES_128_GCM,AES_256_GCM,HMAC_SHA256")
548 );
549 }
550}