1extern crate alloc;
67use alloc::vec::Vec;
68
69use crate::error::WireError;
70use crate::parameter_list::ParameterList;
71
72pub const SUBMESSAGE_ID_HEADER_EXTENSION: u8 = 0x80;
74
75pub const MAX_HE_LENGTH: usize = 16 * 1024;
77
78pub const HE_FLAG_E: u8 = 1 << 0;
82pub const HE_FLAG_L: u8 = 1 << 1;
84pub const HE_FLAG_W: u8 = 1 << 2;
86pub const HE_FLAG_U: u8 = 1 << 3;
88pub const HE_FLAG_V: u8 = 1 << 4;
90pub const HE_FLAG_C0: u8 = 1 << 5;
92pub const HE_FLAG_C1: u8 = 1 << 6;
94pub const HE_FLAG_C_MASK: u8 = HE_FLAG_C0 | HE_FLAG_C1;
96pub const HE_FLAG_P: u8 = 1 << 7;
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum ChecksumKind {
107 None,
109 Crc32c,
111 Crc64,
113 Md5,
115}
116
117impl ChecksumKind {
118 #[must_use]
120 pub fn wire_size(self) -> usize {
121 match self {
122 Self::None => 0,
123 Self::Crc32c => 4,
124 Self::Crc64 => 8,
125 Self::Md5 => 16,
126 }
127 }
128
129 #[must_use]
131 pub fn from_flags(flags: u8) -> Self {
132 match (flags & HE_FLAG_C_MASK) >> 5 {
134 1 => Self::Crc32c,
135 2 => Self::Crc64,
136 3 => Self::Md5,
137 _ => Self::None,
139 }
140 }
141
142 #[must_use]
144 pub fn to_flag_bits(self) -> u8 {
145 let raw = match self {
146 Self::None => 0u8,
147 Self::Crc32c => 1,
148 Self::Crc64 => 2,
149 Self::Md5 => 3,
150 };
151 (raw << 5) & HE_FLAG_C_MASK
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Default)]
157pub enum ChecksumValue {
158 #[default]
160 None,
161 Crc32c(u32),
163 Crc64(u64),
165 Md5([u8; 16]),
167}
168
169impl ChecksumValue {
170 #[must_use]
172 pub fn kind(&self) -> ChecksumKind {
173 match self {
174 Self::None => ChecksumKind::None,
175 Self::Crc32c(_) => ChecksumKind::Crc32c,
176 Self::Crc64(_) => ChecksumKind::Crc64,
177 Self::Md5(_) => ChecksumKind::Md5,
178 }
179 }
180
181 #[must_use]
194 pub fn compute(kind: ChecksumKind, payload: &[u8]) -> Self {
195 match kind {
196 ChecksumKind::None => Self::None,
197 ChecksumKind::Crc32c => Self::Crc32c(zerodds_foundation::crc32c(payload)),
198 ChecksumKind::Crc64 => Self::Crc64(zerodds_foundation::crc64_xz(payload)),
199 ChecksumKind::Md5 => Self::Md5(zerodds_foundation::md5(payload)),
200 }
201 }
202
203 #[must_use]
212 pub fn verify(&self, payload: &[u8]) -> bool {
213 let computed = Self::compute(self.kind(), payload);
214 computed == *self
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
222pub struct HeTimestamp {
223 pub seconds: i32,
225 pub fraction: u32,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Default)]
231pub struct HeaderExtension {
232 pub little_endian: bool,
234 pub message_length: Option<u32>,
236 pub timestamp: Option<HeTimestamp>,
238 pub uextension4: Option<[u8; 4]>,
240 pub wextension8: Option<[u8; 8]>,
242 pub checksum: ChecksumValue,
244 pub parameters: Option<ParameterList>,
246}
247
248impl HeaderExtension {
249 #[must_use]
252 pub fn new() -> Self {
253 Self::default()
254 }
255
256 #[must_use]
258 pub fn flag_byte(&self) -> u8 {
259 let mut f = 0u8;
260 if self.little_endian {
261 f |= HE_FLAG_E;
262 }
263 if self.message_length.is_some() {
264 f |= HE_FLAG_L;
265 }
266 if self.timestamp.is_some() {
267 f |= HE_FLAG_W;
268 }
269 if self.uextension4.is_some() {
270 f |= HE_FLAG_U;
271 }
272 if self.wextension8.is_some() {
273 f |= HE_FLAG_V;
274 }
275 f |= self.checksum.kind().to_flag_bits();
276 if self.parameters.is_some() {
277 f |= HE_FLAG_P;
278 }
279 f
280 }
281
282 pub fn encode_body(&self) -> Result<Vec<u8>, WireError> {
291 let le = self.little_endian;
292 let mut body = Vec::with_capacity(64);
293
294 if let Some(len) = self.message_length {
295 write_u32(&mut body, len, le);
296 }
297 if let Some(ts) = self.timestamp {
298 write_i32(&mut body, ts.seconds, le);
299 write_u32(&mut body, ts.fraction, le);
300 }
301 if let Some(u) = self.uextension4 {
302 body.extend_from_slice(&u);
303 }
304 if let Some(v) = self.wextension8 {
305 body.extend_from_slice(&v);
306 }
307 match self.checksum {
308 ChecksumValue::None => {}
309 ChecksumValue::Crc32c(c) => write_u32(&mut body, c, le),
310 ChecksumValue::Crc64(c) => write_u64(&mut body, c, le),
311 ChecksumValue::Md5(m) => body.extend_from_slice(&m),
312 }
313 if let Some(pl) = &self.parameters {
314 body.extend_from_slice(&pl.to_bytes(le));
316 }
317 if body.len() > MAX_HE_LENGTH {
318 return Err(WireError::ValueOutOfRange {
319 message: "HeaderExtension body exceeds MAX_HE_LENGTH",
320 });
321 }
322 Ok(body)
323 }
324
325 pub fn encode(&self) -> Result<Vec<u8>, WireError> {
331 let body = self.encode_body()?;
332 let body_len = u16::try_from(body.len()).map_err(|_| WireError::ValueOutOfRange {
333 message: "HE body exceeds u16::MAX",
334 })?;
335 let mut out = Vec::with_capacity(4 + body.len());
336 out.push(SUBMESSAGE_ID_HEADER_EXTENSION);
337 out.push(self.flag_byte());
338 let len_bytes = if self.little_endian {
339 body_len.to_le_bytes()
340 } else {
341 body_len.to_be_bytes()
342 };
343 out.extend_from_slice(&len_bytes);
344 out.extend_from_slice(&body);
345 Ok(out)
346 }
347
348 pub fn decode_body(body: &[u8], flags: u8) -> Result<Self, WireError> {
356 if body.len() > MAX_HE_LENGTH {
357 return Err(WireError::ValueOutOfRange {
358 message: "HE body exceeds MAX_HE_LENGTH",
359 });
360 }
361 let le = (flags & HE_FLAG_E) != 0;
362 let mut pos = 0usize;
363 let need = |n: usize, p: usize| -> Result<(), WireError> {
364 if body.len() < p + n {
365 Err(WireError::UnexpectedEof {
366 needed: n,
367 offset: p,
368 })
369 } else {
370 Ok(())
371 }
372 };
373
374 let message_length = if (flags & HE_FLAG_L) != 0 {
375 need(4, pos)?;
376 let v = read_u32(&body[pos..pos + 4], le);
377 pos += 4;
378 Some(v)
379 } else {
380 None
381 };
382 let timestamp = if (flags & HE_FLAG_W) != 0 {
383 need(8, pos)?;
384 let s = read_i32(&body[pos..pos + 4], le);
385 let f = read_u32(&body[pos + 4..pos + 8], le);
386 pos += 8;
387 Some(HeTimestamp {
388 seconds: s,
389 fraction: f,
390 })
391 } else {
392 None
393 };
394 let uextension4 = if (flags & HE_FLAG_U) != 0 {
395 need(4, pos)?;
396 let mut u = [0u8; 4];
397 u.copy_from_slice(&body[pos..pos + 4]);
398 pos += 4;
399 Some(u)
400 } else {
401 None
402 };
403 let wextension8 = if (flags & HE_FLAG_V) != 0 {
404 need(8, pos)?;
405 let mut v = [0u8; 8];
406 v.copy_from_slice(&body[pos..pos + 8]);
407 pos += 8;
408 Some(v)
409 } else {
410 None
411 };
412
413 let kind = ChecksumKind::from_flags(flags);
414 let checksum = match kind {
415 ChecksumKind::None => ChecksumValue::None,
416 ChecksumKind::Crc32c => {
417 need(4, pos)?;
418 let v = read_u32(&body[pos..pos + 4], le);
419 pos += 4;
420 ChecksumValue::Crc32c(v)
421 }
422 ChecksumKind::Crc64 => {
423 need(8, pos)?;
424 let v = read_u64(&body[pos..pos + 8], le);
425 pos += 8;
426 ChecksumValue::Crc64(v)
427 }
428 ChecksumKind::Md5 => {
429 need(16, pos)?;
430 let mut m = [0u8; 16];
431 m.copy_from_slice(&body[pos..pos + 16]);
432 pos += 16;
433 ChecksumValue::Md5(m)
434 }
435 };
436
437 let parameters = if (flags & HE_FLAG_P) != 0 {
438 let pl = ParameterList::from_bytes(&body[pos..], le)?;
440 Some(pl)
452 } else {
453 if pos != body.len() {
455 return Err(WireError::ValueOutOfRange {
456 message: "HE trailing bytes without P-flag",
457 });
458 }
459 None
460 };
461
462 Ok(Self {
463 little_endian: le,
464 message_length,
465 timestamp,
466 uextension4,
467 wextension8,
468 checksum,
469 parameters,
470 })
471 }
472
473 pub fn decode(bytes: &[u8]) -> Result<Self, WireError> {
479 if bytes.len() < 4 {
480 return Err(WireError::UnexpectedEof {
481 needed: 4,
482 offset: 0,
483 });
484 }
485 if bytes[0] != SUBMESSAGE_ID_HEADER_EXTENSION {
486 return Err(WireError::UnknownSubmessageId { id: bytes[0] });
487 }
488 let flags = bytes[1];
489 let le = (flags & HE_FLAG_E) != 0;
490 let mut len_bytes = [0u8; 2];
491 len_bytes.copy_from_slice(&bytes[2..4]);
492 let octets = if le {
493 u16::from_le_bytes(len_bytes)
494 } else {
495 u16::from_be_bytes(len_bytes)
496 } as usize;
497 if bytes.len() < 4 + octets {
498 return Err(WireError::UnexpectedEof {
499 needed: octets,
500 offset: 4,
501 });
502 }
503 Self::decode_body(&bytes[4..4 + octets], flags)
504 }
505
506 pub fn validate_message_length(&self, actual_message_length: u32) -> Result<(), WireError> {
517 if let Some(declared) = self.message_length {
518 if declared != actual_message_length {
519 return Err(WireError::ValueOutOfRange {
520 message: "HeaderExtension messageLength mismatch",
521 });
522 }
523 }
524 Ok(())
525 }
526}
527
528pub const PID_MUST_UNDERSTAND: u16 = 0x4000;
535
536pub const PID_VENDOR_SPECIFIC: u16 = 0x8000;
540
541#[must_use]
544pub fn pid_must_understand(pid: u16) -> bool {
545 (pid & PID_MUST_UNDERSTAND) != 0
546}
547
548#[must_use]
550pub fn pid_strip(pid: u16) -> u16 {
551 pid & !(PID_MUST_UNDERSTAND | PID_VENDOR_SPECIFIC)
552}
553
554fn write_u32(out: &mut Vec<u8>, v: u32, le: bool) {
559 let bytes = if le { v.to_le_bytes() } else { v.to_be_bytes() };
560 out.extend_from_slice(&bytes);
561}
562
563fn write_i32(out: &mut Vec<u8>, v: i32, le: bool) {
564 let bytes = if le { v.to_le_bytes() } else { v.to_be_bytes() };
565 out.extend_from_slice(&bytes);
566}
567
568fn write_u64(out: &mut Vec<u8>, v: u64, le: bool) {
569 let bytes = if le { v.to_le_bytes() } else { v.to_be_bytes() };
570 out.extend_from_slice(&bytes);
571}
572
573fn read_u32(bytes: &[u8], le: bool) -> u32 {
574 let mut buf = [0u8; 4];
575 buf.copy_from_slice(&bytes[..4]);
576 if le {
577 u32::from_le_bytes(buf)
578 } else {
579 u32::from_be_bytes(buf)
580 }
581}
582
583fn read_i32(bytes: &[u8], le: bool) -> i32 {
584 let mut buf = [0u8; 4];
585 buf.copy_from_slice(&bytes[..4]);
586 if le {
587 i32::from_le_bytes(buf)
588 } else {
589 i32::from_be_bytes(buf)
590 }
591}
592
593fn read_u64(bytes: &[u8], le: bool) -> u64 {
594 let mut buf = [0u8; 8];
595 buf.copy_from_slice(&bytes[..8]);
596 if le {
597 u64::from_le_bytes(buf)
598 } else {
599 u64::from_be_bytes(buf)
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 #![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
606 use super::*;
607 use crate::parameter_list::Parameter;
608 use alloc::vec;
609
610 fn roundtrip(he: HeaderExtension) {
611 let bytes = he.encode().unwrap();
612 let decoded = HeaderExtension::decode(&bytes).unwrap();
613 assert_eq!(decoded, he);
614 }
615
616 #[test]
617 fn empty_he_le_roundtrip() {
618 let he = HeaderExtension {
619 little_endian: true,
620 ..HeaderExtension::default()
621 };
622 roundtrip(he);
623 }
624
625 #[test]
626 fn empty_he_be_roundtrip() {
627 let he = HeaderExtension::default();
628 roundtrip(he);
630 }
631
632 #[test]
633 fn flag_byte_empty_le() {
634 let he = HeaderExtension {
635 little_endian: true,
636 ..HeaderExtension::default()
637 };
638 assert_eq!(he.flag_byte(), HE_FLAG_E);
639 }
640
641 #[test]
642 fn flag_byte_with_message_length() {
643 let mut he = HeaderExtension {
644 little_endian: true,
645 message_length: Some(4242),
646 ..HeaderExtension::default()
647 };
648 assert_eq!(he.flag_byte(), HE_FLAG_E | HE_FLAG_L);
649 he.message_length = None;
650 assert_eq!(he.flag_byte(), HE_FLAG_E);
651 }
652
653 #[test]
654 fn flag_byte_all_optional_set_le() {
655 let he = HeaderExtension {
656 little_endian: true,
657 message_length: Some(0x0102_0304),
658 timestamp: Some(HeTimestamp {
659 seconds: 7,
660 fraction: 8,
661 }),
662 uextension4: Some([0xAA, 0xBB, 0xCC, 0xDD]),
663 wextension8: Some([0x11; 8]),
664 checksum: ChecksumValue::Md5([0x22; 16]),
665 parameters: Some(ParameterList::new()),
666 };
667 let f = he.flag_byte();
668 assert_eq!(
669 f,
670 HE_FLAG_E
671 | HE_FLAG_L
672 | HE_FLAG_W
673 | HE_FLAG_U
674 | HE_FLAG_V
675 | HE_FLAG_P
676 | ChecksumKind::Md5.to_flag_bits()
677 );
678 }
679
680 #[test]
681 fn checksum_kind_roundtrip() {
682 for kind in [
683 ChecksumKind::None,
684 ChecksumKind::Crc32c,
685 ChecksumKind::Crc64,
686 ChecksumKind::Md5,
687 ] {
688 let bits = kind.to_flag_bits();
689 assert_eq!(ChecksumKind::from_flags(HE_FLAG_E | bits), kind);
690 }
691 }
692
693 #[test]
694 fn checksum_kind_wire_size() {
695 assert_eq!(ChecksumKind::None.wire_size(), 0);
696 assert_eq!(ChecksumKind::Crc32c.wire_size(), 4);
697 assert_eq!(ChecksumKind::Crc64.wire_size(), 8);
698 assert_eq!(ChecksumKind::Md5.wire_size(), 16);
699 }
700
701 #[test]
702 fn roundtrip_with_message_length_only_le() {
703 let he = HeaderExtension {
704 little_endian: true,
705 message_length: Some(0xDEAD_BEEF),
706 ..HeaderExtension::default()
707 };
708 roundtrip(he);
709 }
710
711 #[test]
712 fn roundtrip_with_timestamp_le() {
713 let he = HeaderExtension {
714 little_endian: true,
715 timestamp: Some(HeTimestamp {
716 seconds: 1_710_000_000,
717 fraction: 500_000_000,
718 }),
719 ..HeaderExtension::default()
720 };
721 roundtrip(he);
722 }
723
724 #[test]
725 fn roundtrip_with_uextension4_be() {
726 let he = HeaderExtension {
727 little_endian: false,
728 uextension4: Some([1, 2, 3, 4]),
729 ..HeaderExtension::default()
730 };
731 roundtrip(he);
732 }
733
734 #[test]
735 fn roundtrip_with_wextension8_le() {
736 let he = HeaderExtension {
737 little_endian: true,
738 wextension8: Some([0xA5; 8]),
739 ..HeaderExtension::default()
740 };
741 roundtrip(he);
742 }
743
744 #[test]
745 fn roundtrip_with_crc32c_le() {
746 let he = HeaderExtension {
747 little_endian: true,
748 checksum: ChecksumValue::Crc32c(0xCAFE_BABE),
749 ..HeaderExtension::default()
750 };
751 roundtrip(he);
752 }
753
754 #[test]
755 fn roundtrip_with_crc64_be() {
756 let he = HeaderExtension {
757 little_endian: false,
758 checksum: ChecksumValue::Crc64(0x0123_4567_89AB_CDEF),
759 ..HeaderExtension::default()
760 };
761 roundtrip(he);
762 }
763
764 #[test]
765 fn roundtrip_with_md5_le() {
766 let he = HeaderExtension {
767 little_endian: true,
768 checksum: ChecksumValue::Md5([
769 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8,
770 0x42, 0x7e,
771 ]),
772 ..HeaderExtension::default()
773 };
774 roundtrip(he);
775 }
776
777 #[test]
778 fn roundtrip_with_parameter_list() {
779 let mut pl = ParameterList::new();
780 pl.push(Parameter::new(0x0015, vec![2, 5, 0, 0]));
781 let he = HeaderExtension {
782 little_endian: true,
783 parameters: Some(pl),
784 ..HeaderExtension::default()
785 };
786 roundtrip(he);
787 }
788
789 #[test]
790 fn decode_rejects_malformed_parameter_list_when_p_flag_set() {
791 let mut pl_bytes = Vec::<u8>::new();
796 pl_bytes.extend_from_slice(&0x0002u16.to_le_bytes());
799 pl_bytes.extend_from_slice(&0x0010u16.to_le_bytes());
800 pl_bytes.extend_from_slice(&[0xAA, 0xBB]); let flags = HE_FLAG_E | HE_FLAG_P;
803 let r = HeaderExtension::decode_body(&pl_bytes, flags);
804 assert!(r.is_err(), "malformed PL must be rejected");
805 }
806
807 #[test]
808 fn decode_rejects_truncated_uextension4_body() {
809 let body: [u8; 2] = [0xAA, 0xBB];
812 let flags = HE_FLAG_E | HE_FLAG_U;
813 let r = HeaderExtension::decode_body(&body, flags);
814 assert!(matches!(r, Err(WireError::UnexpectedEof { .. })));
815 }
816
817 #[test]
818 fn decode_rejects_truncated_wextension8_body() {
819 let body: [u8; 4] = [0; 4];
821 let flags = HE_FLAG_E | HE_FLAG_V;
822 let r = HeaderExtension::decode_body(&body, flags);
823 assert!(matches!(r, Err(WireError::UnexpectedEof { .. })));
824 }
825
826 #[test]
827 fn decode_rejects_truncated_md5_checksum_body() {
828 let body: [u8; 4] = [0; 4];
830 let flags = HE_FLAG_E | HE_FLAG_C0 | HE_FLAG_C1;
831 let r = HeaderExtension::decode_body(&body, flags);
832 assert!(matches!(r, Err(WireError::UnexpectedEof { .. })));
833 }
834
835 #[test]
836 fn flag_byte_includes_all_seven_logical_flags() {
837 let mut pl = ParameterList::new();
839 pl.push(Parameter::new(0x0001, vec![1, 2, 3, 4]));
840 let he = HeaderExtension {
841 little_endian: true,
842 message_length: Some(99),
843 timestamp: Some(HeTimestamp::default()),
844 uextension4: Some([1; 4]),
845 wextension8: Some([2; 8]),
846 checksum: ChecksumValue::Crc64(0xABCD),
847 parameters: Some(pl),
848 };
849 let f = he.flag_byte();
850 assert!(f & HE_FLAG_E != 0, "E");
851 assert!(f & HE_FLAG_L != 0, "L");
852 assert!(f & HE_FLAG_W != 0, "W");
853 assert!(f & HE_FLAG_U != 0, "U");
854 assert!(f & HE_FLAG_V != 0, "V");
855 assert!(f & HE_FLAG_C_MASK != 0, "C");
856 assert!(f & HE_FLAG_P != 0, "P");
857 }
858
859 #[test]
860 fn roundtrip_all_fields_set_le() {
861 let mut pl = ParameterList::new();
862 pl.push(Parameter::new(0x0015, vec![2, 5, 0, 0]));
863 let he = HeaderExtension {
864 little_endian: true,
865 message_length: Some(1234),
866 timestamp: Some(HeTimestamp {
867 seconds: 1,
868 fraction: 2,
869 }),
870 uextension4: Some([0xDE, 0xAD, 0xBE, 0xEF]),
871 wextension8: Some([0xC0; 8]),
872 checksum: ChecksumValue::Crc64(0x1122_3344_5566_7788),
873 parameters: Some(pl),
874 };
875 roundtrip(he);
876 }
877
878 #[test]
879 fn roundtrip_all_fields_set_be() {
880 let mut pl = ParameterList::new();
881 pl.push(Parameter::new(0x0050, vec![0xAA; 16]));
882 let he = HeaderExtension {
883 little_endian: false,
884 message_length: Some(99),
885 timestamp: Some(HeTimestamp {
886 seconds: -3,
887 fraction: 9,
888 }),
889 uextension4: Some([0; 4]),
890 wextension8: Some([0xFF; 8]),
891 checksum: ChecksumValue::Md5([0x77; 16]),
892 parameters: Some(pl),
893 };
894 roundtrip(he);
895 }
896
897 #[test]
898 fn decode_rejects_wrong_submessage_id() {
899 let bytes = [0x15u8, HE_FLAG_E, 0, 0];
901 let res = HeaderExtension::decode(&bytes);
902 assert!(matches!(
903 res,
904 Err(WireError::UnknownSubmessageId { id: 0x15 })
905 ));
906 }
907
908 #[test]
909 fn decode_rejects_truncated_header() {
910 let bytes = [0x80u8, HE_FLAG_E];
911 let res = HeaderExtension::decode(&bytes);
912 assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
913 }
914
915 #[test]
916 fn decode_rejects_truncated_body_before_message_length() {
917 let bytes = [
919 SUBMESSAGE_ID_HEADER_EXTENSION,
920 HE_FLAG_E | HE_FLAG_L,
921 2,
922 0,
923 0xAA,
924 0xBB,
925 ];
926 let res = HeaderExtension::decode(&bytes);
927 assert!(matches!(res, Err(WireError::UnexpectedEof { .. })));
928 }
929
930 #[test]
931 fn decode_rejects_oversized_body() {
932 let big = vec![0u8; MAX_HE_LENGTH + 1];
936 let res = HeaderExtension::decode_body(&big, HE_FLAG_E);
937 assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
938 }
939
940 #[test]
941 fn decode_rejects_trailing_bytes_without_p_flag() {
942 let body = vec![0xAA, 0xBB, 0xCC, 0xDD];
944 let res = HeaderExtension::decode_body(&body, HE_FLAG_E);
945 assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
946 }
947
948 #[test]
949 fn validate_message_length_matches() {
950 let he = HeaderExtension {
951 little_endian: true,
952 message_length: Some(42),
953 ..HeaderExtension::default()
954 };
955 assert!(he.validate_message_length(42).is_ok());
956 }
957
958 #[test]
959 fn validate_message_length_mismatch() {
960 let he = HeaderExtension {
961 little_endian: true,
962 message_length: Some(42),
963 ..HeaderExtension::default()
964 };
965 let res = he.validate_message_length(43);
966 assert!(matches!(res, Err(WireError::ValueOutOfRange { .. })));
967 }
968
969 #[test]
970 fn validate_message_length_no_l_flag_succeeds() {
971 let he = HeaderExtension::default();
972 assert!(he.validate_message_length(123).is_ok());
973 }
974
975 #[test]
976 fn checksum_value_kind_matches() {
977 assert_eq!(ChecksumValue::None.kind(), ChecksumKind::None);
978 assert_eq!(ChecksumValue::Crc32c(1).kind(), ChecksumKind::Crc32c);
979 assert_eq!(ChecksumValue::Crc64(1).kind(), ChecksumKind::Crc64);
980 assert_eq!(ChecksumValue::Md5([0; 16]).kind(), ChecksumKind::Md5);
981 }
982
983 #[test]
984 fn pid_must_understand_helpers() {
985 assert!(pid_must_understand(0x4000));
986 assert!(pid_must_understand(0x4015));
987 assert!(!pid_must_understand(0x0015));
988 assert_eq!(pid_strip(0xC015), 0x0015);
989 assert_eq!(pid_strip(0x4015), 0x0015);
990 assert_eq!(pid_strip(0x8015), 0x0015);
991 assert_eq!(pid_strip(0x0015), 0x0015);
992 }
993
994 #[test]
995 fn wire_layout_le_message_length_correct() {
996 let he = HeaderExtension {
998 little_endian: true,
999 message_length: Some(0xDEAD_BEEF),
1000 ..HeaderExtension::default()
1001 };
1002 let bytes = he.encode().unwrap();
1003 assert_eq!(bytes[0], SUBMESSAGE_ID_HEADER_EXTENSION);
1004 assert_eq!(bytes[1], HE_FLAG_E | HE_FLAG_L);
1005 assert_eq!(&bytes[2..4], &[0x04, 0x00]);
1007 assert_eq!(&bytes[4..8], &[0xEF, 0xBE, 0xAD, 0xDE]);
1009 }
1010
1011 #[test]
1012 fn wire_layout_be_message_length_correct() {
1013 let he = HeaderExtension {
1014 little_endian: false,
1015 message_length: Some(0xDEAD_BEEF),
1016 ..HeaderExtension::default()
1017 };
1018 let bytes = he.encode().unwrap();
1019 assert_eq!(bytes[1], HE_FLAG_L); assert_eq!(&bytes[2..4], &[0x00, 0x04]);
1022 assert_eq!(&bytes[4..8], &[0xDE, 0xAD, 0xBE, 0xEF]);
1023 }
1024
1025 #[test]
1026 fn empty_he_has_zero_octets_to_next_header() {
1027 let he = HeaderExtension {
1028 little_endian: true,
1029 ..HeaderExtension::default()
1030 };
1031 let bytes = he.encode().unwrap();
1032 assert_eq!(bytes.len(), 4);
1033 assert_eq!(&bytes[2..4], &[0, 0]);
1034 }
1035
1036 #[test]
1041 fn checksum_compute_none_yields_none() {
1042 let cs = ChecksumValue::compute(ChecksumKind::None, b"any payload");
1043 assert_eq!(cs, ChecksumValue::None);
1044 }
1045
1046 #[test]
1047 fn checksum_compute_crc32c_matches_rfc4960_vector() {
1048 let cs = ChecksumValue::compute(ChecksumKind::Crc32c, b"123456789");
1050 assert_eq!(cs, ChecksumValue::Crc32c(0xE306_9283));
1051 }
1052
1053 #[test]
1054 fn checksum_compute_crc64_xz_matches_known_vector() {
1055 let cs = ChecksumValue::compute(ChecksumKind::Crc64, b"123456789");
1058 assert_eq!(cs, ChecksumValue::Crc64(0x995D_C9BB_DF19_39FA));
1059 }
1060
1061 #[test]
1062 fn checksum_compute_md5_matches_rfc1321_vector() {
1063 let cs = ChecksumValue::compute(ChecksumKind::Md5, b"abc");
1065 let expected: [u8; 16] = [
1066 0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1,
1067 0x7f, 0x72,
1068 ];
1069 assert_eq!(cs, ChecksumValue::Md5(expected));
1070 }
1071
1072 #[test]
1073 fn checksum_verify_round_trip_crc32c_succeeds() {
1074 let payload = b"the quick brown fox jumps over the lazy dog";
1075 let cs = ChecksumValue::compute(ChecksumKind::Crc32c, payload);
1076 assert!(cs.verify(payload));
1077 }
1078
1079 #[test]
1080 fn checksum_verify_detects_tampered_payload() {
1081 let original = b"original message";
1082 let cs = ChecksumValue::compute(ChecksumKind::Crc32c, original);
1083 let tampered = b"original Message";
1084 assert!(!cs.verify(tampered));
1085 }
1086
1087 #[test]
1088 fn checksum_verify_none_kind_always_succeeds() {
1089 let cs = ChecksumValue::None;
1093 assert!(cs.verify(b"any payload"));
1094 assert!(cs.verify(b""));
1095 }
1096
1097 #[test]
1098 fn checksum_verify_crc64_round_trip() {
1099 let payload = b"\x52\x54\x50\x53\x02\x05\x01\x0F";
1100 let cs = ChecksumValue::compute(ChecksumKind::Crc64, payload);
1101 assert!(cs.verify(payload));
1102 }
1103
1104 #[test]
1105 fn checksum_verify_md5_round_trip() {
1106 let payload = b"DDSI-RTPS 2.5 HEADER_EXTENSION";
1107 let cs = ChecksumValue::compute(ChecksumKind::Md5, payload);
1108 assert!(cs.verify(payload));
1109 }
1110}