1use alloc::vec::Vec;
2use core::fmt;
3
4use crate::constants;
5
6#[derive(Debug)]
7pub enum PacketError {
8 TooShort,
9 ExceedsMtu,
10 MissingTransportId,
11 InvalidHeaderType,
12 ZeroLengthData,
13 InvalidHopCount(u8),
15}
16
17impl fmt::Display for PacketError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 PacketError::TooShort => write!(f, "Packet too short"),
21 PacketError::ExceedsMtu => write!(f, "Packet exceeds MTU"),
22 PacketError::MissingTransportId => write!(f, "HEADER_2 requires transport_id"),
23 PacketError::InvalidHeaderType => write!(f, "Invalid header type"),
24 PacketError::ZeroLengthData => write!(f, "Zero-length data field"),
25 PacketError::InvalidHopCount(hops) => write!(f, "Invalid hop count: {}", hops),
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct PacketFlags {
36 pub header_type: u8,
37 pub context_flag: u8,
38 pub transport_type: u8,
39 pub destination_type: u8,
40 pub packet_type: u8,
41}
42
43impl PacketFlags {
44 pub fn pack(&self) -> u8 {
55 (self.header_type << 6)
56 | (self.context_flag << 5)
57 | (self.transport_type << 4)
58 | (self.destination_type << 2)
59 | self.packet_type
60 }
61
62 pub fn unpack(byte: u8) -> Self {
64 PacketFlags {
65 header_type: (byte & 0b01000000) >> 6,
66 context_flag: (byte & 0b00100000) >> 5,
67 transport_type: (byte & 0b00010000) >> 4,
68 destination_type: (byte & 0b00001100) >> 2,
69 packet_type: byte & 0b00000011,
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
79pub struct RawPacket {
80 pub flags: PacketFlags,
81 pub hops: u8,
82 pub transport_id: Option<[u8; 16]>,
83 pub destination_hash: [u8; 16],
84 pub context: u8,
85 pub raw: Vec<u8>,
86 pub packet_hash: [u8; 32],
87 pub rssi: Option<i16>,
88 pub snr: Option<f32>,
89}
90
91impl RawPacket {
92 #[inline]
94 pub const fn data_offset(header_type: u8) -> usize {
95 if header_type == constants::HEADER_2 {
96 2 + 2 * (constants::TRUNCATED_HASHLENGTH / 8) + 1
97 } else {
98 2 + constants::TRUNCATED_HASHLENGTH / 8 + 1
99 }
100 }
101
102 #[inline]
104 pub fn data(&self) -> &[u8] {
105 &self.raw[Self::data_offset(self.flags.header_type)..]
106 }
107
108 #[inline]
114 pub fn is_link_resource_part(&self) -> bool {
115 Self::is_link_resource_part_header(&self.flags, self.context)
116 }
117
118 pub fn is_link_resource_part_wire(raw: &[u8]) -> bool {
121 let Some(&flags) = raw.first() else {
122 return false;
123 };
124 let flags = PacketFlags::unpack(flags);
125 raw.get(Self::data_offset(flags.header_type) - 1)
126 .is_some_and(|&context| Self::is_link_resource_part_header(&flags, context))
127 }
128
129 pub fn is_unproven_link_data(&self) -> bool {
135 self.flags.packet_type == constants::PACKET_TYPE_DATA
136 && self.flags.destination_type == constants::DESTINATION_LINK
137 && matches!(
138 self.context,
139 constants::CONTEXT_REQUEST
140 | constants::CONTEXT_RESPONSE
141 | constants::CONTEXT_RESOURCE
142 | constants::CONTEXT_RESOURCE_ADV
143 | constants::CONTEXT_RESOURCE_REQ
144 | constants::CONTEXT_RESOURCE_HMU
145 | constants::CONTEXT_RESOURCE_ICL
146 | constants::CONTEXT_RESOURCE_RCL
147 | constants::CONTEXT_KEEPALIVE
148 | constants::CONTEXT_LINKIDENTIFY
149 | constants::CONTEXT_LRRTT
150 | constants::CONTEXT_LINKCLOSE
151 )
152 }
153
154 #[inline]
155 fn is_link_resource_part_header(flags: &PacketFlags, context: u8) -> bool {
156 flags.packet_type == constants::PACKET_TYPE_DATA
157 && flags.destination_type == constants::DESTINATION_LINK
158 && context == constants::CONTEXT_RESOURCE
159 }
160
161 pub fn pack(
163 flags: PacketFlags,
164 hops: u8,
165 destination_hash: &[u8; 16],
166 transport_id: Option<&[u8; 16]>,
167 context: u8,
168 data: &[u8],
169 ) -> Result<Self, PacketError> {
170 Self::pack_with_max_mtu(
171 flags,
172 hops,
173 destination_hash,
174 transport_id,
175 context,
176 data,
177 constants::MTU,
178 )
179 }
180
181 pub fn pack_raw_with_hash(
183 flags: PacketFlags,
184 hops: u8,
185 destination_hash: &[u8; 16],
186 transport_id: Option<&[u8; 16]>,
187 context: u8,
188 data: &[u8],
189 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
190 Self::pack_raw_with_hash_with_max_mtu(
191 flags,
192 hops,
193 destination_hash,
194 transport_id,
195 context,
196 data,
197 constants::MTU,
198 )
199 }
200
201 pub fn pack_with_max_mtu(
203 flags: PacketFlags,
204 hops: u8,
205 destination_hash: &[u8; 16],
206 transport_id: Option<&[u8; 16]>,
207 context: u8,
208 data: &[u8],
209 max_mtu: usize,
210 ) -> Result<Self, PacketError> {
211 let (raw, packet_hash) = Self::pack_raw_with_hash_with_max_mtu(
212 flags,
213 hops,
214 destination_hash,
215 transport_id,
216 context,
217 data,
218 max_mtu,
219 )?;
220
221 Ok(RawPacket {
222 flags,
223 hops,
224 transport_id: transport_id.copied(),
225 destination_hash: *destination_hash,
226 context,
227 raw,
228 packet_hash,
229 rssi: None,
230 snr: None,
231 })
232 }
233
234 pub fn pack_raw_with_hash_with_max_mtu(
236 flags: PacketFlags,
237 hops: u8,
238 destination_hash: &[u8; 16],
239 transport_id: Option<&[u8; 16]>,
240 context: u8,
241 data: &[u8],
242 max_mtu: usize,
243 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
244 let raw = Self::pack_raw_with_max_mtu(
245 flags,
246 hops,
247 destination_hash,
248 transport_id,
249 context,
250 data,
251 max_mtu,
252 )?;
253 let packet_hash = Self::compute_packet_hash(flags.header_type, &raw);
254 Ok((raw, packet_hash))
255 }
256
257 pub fn pack_raw_with_max_mtu(
260 flags: PacketFlags,
261 hops: u8,
262 destination_hash: &[u8; 16],
263 transport_id: Option<&[u8; 16]>,
264 context: u8,
265 data: &[u8],
266 max_mtu: usize,
267 ) -> Result<Vec<u8>, PacketError> {
268 if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
269 return Err(PacketError::MissingTransportId);
270 }
271
272 let mut raw = Vec::new();
273 raw.push(flags.pack());
274 raw.push(hops);
275
276 if let Some(transport_id) = transport_id {
277 if flags.header_type == constants::HEADER_2 {
278 raw.extend_from_slice(transport_id);
279 }
280 }
281
282 raw.extend_from_slice(destination_hash);
283 raw.push(context);
284 raw.extend_from_slice(data);
285
286 if raw.len() > max_mtu {
287 return Err(PacketError::ExceedsMtu);
288 }
289
290 Ok(raw)
291 }
292
293 pub fn unpack(raw: &[u8]) -> Result<Self, PacketError> {
295 Self::unpack_with::<true, _>(raw)
296 }
297
298 pub fn unpack_owned(raw: Vec<u8>) -> Result<Self, PacketError> {
301 Self::unpack_with::<true, _>(raw)
302 }
303
304 pub(crate) fn unpack_unhashed(raw: Vec<u8>) -> Result<Self, PacketError> {
308 Self::unpack_with::<false, _>(raw)
309 }
310
311 pub fn unpack_outbound(raw: Vec<u8>) -> Result<Self, PacketError> {
315 let mut packet = Self::unpack_unhashed(raw)?;
316 if !packet.is_link_resource_part() {
317 packet.hash_in_place();
318 }
319 Ok(packet)
320 }
321
322 pub fn unpack_prehashed(raw: &[u8], packet_hash: [u8; 32]) -> Result<Self, PacketError> {
326 let mut packet = Self::unpack_with::<false, _>(raw)?;
327 packet.packet_hash = packet_hash;
328 Ok(packet)
329 }
330
331 fn unpack_with<const HASH: bool, B>(input: B) -> Result<Self, PacketError>
332 where
333 B: AsRef<[u8]> + Into<Vec<u8>>,
334 {
335 let raw = input.as_ref();
336 if raw.len() < constants::HEADER_MINSIZE {
337 return Err(PacketError::TooShort);
338 }
339
340 let flags = PacketFlags::unpack(raw[0]);
341 let hops = raw[1];
342 if hops >= constants::PATHFINDER_M {
343 return Err(PacketError::InvalidHopCount(hops));
344 }
345
346 let dst_len = constants::TRUNCATED_HASHLENGTH / 8; if flags.header_type == constants::HEADER_2 {
349 let min_len = 2 + dst_len * 2 + 1;
351 if raw.len() < min_len {
352 return Err(PacketError::TooShort);
353 }
354
355 let mut transport_id = [0u8; 16];
356 transport_id.copy_from_slice(&raw[2..2 + dst_len]);
357
358 let mut destination_hash = [0u8; 16];
359 destination_hash.copy_from_slice(&raw[2 + dst_len..2 + 2 * dst_len]);
360
361 let context = raw[2 + 2 * dst_len];
362 if raw.len() == 2 + 2 * dst_len + 1 {
363 return Err(PacketError::ZeroLengthData);
364 }
365
366 let packet_hash = if HASH {
367 Self::compute_packet_hash(flags.header_type, raw)
368 } else {
369 [0; 32]
370 };
371
372 Ok(RawPacket {
373 flags,
374 hops,
375 transport_id: Some(transport_id),
376 destination_hash,
377 context,
378 raw: input.into(),
379 packet_hash,
380 rssi: None,
381 snr: None,
382 })
383 } else if flags.header_type == constants::HEADER_1 {
384 let min_len = 2 + dst_len + 1;
386 if raw.len() < min_len {
387 return Err(PacketError::TooShort);
388 }
389
390 let mut destination_hash = [0u8; 16];
391 destination_hash.copy_from_slice(&raw[2..2 + dst_len]);
392
393 let context = raw[2 + dst_len];
394 if raw.len() == 2 + dst_len + 1 {
395 return Err(PacketError::ZeroLengthData);
396 }
397
398 let packet_hash = if HASH {
399 Self::compute_packet_hash(flags.header_type, raw)
400 } else {
401 [0; 32]
402 };
403
404 Ok(RawPacket {
405 flags,
406 hops,
407 transport_id: None,
408 destination_hash,
409 context,
410 raw: input.into(),
411 packet_hash,
412 rssi: None,
413 snr: None,
414 })
415 } else {
416 Err(PacketError::InvalidHeaderType)
417 }
418 }
419
420 pub fn get_hashable_part(&self) -> Vec<u8> {
427 Self::compute_hashable_part(self.flags.header_type, &self.raw)
428 }
429
430 fn hashable_tail(header_type: u8, raw: &[u8]) -> &[u8] {
431 if header_type == constants::HEADER_2 {
432 &raw[(constants::TRUNCATED_HASHLENGTH / 8 + 2)..]
434 } else {
435 &raw[2..]
436 }
437 }
438
439 fn compute_hashable_part(header_type: u8, raw: &[u8]) -> Vec<u8> {
440 let tail = Self::hashable_tail(header_type, raw);
441 let mut hashable = Vec::with_capacity(1 + tail.len());
442 hashable.push(raw[0] & 0b00001111);
443 hashable.extend_from_slice(tail);
444 hashable
445 }
446
447 fn compute_packet_hash(header_type: u8, raw: &[u8]) -> [u8; 32] {
450 let mut hasher = rns_crypto::sha256::Sha256::new();
451 hasher.update(&[raw[0] & 0b00001111]);
452 hasher.update(Self::hashable_tail(header_type, raw));
453 hasher.digest()
454 }
455
456 pub(crate) fn hash_in_place(&mut self) {
458 self.packet_hash = Self::compute_packet_hash(self.flags.header_type, &self.raw);
459 }
460
461 pub fn get_hash(&self) -> [u8; 32] {
463 self.packet_hash
464 }
465
466 pub fn get_truncated_hash(&self) -> [u8; 16] {
471 let mut result = [0u8; 16];
472 result.copy_from_slice(&self.packet_hash[..16]);
473 result
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn unpack_prehashed_matches_unpack_but_keeps_the_given_hash() {
483 let flags = PacketFlags {
484 header_type: constants::HEADER_1,
485 context_flag: constants::FLAG_UNSET,
486 transport_type: constants::TRANSPORT_BROADCAST,
487 destination_type: constants::DESTINATION_LINK,
488 packet_type: constants::PACKET_TYPE_DATA,
489 };
490 let packet = RawPacket::pack(
491 flags,
492 3,
493 &[0x42; 16],
494 None,
495 constants::CONTEXT_RESOURCE,
496 &[9; 300],
497 )
498 .unwrap();
499 let full = RawPacket::unpack(&packet.raw).unwrap();
500 let known = RawPacket::unpack_prehashed(&packet.raw, full.packet_hash).unwrap();
501 assert_eq!(known.raw, full.raw);
502 assert_eq!(known.packet_hash, full.packet_hash);
503 assert_eq!(known.destination_hash, full.destination_hash);
504 assert_eq!((known.hops, known.context), (full.hops, full.context));
505 assert_eq!(known.data(), full.data());
506 let other = RawPacket::unpack_prehashed(&packet.raw, [7; 32]).unwrap();
508 assert_eq!(other.packet_hash, [7; 32]);
509 assert_eq!(
511 format!(
512 "{:?}",
513 RawPacket::unpack_prehashed(&packet.raw[..10], [0; 32]).unwrap_err()
514 ),
515 format!("{:?}", RawPacket::unpack(&packet.raw[..10]).unwrap_err())
516 );
517 }
518
519 #[test]
520 fn only_link_resource_parts_are_sent_unhashed() {
521 let cases = [
522 (
523 constants::DESTINATION_LINK,
524 constants::PACKET_TYPE_DATA,
525 constants::CONTEXT_RESOURCE,
526 true,
527 ),
528 (
529 constants::DESTINATION_LINK,
530 constants::PACKET_TYPE_DATA,
531 constants::CONTEXT_CHANNEL,
532 false,
533 ),
534 (
535 constants::DESTINATION_LINK,
536 constants::PACKET_TYPE_DATA,
537 constants::CONTEXT_RESOURCE_REQ,
538 false,
539 ),
540 (
541 constants::DESTINATION_LINK,
542 constants::PACKET_TYPE_PROOF,
543 constants::CONTEXT_RESOURCE,
544 false,
545 ),
546 (
547 constants::DESTINATION_SINGLE,
548 constants::PACKET_TYPE_DATA,
549 constants::CONTEXT_RESOURCE,
550 false,
551 ),
552 ];
553 for header_type in [constants::HEADER_1, constants::HEADER_2] {
554 for (destination_type, packet_type, context, part) in cases {
555 let flags = PacketFlags {
556 header_type,
557 context_flag: constants::FLAG_UNSET,
558 transport_type: constants::TRANSPORT_BROADCAST,
559 destination_type,
560 packet_type,
561 };
562 let transport_id = (header_type == constants::HEADER_2).then_some([0x11; 16]);
563 let packet = RawPacket::pack(
564 flags,
565 0,
566 &[0x42; 16],
567 transport_id.as_ref(),
568 context,
569 &[9; 300],
570 )
571 .unwrap();
572 assert_eq!(packet.is_link_resource_part(), part);
573 assert_eq!(RawPacket::is_link_resource_part_wire(&packet.raw), part);
574 let sent = RawPacket::unpack_outbound(packet.raw.clone()).unwrap();
575 assert_eq!(sent.raw, packet.raw);
576 let expected = if part { [0; 32] } else { packet.packet_hash };
577 assert_eq!(sent.packet_hash, expected);
578 }
579 }
580 let part = RawPacket::pack(
582 PacketFlags {
583 header_type: constants::HEADER_1,
584 context_flag: constants::FLAG_UNSET,
585 transport_type: constants::TRANSPORT_BROADCAST,
586 destination_type: constants::DESTINATION_LINK,
587 packet_type: constants::PACKET_TYPE_DATA,
588 },
589 0,
590 &[0x42; 16],
591 None,
592 constants::CONTEXT_RESOURCE,
593 &[9; 4],
594 )
595 .unwrap();
596 let context_at = RawPacket::data_offset(constants::HEADER_1) - 1;
597 assert!(RawPacket::is_link_resource_part_wire(
598 &part.raw[..=context_at]
599 ));
600 assert!(!RawPacket::is_link_resource_part_wire(
601 &part.raw[..context_at]
602 ));
603 assert!(!RawPacket::is_link_resource_part_wire(&[]));
604 }
605
606 #[test]
607 fn only_plain_link_data_and_channel_messages_await_proofs() {
608 let packet = |destination_type, packet_type, context| {
609 RawPacket::pack(
610 PacketFlags {
611 header_type: constants::HEADER_1,
612 context_flag: constants::FLAG_UNSET,
613 transport_type: constants::TRANSPORT_BROADCAST,
614 destination_type,
615 packet_type,
616 },
617 0,
618 &[0x42; 16],
619 None,
620 context,
621 b"payload",
622 )
623 .unwrap()
624 };
625 let link_data = |context| {
626 packet(
627 constants::DESTINATION_LINK,
628 constants::PACKET_TYPE_DATA,
629 context,
630 )
631 };
632 for context in [constants::CONTEXT_NONE, constants::CONTEXT_CHANNEL] {
633 assert!(
634 !link_data(context).is_unproven_link_data(),
635 "context {context}"
636 );
637 }
638 for context in [
639 constants::CONTEXT_REQUEST,
640 constants::CONTEXT_RESPONSE,
641 constants::CONTEXT_RESOURCE,
642 constants::CONTEXT_RESOURCE_ADV,
643 constants::CONTEXT_RESOURCE_REQ,
644 constants::CONTEXT_RESOURCE_HMU,
645 constants::CONTEXT_RESOURCE_ICL,
646 constants::CONTEXT_RESOURCE_RCL,
647 constants::CONTEXT_KEEPALIVE,
648 constants::CONTEXT_LINKIDENTIFY,
649 constants::CONTEXT_LRRTT,
650 constants::CONTEXT_LINKCLOSE,
651 ] {
652 assert!(
653 link_data(context).is_unproven_link_data(),
654 "context {context}"
655 );
656 }
657 let request = constants::CONTEXT_REQUEST;
659 assert!(!packet(
660 constants::DESTINATION_SINGLE,
661 constants::PACKET_TYPE_DATA,
662 request
663 )
664 .is_unproven_link_data());
665 assert!(!packet(
666 constants::DESTINATION_LINK,
667 constants::PACKET_TYPE_PROOF,
668 request
669 )
670 .is_unproven_link_data());
671 }
672
673 #[test]
674 fn raw_only_packing_preserves_wire_layout_and_limits() {
675 for header in [constants::HEADER_1, constants::HEADER_2] {
676 for transport in [None, Some([0xBB; 16])] {
677 for len in [0, 1, 464, 16348] {
678 let flags = PacketFlags {
679 header_type: header,
680 context_flag: constants::FLAG_UNSET,
681 transport_type: constants::TRANSPORT_BROADCAST,
682 destination_type: constants::DESTINATION_LINK,
683 packet_type: constants::PACKET_TYPE_DATA,
684 };
685 let data = vec![0xCC; len];
686 let mut expected = vec![flags.pack(), 7];
687 if header == constants::HEADER_2 {
688 expected.extend_from_slice(&[0xBB; 16]);
689 }
690 expected.extend_from_slice(&[0xAA; 16]);
691 expected.push(constants::CONTEXT_RESOURCE);
692 expected.extend_from_slice(&data);
693 for limit in [0, expected.len() - 1, expected.len(), expected.len() + 1] {
694 let result = RawPacket::pack_raw_with_max_mtu(
695 flags,
696 7,
697 &[0xAA; 16],
698 transport.as_ref(),
699 constants::CONTEXT_RESOURCE,
700 &data,
701 limit,
702 );
703 if header == constants::HEADER_2 && transport.is_none() {
704 assert!(matches!(result, Err(PacketError::MissingTransportId)));
705 } else if limit < expected.len() {
706 assert!(matches!(result, Err(PacketError::ExceedsMtu)));
707 } else {
708 assert_eq!(result.unwrap(), expected);
709 let (raw, hash) = RawPacket::pack_raw_with_hash_with_max_mtu(
710 flags,
711 7,
712 &[0xAA; 16],
713 transport.as_ref(),
714 constants::CONTEXT_RESOURCE,
715 &data,
716 limit,
717 )
718 .unwrap();
719 assert_eq!(raw, expected);
720 if len == 0 {
721 assert!(matches!(
722 RawPacket::unpack(&raw),
723 Err(PacketError::ZeroLengthData)
724 ));
725 } else {
726 assert_eq!(RawPacket::unpack(&raw).unwrap().packet_hash, hash);
727 }
728 }
729 }
730 }
731 }
732 }
733 }
734
735 #[test]
736 fn test_flags_pack_header1_data_single_broadcast() {
737 let flags = PacketFlags {
738 header_type: constants::HEADER_1,
739 context_flag: constants::FLAG_UNSET,
740 transport_type: constants::TRANSPORT_BROADCAST,
741 destination_type: constants::DESTINATION_SINGLE,
742 packet_type: constants::PACKET_TYPE_DATA,
743 };
744 assert_eq!(flags.pack(), 0x00);
745 }
746
747 #[test]
748 fn test_flags_pack_header2_announce_single_transport() {
749 let flags = PacketFlags {
750 header_type: constants::HEADER_2,
751 context_flag: constants::FLAG_UNSET,
752 transport_type: constants::TRANSPORT_TRANSPORT,
753 destination_type: constants::DESTINATION_SINGLE,
754 packet_type: constants::PACKET_TYPE_ANNOUNCE,
755 };
756 assert_eq!(flags.pack(), 0x51);
758 }
759
760 #[test]
761 fn test_flags_roundtrip() {
762 for byte in 0..=0x7Fu8 {
763 let flags = PacketFlags::unpack(byte);
764 assert_eq!(flags.pack(), byte);
765 }
766 }
767
768 #[test]
769 fn test_pack_header1() {
770 let dest_hash = [0xAA; 16];
771 let data = b"hello";
772 let flags = PacketFlags {
773 header_type: constants::HEADER_1,
774 context_flag: constants::FLAG_UNSET,
775 transport_type: constants::TRANSPORT_BROADCAST,
776 destination_type: constants::DESTINATION_SINGLE,
777 packet_type: constants::PACKET_TYPE_DATA,
778 };
779
780 let pkt =
781 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, data).unwrap();
782
783 assert_eq!(pkt.raw.len(), 24);
785 assert_eq!(pkt.raw[0], 0x00); assert_eq!(pkt.raw[1], 0x00); assert_eq!(&pkt.raw[2..18], &dest_hash); assert_eq!(pkt.raw[18], 0x00); assert_eq!(&pkt.raw[19..], b"hello"); }
791
792 #[test]
793 fn test_pack_header2() {
794 let dest_hash = [0xAA; 16];
795 let transport_id = [0xBB; 16];
796 let data = b"world";
797 let flags = PacketFlags {
798 header_type: constants::HEADER_2,
799 context_flag: constants::FLAG_UNSET,
800 transport_type: constants::TRANSPORT_TRANSPORT,
801 destination_type: constants::DESTINATION_SINGLE,
802 packet_type: constants::PACKET_TYPE_ANNOUNCE,
803 };
804
805 let pkt = RawPacket::pack(
806 flags,
807 3,
808 &dest_hash,
809 Some(&transport_id),
810 constants::CONTEXT_NONE,
811 data,
812 )
813 .unwrap();
814
815 assert_eq!(pkt.raw.len(), 40);
817 assert_eq!(pkt.raw[0], flags.pack());
818 assert_eq!(pkt.raw[1], 3);
819 assert_eq!(&pkt.raw[2..18], &transport_id);
820 assert_eq!(&pkt.raw[18..34], &dest_hash);
821 assert_eq!(pkt.raw[34], 0x00);
822 assert_eq!(&pkt.raw[35..], b"world");
823 }
824
825 #[test]
826 fn test_unpack_roundtrip_header1() {
827 let dest_hash = [0x11; 16];
828 let data = b"test data";
829 let flags = PacketFlags {
830 header_type: constants::HEADER_1,
831 context_flag: constants::FLAG_UNSET,
832 transport_type: constants::TRANSPORT_BROADCAST,
833 destination_type: constants::DESTINATION_SINGLE,
834 packet_type: constants::PACKET_TYPE_DATA,
835 };
836
837 let pkt = RawPacket::pack(
838 flags,
839 5,
840 &dest_hash,
841 None,
842 constants::CONTEXT_RESOURCE,
843 data,
844 )
845 .unwrap();
846 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
847
848 assert_eq!(unpacked.flags, flags);
849 assert_eq!(unpacked.hops, 5);
850 assert!(unpacked.transport_id.is_none());
851 assert_eq!(unpacked.destination_hash, dest_hash);
852 assert_eq!(unpacked.context, constants::CONTEXT_RESOURCE);
853 assert_eq!(unpacked.data(), data);
854 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
855 }
856
857 #[test]
858 fn test_unpack_roundtrip_header2() {
859 let dest_hash = [0x22; 16];
860 let transport_id = [0x33; 16];
861 let data = b"transported";
862 let flags = PacketFlags {
863 header_type: constants::HEADER_2,
864 context_flag: constants::FLAG_SET,
865 transport_type: constants::TRANSPORT_TRANSPORT,
866 destination_type: constants::DESTINATION_SINGLE,
867 packet_type: constants::PACKET_TYPE_ANNOUNCE,
868 };
869
870 let pkt = RawPacket::pack(
871 flags,
872 2,
873 &dest_hash,
874 Some(&transport_id),
875 constants::CONTEXT_NONE,
876 data,
877 )
878 .unwrap();
879 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
880
881 assert_eq!(unpacked.flags, flags);
882 assert_eq!(unpacked.hops, 2);
883 assert_eq!(unpacked.transport_id.unwrap(), transport_id);
884 assert_eq!(unpacked.destination_hash, dest_hash);
885 assert_eq!(unpacked.context, constants::CONTEXT_NONE);
886 assert_eq!(unpacked.data(), data);
887 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
888 }
889
890 #[test]
891 fn truncated_hash_is_derived_from_cached_packet_hash() {
892 let mut packet = RawPacket::pack(
893 PacketFlags {
894 header_type: constants::HEADER_1,
895 context_flag: constants::FLAG_UNSET,
896 transport_type: constants::TRANSPORT_BROADCAST,
897 destination_type: constants::DESTINATION_SINGLE,
898 packet_type: constants::PACKET_TYPE_DATA,
899 },
900 1,
901 &[0x42; 16],
902 None,
903 constants::CONTEXT_NONE,
904 b"cached hash",
905 )
906 .unwrap();
907 let expected: [u8; 16] = packet.packet_hash[..16].try_into().unwrap();
908
909 let last = packet.raw.len() - 1;
912 packet.raw[last] ^= 0xff;
913
914 assert_eq!(packet.get_truncated_hash(), expected);
915 }
916
917 #[test]
918 fn test_unpack_too_short() {
919 assert!(RawPacket::unpack(&[0x00; 5]).is_err());
920 }
921
922 #[test]
923 fn unpack_rejects_zero_length_data_for_both_header_types() {
924 for header_type in [constants::HEADER_1, constants::HEADER_2] {
925 let packet = RawPacket::pack(
926 PacketFlags {
927 header_type,
928 context_flag: constants::FLAG_UNSET,
929 transport_type: constants::TRANSPORT_BROADCAST,
930 destination_type: constants::DESTINATION_SINGLE,
931 packet_type: constants::PACKET_TYPE_DATA,
932 },
933 0,
934 &[0x42; 16],
935 (header_type == constants::HEADER_2).then_some(&[0x24; 16]),
936 constants::CONTEXT_NONE,
937 b"",
938 )
939 .unwrap();
940 assert!(matches!(
941 RawPacket::unpack(&packet.raw),
942 Err(PacketError::ZeroLengthData)
943 ));
944 }
945 }
946
947 #[test]
948 fn unpack_enforces_pathfinder_hop_boundary_but_pack_remains_permissive() {
949 let flags = PacketFlags {
950 header_type: constants::HEADER_1,
951 context_flag: constants::FLAG_UNSET,
952 transport_type: constants::TRANSPORT_BROADCAST,
953 destination_type: constants::DESTINATION_SINGLE,
954 packet_type: constants::PACKET_TYPE_DATA,
955 };
956 let accepted = RawPacket::pack(flags, 127, &[1; 16], None, 0, b"x").unwrap();
957 assert_eq!(RawPacket::unpack(&accepted.raw).unwrap().hops, 127);
958
959 for hops in [128, 255] {
960 let packed = RawPacket::pack(flags, hops, &[1; 16], None, 0, b"x").unwrap();
961 assert!(matches!(
962 RawPacket::unpack(&packed.raw),
963 Err(PacketError::InvalidHopCount(value)) if value == hops
964 ));
965 }
966 }
967
968 #[test]
969 fn test_pack_exceeds_mtu() {
970 let flags = PacketFlags {
971 header_type: constants::HEADER_1,
972 context_flag: constants::FLAG_UNSET,
973 transport_type: constants::TRANSPORT_BROADCAST,
974 destination_type: constants::DESTINATION_SINGLE,
975 packet_type: constants::PACKET_TYPE_DATA,
976 };
977 let data = [0u8; 500]; let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, &data);
979 assert!(result.is_err());
980 }
981
982 #[test]
983 fn test_header2_missing_transport_id() {
984 let flags = PacketFlags {
985 header_type: constants::HEADER_2,
986 context_flag: constants::FLAG_UNSET,
987 transport_type: constants::TRANSPORT_TRANSPORT,
988 destination_type: constants::DESTINATION_SINGLE,
989 packet_type: constants::PACKET_TYPE_ANNOUNCE,
990 };
991 let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, b"data");
992 assert!(result.is_err());
993 }
994
995 #[test]
996 fn test_hashable_part_header1_masks_upper_flags() {
997 let dest_hash = [0xCC; 16];
998 let flags = PacketFlags {
999 header_type: constants::HEADER_1,
1000 context_flag: constants::FLAG_SET,
1001 transport_type: constants::TRANSPORT_BROADCAST,
1002 destination_type: constants::DESTINATION_SINGLE,
1003 packet_type: constants::PACKET_TYPE_DATA,
1004 };
1005
1006 let pkt =
1007 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, b"test").unwrap();
1008 let hashable = pkt.get_hashable_part();
1009
1010 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1012 assert_eq!(&hashable[1..], &pkt.raw[2..]);
1014 }
1015
1016 #[test]
1017 fn test_hashable_part_header2_strips_transport_id() {
1018 let dest_hash = [0xDD; 16];
1019 let transport_id = [0xEE; 16];
1020 let flags = PacketFlags {
1021 header_type: constants::HEADER_2,
1022 context_flag: constants::FLAG_UNSET,
1023 transport_type: constants::TRANSPORT_TRANSPORT,
1024 destination_type: constants::DESTINATION_SINGLE,
1025 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1026 };
1027
1028 let pkt = RawPacket::pack(
1029 flags,
1030 0,
1031 &dest_hash,
1032 Some(&transport_id),
1033 constants::CONTEXT_NONE,
1034 b"data",
1035 )
1036 .unwrap();
1037 let hashable = pkt.get_hashable_part();
1038
1039 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1041 assert_eq!(&hashable[1..], &pkt.raw[18..]);
1043 }
1044}