1use alloc::sync::Arc;
2use alloc::vec::Vec;
3use core::fmt;
4
5use crate::constants;
6use crate::transport::types::PacketBytes;
7
8pub trait PacketStorage: AsRef<[u8]> {
11 fn to_shared(&self) -> PacketBytes;
12}
13
14impl PacketStorage for Vec<u8> {
15 fn to_shared(&self) -> PacketBytes {
16 PacketBytes::from(self.as_slice())
17 }
18}
19
20impl PacketStorage for &[u8] {
21 fn to_shared(&self) -> PacketBytes {
22 PacketBytes::from(*self)
23 }
24}
25
26impl PacketStorage for Arc<[u8]> {
27 fn to_shared(&self) -> PacketBytes {
28 self.clone()
29 }
30}
31
32#[derive(Debug)]
33pub enum PacketError {
34 TooShort,
35 ExceedsMtu,
36 MissingTransportId,
37 InvalidHeaderType,
38 ZeroLengthData,
39 InvalidHopCount(u8),
44}
45
46impl fmt::Display for PacketError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 PacketError::TooShort => write!(f, "Packet too short"),
50 PacketError::ExceedsMtu => write!(f, "Packet exceeds MTU"),
51 PacketError::MissingTransportId => write!(f, "HEADER_2 requires transport_id"),
52 PacketError::InvalidHeaderType => write!(f, "Invalid header type"),
53 PacketError::ZeroLengthData => write!(f, "Zero-length data field"),
54 PacketError::InvalidHopCount(hops) => write!(f, "Invalid hop count: {}", hops),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct PacketFlags {
65 pub header_type: u8,
66 pub context_flag: u8,
67 pub transport_type: u8,
68 pub destination_type: u8,
69 pub packet_type: u8,
70}
71
72impl PacketFlags {
73 pub fn pack(&self) -> u8 {
84 (self.header_type << 6)
85 | (self.context_flag << 5)
86 | (self.transport_type << 4)
87 | (self.destination_type << 2)
88 | self.packet_type
89 }
90
91 pub fn unpack(byte: u8) -> Self {
93 PacketFlags {
94 header_type: (byte & 0b01000000) >> 6,
95 context_flag: (byte & 0b00100000) >> 5,
96 transport_type: (byte & 0b00010000) >> 4,
97 destination_type: (byte & 0b00001100) >> 2,
98 packet_type: byte & 0b00000011,
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
111pub struct RawPacket<B = Vec<u8>> {
112 pub flags: PacketFlags,
113 pub hops: u8,
114 pub transport_id: Option<[u8; 16]>,
115 pub destination_hash: [u8; 16],
116 pub context: u8,
117 pub raw: B,
118 pub packet_hash: [u8; 32],
119 pub rssi: Option<i16>,
120 pub snr: Option<f32>,
121}
122
123impl RawPacket {
124 #[inline]
126 pub const fn data_offset(header_type: u8) -> usize {
127 if header_type == constants::HEADER_2 {
128 2 + 2 * (constants::TRUNCATED_HASHLENGTH / 8) + 1
129 } else {
130 2 + constants::TRUNCATED_HASHLENGTH / 8 + 1
131 }
132 }
133
134 pub fn is_unhashed_link_resource_wire(raw: &[u8]) -> bool {
137 let Some(&flags) = raw.first() else {
138 return false;
139 };
140 let flags = PacketFlags::unpack(flags);
141 raw.get(Self::data_offset(flags.header_type) - 1)
142 .is_some_and(|&context| Self::is_unhashed_link_resource_header(&flags, context))
143 }
144
145 #[inline]
146 fn is_unhashed_link_resource_header(flags: &PacketFlags, context: u8) -> bool {
147 flags.destination_type == constants::DESTINATION_LINK
148 && match context {
149 constants::CONTEXT_RESOURCE | constants::CONTEXT_RESOURCE_REQ => {
150 flags.packet_type == constants::PACKET_TYPE_DATA
151 }
152 constants::CONTEXT_RESOURCE_PRF => {
153 flags.packet_type == constants::PACKET_TYPE_PROOF
154 }
155 _ => false,
156 }
157 }
158
159 pub fn pack(
161 flags: PacketFlags,
162 hops: u8,
163 destination_hash: &[u8; 16],
164 transport_id: Option<&[u8; 16]>,
165 context: u8,
166 data: &[u8],
167 ) -> Result<Self, PacketError> {
168 Self::pack_with_max_mtu(
169 flags,
170 hops,
171 destination_hash,
172 transport_id,
173 context,
174 data,
175 constants::MTU,
176 )
177 }
178
179 pub fn pack_raw_with_hash(
181 flags: PacketFlags,
182 hops: u8,
183 destination_hash: &[u8; 16],
184 transport_id: Option<&[u8; 16]>,
185 context: u8,
186 data: &[u8],
187 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
188 Self::pack_raw_with_hash_with_max_mtu(
189 flags,
190 hops,
191 destination_hash,
192 transport_id,
193 context,
194 data,
195 constants::MTU,
196 )
197 }
198
199 pub fn pack_with_max_mtu(
201 flags: PacketFlags,
202 hops: u8,
203 destination_hash: &[u8; 16],
204 transport_id: Option<&[u8; 16]>,
205 context: u8,
206 data: &[u8],
207 max_mtu: usize,
208 ) -> Result<Self, PacketError> {
209 let (raw, packet_hash) = Self::pack_raw_with_hash_with_max_mtu(
210 flags,
211 hops,
212 destination_hash,
213 transport_id,
214 context,
215 data,
216 max_mtu,
217 )?;
218
219 Ok(RawPacket {
220 flags,
221 hops,
222 transport_id: transport_id.copied(),
223 destination_hash: *destination_hash,
224 context,
225 raw,
226 packet_hash,
227 rssi: None,
228 snr: None,
229 })
230 }
231
232 pub fn pack_raw_with_hash_with_max_mtu(
234 flags: PacketFlags,
235 hops: u8,
236 destination_hash: &[u8; 16],
237 transport_id: Option<&[u8; 16]>,
238 context: u8,
239 data: &[u8],
240 max_mtu: usize,
241 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
242 let raw = Self::pack_raw_with_max_mtu(
243 flags,
244 hops,
245 destination_hash,
246 transport_id,
247 context,
248 data,
249 max_mtu,
250 )?;
251 let packet_hash = Self::compute_packet_hash(flags.header_type, &raw);
252 Ok((raw, packet_hash))
253 }
254
255 #[allow(clippy::too_many_arguments)]
259 pub fn pack_raw_shared_with_hash_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<(PacketBytes, [u8; 32]), PacketError> {
268 let raw = Self::pack_raw_shared_with_max_mtu(
269 flags,
270 hops,
271 destination_hash,
272 transport_id,
273 context,
274 data,
275 max_mtu,
276 )?;
277 let packet_hash = Self::compute_packet_hash(flags.header_type, &raw);
278 Ok((raw, packet_hash))
279 }
280
281 pub fn packet_hash_of(header_type: u8, raw: &[u8]) -> [u8; 32] {
283 Self::compute_packet_hash(header_type, raw)
284 }
285
286 #[allow(clippy::too_many_arguments)]
291 pub fn pack_raw_shared_filled_with_max_mtu(
292 flags: PacketFlags,
293 hops: u8,
294 destination_hash: &[u8; 16],
295 transport_id: Option<&[u8; 16]>,
296 context: u8,
297 payload_len: usize,
298 max_mtu: usize,
299 fill: impl FnOnce(&mut [u8]),
300 ) -> Result<PacketBytes, PacketError> {
301 if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
302 return Err(PacketError::MissingTransportId);
303 }
304 let transport_id = transport_id.filter(|_| flags.header_type == constants::HEADER_2);
305 let header_len = 2 + transport_id.map_or(0, |id| id.len()) + destination_hash.len() + 1;
306 let len = header_len + payload_len;
307 if len > max_mtu {
308 return Err(PacketError::ExceedsMtu);
309 }
310 let mut shared = Arc::<[u8]>::new_uninit_slice(len);
311 let out = Arc::get_mut(&mut shared).expect("a new Arc is unique");
312 let out: &mut [u8] = unsafe {
315 core::ptr::write_bytes(out.as_mut_ptr(), 0, len);
316 core::slice::from_raw_parts_mut(out.as_mut_ptr().cast::<u8>(), len)
317 };
318 out[0] = flags.pack();
319 out[1] = hops;
320 let mut at = 2;
321 if let Some(transport_id) = transport_id {
322 out[at..at + 16].copy_from_slice(transport_id);
323 at += 16;
324 }
325 out[at..at + 16].copy_from_slice(destination_hash);
326 at += 16;
327 out[at] = context;
328 fill(&mut out[header_len..]);
329 Ok(unsafe { shared.assume_init() })
331 }
332
333 pub fn pack_raw_shared_with_max_mtu(
336 flags: PacketFlags,
337 hops: u8,
338 destination_hash: &[u8; 16],
339 transport_id: Option<&[u8; 16]>,
340 context: u8,
341 data: &[u8],
342 max_mtu: usize,
343 ) -> Result<PacketBytes, PacketError> {
344 if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
345 return Err(PacketError::MissingTransportId);
346 }
347 let transport_id = transport_id.filter(|_| flags.header_type == constants::HEADER_2);
348 let len =
349 2 + transport_id.map_or(0, |id| id.len()) + destination_hash.len() + 1 + data.len();
350 if len > max_mtu {
351 return Err(PacketError::ExceedsMtu);
352 }
353 let mut shared = Arc::<[u8]>::new_uninit_slice(len);
354 let out = Arc::get_mut(&mut shared).expect("a new Arc is unique");
355 let mut at = 0;
356 let mut put = |bytes: &[u8]| {
357 let slots = &mut out[at..at + bytes.len()];
358 unsafe {
361 core::ptr::copy_nonoverlapping(
362 bytes.as_ptr(),
363 slots.as_mut_ptr().cast::<u8>(),
364 bytes.len(),
365 )
366 };
367 at += bytes.len();
368 };
369 put(&[flags.pack(), hops]);
370 if let Some(transport_id) = transport_id {
371 put(transport_id);
372 }
373 put(destination_hash);
374 put(&[context]);
375 put(data);
376 assert_eq!(at, len);
377 Ok(unsafe { shared.assume_init() })
379 }
380
381 pub fn pack_raw_with_max_mtu(
382 flags: PacketFlags,
383 hops: u8,
384 destination_hash: &[u8; 16],
385 transport_id: Option<&[u8; 16]>,
386 context: u8,
387 data: &[u8],
388 max_mtu: usize,
389 ) -> Result<Vec<u8>, PacketError> {
390 if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
391 return Err(PacketError::MissingTransportId);
392 }
393
394 let transport_id = transport_id.filter(|_| flags.header_type == constants::HEADER_2);
395 let len =
397 2 + transport_id.map_or(0, |id| id.len()) + destination_hash.len() + 1 + data.len();
398 if len > max_mtu {
399 return Err(PacketError::ExceedsMtu);
400 }
401
402 let mut raw = Vec::with_capacity(len);
405 raw.push(flags.pack());
406 raw.push(hops);
407 if let Some(transport_id) = transport_id {
408 raw.extend_from_slice(transport_id);
409 }
410 raw.extend_from_slice(destination_hash);
411 raw.push(context);
412 raw.extend_from_slice(data);
413 debug_assert_eq!(raw.len(), len);
414
415 Ok(raw)
416 }
417
418 pub fn unpack(raw: &[u8]) -> Result<Self, PacketError> {
420 RawPacket::<&[u8]>::parse::<true>(raw).map(|packet| packet.map_raw(<[u8]>::to_vec))
421 }
422
423 pub fn unpack_owned(raw: Vec<u8>) -> Result<Self, PacketError> {
426 Self::parse::<true>(raw)
427 }
428
429 pub(crate) fn unpack_unhashed(raw: Vec<u8>) -> Result<Self, PacketError> {
433 Self::parse::<false>(raw)
434 }
435
436 pub fn unpack_outbound(raw: Vec<u8>) -> Result<Self, PacketError> {
441 let mut packet = Self::unpack_unhashed(raw)?;
442 if !packet.is_unhashed_link_resource() {
443 packet.hash_in_place();
444 }
445 Ok(packet)
446 }
447
448 pub fn unpack_shared(raw: PacketBytes) -> Result<RawPacket<PacketBytes>, PacketError> {
450 RawPacket::<PacketBytes>::parse::<true>(raw)
451 }
452
453 pub(crate) fn unpack_shared_unhashed(
456 raw: PacketBytes,
457 ) -> Result<RawPacket<PacketBytes>, PacketError> {
458 RawPacket::<PacketBytes>::parse::<false>(raw)
459 }
460
461 pub fn unpack_outbound_shared(raw: PacketBytes) -> Result<RawPacket<PacketBytes>, PacketError> {
463 let mut packet = RawPacket::<PacketBytes>::parse::<false>(raw)?;
464 if !packet.is_unhashed_link_resource() {
465 packet.hash_in_place();
466 }
467 Ok(packet)
468 }
469
470 pub fn unpack_prehashed(raw: &[u8], packet_hash: [u8; 32]) -> Result<Self, PacketError> {
474 RawPacket::view_prehashed(raw, packet_hash).map(|packet| packet.map_raw(<[u8]>::to_vec))
475 }
476
477 fn hashable_tail(header_type: u8, raw: &[u8]) -> &[u8] {
478 if header_type == constants::HEADER_2 {
479 &raw[(constants::TRUNCATED_HASHLENGTH / 8 + 2)..]
481 } else {
482 &raw[2..]
483 }
484 }
485
486 fn compute_hashable_part(header_type: u8, raw: &[u8]) -> Vec<u8> {
487 let tail = Self::hashable_tail(header_type, raw);
488 let mut hashable = Vec::with_capacity(1 + tail.len());
489 hashable.push(raw[0] & 0b00001111);
490 hashable.extend_from_slice(tail);
491 hashable
492 }
493
494 fn compute_packet_hash(header_type: u8, raw: &[u8]) -> [u8; 32] {
497 let mut hasher = rns_crypto::sha256::Sha256::new();
498 hasher.update(&[raw[0] & 0b00001111]);
499 hasher.update(Self::hashable_tail(header_type, raw));
500 hasher.digest()
501 }
502}
503
504impl<B: AsRef<[u8]>> RawPacket<B> {
505 #[inline]
507 pub fn data(&self) -> &[u8] {
508 &self.raw.as_ref()[<RawPacket>::data_offset(self.flags.header_type)..]
509 }
510
511 #[inline]
519 pub fn is_unhashed_link_resource(&self) -> bool {
520 <RawPacket>::is_unhashed_link_resource_header(&self.flags, self.context)
521 }
522
523 pub fn is_unproven_link_data(&self) -> bool {
529 self.flags.packet_type == constants::PACKET_TYPE_DATA
530 && self.flags.destination_type == constants::DESTINATION_LINK
531 && matches!(
532 self.context,
533 constants::CONTEXT_REQUEST
534 | constants::CONTEXT_RESPONSE
535 | constants::CONTEXT_RESOURCE
536 | constants::CONTEXT_RESOURCE_ADV
537 | constants::CONTEXT_RESOURCE_REQ
538 | constants::CONTEXT_RESOURCE_HMU
539 | constants::CONTEXT_RESOURCE_ICL
540 | constants::CONTEXT_RESOURCE_RCL
541 | constants::CONTEXT_KEEPALIVE
542 | constants::CONTEXT_LINKIDENTIFY
543 | constants::CONTEXT_LRRTT
544 | constants::CONTEXT_LINKCLOSE
545 )
546 }
547
548 pub fn get_hashable_part(&self) -> Vec<u8> {
555 <RawPacket>::compute_hashable_part(self.flags.header_type, self.raw.as_ref())
556 }
557
558 pub(crate) fn hash_in_place(&mut self) {
560 self.packet_hash =
561 <RawPacket>::compute_packet_hash(self.flags.header_type, self.raw.as_ref());
562 }
563
564 pub fn get_hash(&self) -> [u8; 32] {
566 self.packet_hash
567 }
568
569 pub fn get_truncated_hash(&self) -> [u8; 16] {
574 let mut result = [0u8; 16];
575 result.copy_from_slice(&self.packet_hash[..16]);
576 result
577 }
578 pub fn as_view(&self) -> RawPacket<&[u8]> {
580 RawPacket {
581 flags: self.flags,
582 hops: self.hops,
583 transport_id: self.transport_id,
584 destination_hash: self.destination_hash,
585 context: self.context,
586 raw: self.raw.as_ref(),
587 packet_hash: self.packet_hash,
588 rssi: self.rssi,
589 snr: self.snr,
590 }
591 }
592
593 pub fn to_owned_packet(&self) -> RawPacket {
595 self.as_view().map_raw(<[u8]>::to_vec)
596 }
597
598 pub fn map_raw<C>(self, f: impl FnOnce(B) -> C) -> RawPacket<C> {
600 RawPacket {
601 flags: self.flags,
602 hops: self.hops,
603 transport_id: self.transport_id,
604 destination_hash: self.destination_hash,
605 context: self.context,
606 raw: f(self.raw),
607 packet_hash: self.packet_hash,
608 rssi: self.rssi,
609 snr: self.snr,
610 }
611 }
612
613 fn parse<const HASH: bool>(input: B) -> Result<Self, PacketError> {
615 let raw = input.as_ref();
616 if raw.len() < constants::HEADER_MINSIZE {
617 return Err(PacketError::TooShort);
618 }
619
620 let flags = PacketFlags::unpack(raw[0]);
621 let hops = raw[1];
622 if hops >= constants::PATHFINDER_M {
623 return Err(PacketError::InvalidHopCount(hops));
624 }
625
626 let dst_len = constants::TRUNCATED_HASHLENGTH / 8; if flags.header_type == constants::HEADER_2 {
629 let min_len = 2 + dst_len * 2 + 1;
631 if raw.len() < min_len {
632 return Err(PacketError::TooShort);
633 }
634
635 let mut transport_id = [0u8; 16];
636 transport_id.copy_from_slice(&raw[2..2 + dst_len]);
637
638 let mut destination_hash = [0u8; 16];
639 destination_hash.copy_from_slice(&raw[2 + dst_len..2 + 2 * dst_len]);
640
641 let context = raw[2 + 2 * dst_len];
642 if raw.len() == 2 + 2 * dst_len + 1 {
643 return Err(PacketError::ZeroLengthData);
644 }
645
646 let packet_hash = if HASH {
647 <RawPacket>::compute_packet_hash(flags.header_type, raw)
648 } else {
649 [0; 32]
650 };
651
652 Ok(RawPacket {
653 flags,
654 hops,
655 transport_id: Some(transport_id),
656 destination_hash,
657 context,
658 raw: input,
659 packet_hash,
660 rssi: None,
661 snr: None,
662 })
663 } else if flags.header_type == constants::HEADER_1 {
664 let min_len = 2 + dst_len + 1;
666 if raw.len() < min_len {
667 return Err(PacketError::TooShort);
668 }
669
670 let mut destination_hash = [0u8; 16];
671 destination_hash.copy_from_slice(&raw[2..2 + dst_len]);
672
673 let context = raw[2 + dst_len];
674 if raw.len() == 2 + dst_len + 1 {
675 return Err(PacketError::ZeroLengthData);
676 }
677
678 let packet_hash = if HASH {
679 <RawPacket>::compute_packet_hash(flags.header_type, raw)
680 } else {
681 [0; 32]
682 };
683
684 Ok(RawPacket {
685 flags,
686 hops,
687 transport_id: None,
688 destination_hash,
689 context,
690 raw: input,
691 packet_hash,
692 rssi: None,
693 snr: None,
694 })
695 } else {
696 Err(PacketError::InvalidHeaderType)
697 }
698 }
699}
700
701impl<'a> RawPacket<&'a [u8]> {
702 pub fn view_prehashed(raw: &'a [u8], packet_hash: [u8; 32]) -> Result<Self, PacketError> {
704 let mut packet = Self::parse::<false>(raw)?;
705 packet.packet_hash = packet_hash;
706 Ok(packet)
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 #[test]
715 fn unpack_prehashed_matches_unpack_but_keeps_the_given_hash() {
716 let flags = PacketFlags {
717 header_type: constants::HEADER_1,
718 context_flag: constants::FLAG_UNSET,
719 transport_type: constants::TRANSPORT_BROADCAST,
720 destination_type: constants::DESTINATION_LINK,
721 packet_type: constants::PACKET_TYPE_DATA,
722 };
723 let packet = RawPacket::pack(
724 flags,
725 3,
726 &[0x42; 16],
727 None,
728 constants::CONTEXT_RESOURCE,
729 &[9; 300],
730 )
731 .unwrap();
732 let full = RawPacket::unpack(&packet.raw).unwrap();
733 let known = RawPacket::unpack_prehashed(&packet.raw, full.packet_hash).unwrap();
734 assert_eq!(known.raw, full.raw);
735 assert_eq!(known.packet_hash, full.packet_hash);
736 assert_eq!(known.destination_hash, full.destination_hash);
737 assert_eq!((known.hops, known.context), (full.hops, full.context));
738 assert_eq!(known.data(), full.data());
739 let other = RawPacket::unpack_prehashed(&packet.raw, [7; 32]).unwrap();
741 assert_eq!(other.packet_hash, [7; 32]);
742 assert_eq!(
744 format!(
745 "{:?}",
746 RawPacket::unpack_prehashed(&packet.raw[..10], [0; 32]).unwrap_err()
747 ),
748 format!("{:?}", RawPacket::unpack(&packet.raw[..10]).unwrap_err())
749 );
750 }
751
752 #[test]
753 fn only_link_resource_transfers_are_sent_unhashed() {
754 let cases = [
755 (
756 constants::DESTINATION_LINK,
757 constants::PACKET_TYPE_DATA,
758 constants::CONTEXT_RESOURCE,
759 true,
760 ),
761 (
762 constants::DESTINATION_LINK,
763 constants::PACKET_TYPE_DATA,
764 constants::CONTEXT_CHANNEL,
765 false,
766 ),
767 (
768 constants::DESTINATION_LINK,
769 constants::PACKET_TYPE_DATA,
770 constants::CONTEXT_RESOURCE_REQ,
771 true,
772 ),
773 (
774 constants::DESTINATION_LINK,
775 constants::PACKET_TYPE_PROOF,
776 constants::CONTEXT_RESOURCE_PRF,
777 true,
778 ),
779 (
780 constants::DESTINATION_LINK,
781 constants::PACKET_TYPE_DATA,
782 constants::CONTEXT_RESOURCE_PRF,
783 false,
784 ),
785 (
786 constants::DESTINATION_LINK,
787 constants::PACKET_TYPE_DATA,
788 constants::CONTEXT_RESOURCE_HMU,
789 false,
790 ),
791 (
792 constants::DESTINATION_LINK,
793 constants::PACKET_TYPE_PROOF,
794 constants::CONTEXT_RESOURCE,
795 false,
796 ),
797 (
798 constants::DESTINATION_LINK,
799 constants::PACKET_TYPE_PROOF,
800 constants::CONTEXT_NONE,
801 false,
802 ),
803 (
804 constants::DESTINATION_SINGLE,
805 constants::PACKET_TYPE_DATA,
806 constants::CONTEXT_RESOURCE,
807 false,
808 ),
809 ];
810 for header_type in [constants::HEADER_1, constants::HEADER_2] {
811 for (destination_type, packet_type, context, part) in cases {
812 let flags = PacketFlags {
813 header_type,
814 context_flag: constants::FLAG_UNSET,
815 transport_type: constants::TRANSPORT_BROADCAST,
816 destination_type,
817 packet_type,
818 };
819 let transport_id = (header_type == constants::HEADER_2).then_some([0x11; 16]);
820 let packet = RawPacket::pack(
821 flags,
822 0,
823 &[0x42; 16],
824 transport_id.as_ref(),
825 context,
826 &[9; 300],
827 )
828 .unwrap();
829 assert_eq!(packet.is_unhashed_link_resource(), part);
830 assert_eq!(RawPacket::is_unhashed_link_resource_wire(&packet.raw), part);
831 let sent = RawPacket::unpack_outbound(packet.raw.clone()).unwrap();
832 assert_eq!(sent.raw, packet.raw);
833 let expected = if part { [0; 32] } else { packet.packet_hash };
834 assert_eq!(sent.packet_hash, expected);
835 }
836 }
837 let part = RawPacket::pack(
839 PacketFlags {
840 header_type: constants::HEADER_1,
841 context_flag: constants::FLAG_UNSET,
842 transport_type: constants::TRANSPORT_BROADCAST,
843 destination_type: constants::DESTINATION_LINK,
844 packet_type: constants::PACKET_TYPE_DATA,
845 },
846 0,
847 &[0x42; 16],
848 None,
849 constants::CONTEXT_RESOURCE,
850 &[9; 4],
851 )
852 .unwrap();
853 let context_at = RawPacket::data_offset(constants::HEADER_1) - 1;
854 assert!(RawPacket::is_unhashed_link_resource_wire(
855 &part.raw[..=context_at]
856 ));
857 assert!(!RawPacket::is_unhashed_link_resource_wire(
858 &part.raw[..context_at]
859 ));
860 assert!(!RawPacket::is_unhashed_link_resource_wire(&[]));
861 }
862
863 #[test]
864 fn only_plain_link_data_and_channel_messages_await_proofs() {
865 let packet = |destination_type, packet_type, context| {
866 RawPacket::pack(
867 PacketFlags {
868 header_type: constants::HEADER_1,
869 context_flag: constants::FLAG_UNSET,
870 transport_type: constants::TRANSPORT_BROADCAST,
871 destination_type,
872 packet_type,
873 },
874 0,
875 &[0x42; 16],
876 None,
877 context,
878 b"payload",
879 )
880 .unwrap()
881 };
882 let link_data = |context| {
883 packet(
884 constants::DESTINATION_LINK,
885 constants::PACKET_TYPE_DATA,
886 context,
887 )
888 };
889 for context in [constants::CONTEXT_NONE, constants::CONTEXT_CHANNEL] {
890 assert!(
891 !link_data(context).is_unproven_link_data(),
892 "context {context}"
893 );
894 }
895 for context in [
896 constants::CONTEXT_REQUEST,
897 constants::CONTEXT_RESPONSE,
898 constants::CONTEXT_RESOURCE,
899 constants::CONTEXT_RESOURCE_ADV,
900 constants::CONTEXT_RESOURCE_REQ,
901 constants::CONTEXT_RESOURCE_HMU,
902 constants::CONTEXT_RESOURCE_ICL,
903 constants::CONTEXT_RESOURCE_RCL,
904 constants::CONTEXT_KEEPALIVE,
905 constants::CONTEXT_LINKIDENTIFY,
906 constants::CONTEXT_LRRTT,
907 constants::CONTEXT_LINKCLOSE,
908 ] {
909 assert!(
910 link_data(context).is_unproven_link_data(),
911 "context {context}"
912 );
913 }
914 let request = constants::CONTEXT_REQUEST;
916 assert!(!packet(
917 constants::DESTINATION_SINGLE,
918 constants::PACKET_TYPE_DATA,
919 request
920 )
921 .is_unproven_link_data());
922 assert!(!packet(
923 constants::DESTINATION_LINK,
924 constants::PACKET_TYPE_PROOF,
925 request
926 )
927 .is_unproven_link_data());
928 }
929
930 #[test]
931 fn shared_packing_matches_owned_packing_and_unpacks_alike() {
932 let flags = PacketFlags {
933 header_type: constants::HEADER_1,
934 context_flag: constants::FLAG_UNSET,
935 transport_type: constants::TRANSPORT_BROADCAST,
936 destination_type: constants::DESTINATION_LINK,
937 packet_type: constants::PACKET_TYPE_DATA,
938 };
939 let header2 = PacketFlags {
940 header_type: constants::HEADER_2,
941 ..flags
942 };
943 let dest = [0x11; 16];
944 let tid = [0x22; 16];
945 for (flags, tid) in [(flags, None), (flags, Some(&tid)), (header2, Some(&tid))] {
946 for len in [1usize, 2, 15, 16, 400, 465] {
947 let data: Vec<u8> = (0..len).map(|i| i as u8).collect();
948 for mtu in [len + 18, len + 35, 500] {
949 let owned =
950 RawPacket::pack_raw_with_max_mtu(flags, 3, &dest, tid, 0x07, &data, mtu);
951 let shared = RawPacket::pack_raw_shared_with_max_mtu(
952 flags, 3, &dest, tid, 0x07, &data, mtu,
953 );
954 let filled = RawPacket::pack_raw_shared_filled_with_max_mtu(
955 flags,
956 3,
957 &dest,
958 tid,
959 0x07,
960 data.len(),
961 mtu,
962 |payload| payload.copy_from_slice(&data),
963 );
964 assert_eq!(format!("{shared:?}"), format!("{filled:?}"));
965 match (owned, shared) {
966 (Ok(owned), Ok(shared)) => {
967 assert_eq!(&owned[..], &shared[..]);
968 let a = RawPacket::unpack_outbound(owned).unwrap();
969 let b = RawPacket::unpack_outbound_shared(shared).unwrap();
970 assert_eq!(a.packet_hash, b.packet_hash);
971 assert_eq!(a.data(), b.data());
972 }
973 (Err(PacketError::ExceedsMtu), Err(PacketError::ExceedsMtu)) => {}
974 other => panic!("diverged: {other:?}"),
975 }
976 }
977 }
978 }
979 assert!(matches!(
980 RawPacket::pack_raw_shared_with_max_mtu(header2, 0, &dest, None, 0, &[1], 500),
981 Err(PacketError::MissingTransportId)
982 ));
983 assert!(matches!(
984 RawPacket::pack_raw_shared_filled_with_max_mtu(
985 header2,
986 0,
987 &dest,
988 None,
989 0,
990 1,
991 500,
992 |_| {}
993 ),
994 Err(PacketError::MissingTransportId)
995 ));
996 }
997
998 #[test]
999 fn raw_only_packing_preserves_wire_layout_and_limits() {
1000 for header in [constants::HEADER_1, constants::HEADER_2] {
1001 for transport in [None, Some([0xBB; 16])] {
1002 for len in [0, 1, 464, 16348] {
1003 let flags = PacketFlags {
1004 header_type: header,
1005 context_flag: constants::FLAG_UNSET,
1006 transport_type: constants::TRANSPORT_BROADCAST,
1007 destination_type: constants::DESTINATION_LINK,
1008 packet_type: constants::PACKET_TYPE_DATA,
1009 };
1010 let data = vec![0xCC; len];
1011 let mut expected = vec![flags.pack(), 7];
1012 if header == constants::HEADER_2 {
1013 expected.extend_from_slice(&[0xBB; 16]);
1014 }
1015 expected.extend_from_slice(&[0xAA; 16]);
1016 expected.push(constants::CONTEXT_RESOURCE);
1017 expected.extend_from_slice(&data);
1018 for limit in [0, expected.len() - 1, expected.len(), expected.len() + 1] {
1019 let result = RawPacket::pack_raw_with_max_mtu(
1020 flags,
1021 7,
1022 &[0xAA; 16],
1023 transport.as_ref(),
1024 constants::CONTEXT_RESOURCE,
1025 &data,
1026 limit,
1027 );
1028 if header == constants::HEADER_2 && transport.is_none() {
1029 assert!(matches!(result, Err(PacketError::MissingTransportId)));
1030 } else if limit < expected.len() {
1031 assert!(matches!(result, Err(PacketError::ExceedsMtu)));
1032 } else {
1033 assert_eq!(result.unwrap(), expected);
1034 let (raw, hash) = RawPacket::pack_raw_with_hash_with_max_mtu(
1035 flags,
1036 7,
1037 &[0xAA; 16],
1038 transport.as_ref(),
1039 constants::CONTEXT_RESOURCE,
1040 &data,
1041 limit,
1042 )
1043 .unwrap();
1044 assert_eq!(raw, expected);
1045 if len == 0 {
1046 assert!(matches!(
1047 RawPacket::unpack(&raw),
1048 Err(PacketError::ZeroLengthData)
1049 ));
1050 } else {
1051 assert_eq!(RawPacket::unpack(&raw).unwrap().packet_hash, hash);
1052 }
1053 }
1054 }
1055 }
1056 }
1057 }
1058 }
1059
1060 #[test]
1061 fn test_flags_pack_header1_data_single_broadcast() {
1062 let flags = PacketFlags {
1063 header_type: constants::HEADER_1,
1064 context_flag: constants::FLAG_UNSET,
1065 transport_type: constants::TRANSPORT_BROADCAST,
1066 destination_type: constants::DESTINATION_SINGLE,
1067 packet_type: constants::PACKET_TYPE_DATA,
1068 };
1069 assert_eq!(flags.pack(), 0x00);
1070 }
1071
1072 #[test]
1073 fn test_flags_pack_header2_announce_single_transport() {
1074 let flags = PacketFlags {
1075 header_type: constants::HEADER_2,
1076 context_flag: constants::FLAG_UNSET,
1077 transport_type: constants::TRANSPORT_TRANSPORT,
1078 destination_type: constants::DESTINATION_SINGLE,
1079 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1080 };
1081 assert_eq!(flags.pack(), 0x51);
1083 }
1084
1085 #[test]
1086 fn test_flags_roundtrip() {
1087 for byte in 0..=0x7Fu8 {
1088 let flags = PacketFlags::unpack(byte);
1089 assert_eq!(flags.pack(), byte);
1090 }
1091 }
1092
1093 #[test]
1094 fn test_pack_header1() {
1095 let dest_hash = [0xAA; 16];
1096 let data = b"hello";
1097 let flags = PacketFlags {
1098 header_type: constants::HEADER_1,
1099 context_flag: constants::FLAG_UNSET,
1100 transport_type: constants::TRANSPORT_BROADCAST,
1101 destination_type: constants::DESTINATION_SINGLE,
1102 packet_type: constants::PACKET_TYPE_DATA,
1103 };
1104
1105 let pkt =
1106 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, data).unwrap();
1107
1108 assert_eq!(pkt.raw.len(), 24);
1110 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"); }
1116
1117 #[test]
1118 fn test_pack_header2() {
1119 let dest_hash = [0xAA; 16];
1120 let transport_id = [0xBB; 16];
1121 let data = b"world";
1122 let flags = PacketFlags {
1123 header_type: constants::HEADER_2,
1124 context_flag: constants::FLAG_UNSET,
1125 transport_type: constants::TRANSPORT_TRANSPORT,
1126 destination_type: constants::DESTINATION_SINGLE,
1127 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1128 };
1129
1130 let pkt = RawPacket::pack(
1131 flags,
1132 3,
1133 &dest_hash,
1134 Some(&transport_id),
1135 constants::CONTEXT_NONE,
1136 data,
1137 )
1138 .unwrap();
1139
1140 assert_eq!(pkt.raw.len(), 40);
1142 assert_eq!(pkt.raw[0], flags.pack());
1143 assert_eq!(pkt.raw[1], 3);
1144 assert_eq!(&pkt.raw[2..18], &transport_id);
1145 assert_eq!(&pkt.raw[18..34], &dest_hash);
1146 assert_eq!(pkt.raw[34], 0x00);
1147 assert_eq!(&pkt.raw[35..], b"world");
1148 }
1149
1150 #[test]
1151 fn test_unpack_roundtrip_header1() {
1152 let dest_hash = [0x11; 16];
1153 let data = b"test data";
1154 let flags = PacketFlags {
1155 header_type: constants::HEADER_1,
1156 context_flag: constants::FLAG_UNSET,
1157 transport_type: constants::TRANSPORT_BROADCAST,
1158 destination_type: constants::DESTINATION_SINGLE,
1159 packet_type: constants::PACKET_TYPE_DATA,
1160 };
1161
1162 let pkt = RawPacket::pack(
1163 flags,
1164 5,
1165 &dest_hash,
1166 None,
1167 constants::CONTEXT_RESOURCE,
1168 data,
1169 )
1170 .unwrap();
1171 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
1172
1173 assert_eq!(unpacked.flags, flags);
1174 assert_eq!(unpacked.hops, 5);
1175 assert!(unpacked.transport_id.is_none());
1176 assert_eq!(unpacked.destination_hash, dest_hash);
1177 assert_eq!(unpacked.context, constants::CONTEXT_RESOURCE);
1178 assert_eq!(unpacked.data(), data);
1179 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
1180 }
1181
1182 #[test]
1183 fn test_unpack_roundtrip_header2() {
1184 let dest_hash = [0x22; 16];
1185 let transport_id = [0x33; 16];
1186 let data = b"transported";
1187 let flags = PacketFlags {
1188 header_type: constants::HEADER_2,
1189 context_flag: constants::FLAG_SET,
1190 transport_type: constants::TRANSPORT_TRANSPORT,
1191 destination_type: constants::DESTINATION_SINGLE,
1192 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1193 };
1194
1195 let pkt = RawPacket::pack(
1196 flags,
1197 2,
1198 &dest_hash,
1199 Some(&transport_id),
1200 constants::CONTEXT_NONE,
1201 data,
1202 )
1203 .unwrap();
1204 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
1205
1206 assert_eq!(unpacked.flags, flags);
1207 assert_eq!(unpacked.hops, 2);
1208 assert_eq!(unpacked.transport_id.unwrap(), transport_id);
1209 assert_eq!(unpacked.destination_hash, dest_hash);
1210 assert_eq!(unpacked.context, constants::CONTEXT_NONE);
1211 assert_eq!(unpacked.data(), data);
1212 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
1213 }
1214
1215 #[test]
1216 fn truncated_hash_is_derived_from_cached_packet_hash() {
1217 let mut packet = RawPacket::pack(
1218 PacketFlags {
1219 header_type: constants::HEADER_1,
1220 context_flag: constants::FLAG_UNSET,
1221 transport_type: constants::TRANSPORT_BROADCAST,
1222 destination_type: constants::DESTINATION_SINGLE,
1223 packet_type: constants::PACKET_TYPE_DATA,
1224 },
1225 1,
1226 &[0x42; 16],
1227 None,
1228 constants::CONTEXT_NONE,
1229 b"cached hash",
1230 )
1231 .unwrap();
1232 let expected: [u8; 16] = packet.packet_hash[..16].try_into().unwrap();
1233
1234 let last = packet.raw.len() - 1;
1237 packet.raw[last] ^= 0xff;
1238
1239 assert_eq!(packet.get_truncated_hash(), expected);
1240 }
1241
1242 #[test]
1243 fn test_unpack_too_short() {
1244 assert!(RawPacket::unpack(&[0x00; 5]).is_err());
1245 }
1246
1247 #[test]
1248 fn unpack_rejects_zero_length_data_for_both_header_types() {
1249 for header_type in [constants::HEADER_1, constants::HEADER_2] {
1250 let packet = RawPacket::pack(
1251 PacketFlags {
1252 header_type,
1253 context_flag: constants::FLAG_UNSET,
1254 transport_type: constants::TRANSPORT_BROADCAST,
1255 destination_type: constants::DESTINATION_SINGLE,
1256 packet_type: constants::PACKET_TYPE_DATA,
1257 },
1258 0,
1259 &[0x42; 16],
1260 (header_type == constants::HEADER_2).then_some(&[0x24; 16]),
1261 constants::CONTEXT_NONE,
1262 b"",
1263 )
1264 .unwrap();
1265 assert!(matches!(
1266 RawPacket::unpack(&packet.raw),
1267 Err(PacketError::ZeroLengthData)
1268 ));
1269 }
1270 }
1271
1272 #[test]
1273 fn unpack_enforces_pathfinder_hop_boundary_but_pack_remains_permissive() {
1274 let flags = PacketFlags {
1275 header_type: constants::HEADER_1,
1276 context_flag: constants::FLAG_UNSET,
1277 transport_type: constants::TRANSPORT_BROADCAST,
1278 destination_type: constants::DESTINATION_SINGLE,
1279 packet_type: constants::PACKET_TYPE_DATA,
1280 };
1281 let accepted = RawPacket::pack(flags, 127, &[1; 16], None, 0, b"x").unwrap();
1282 assert_eq!(RawPacket::unpack(&accepted.raw).unwrap().hops, 127);
1283
1284 for hops in [128, 255] {
1285 let packed = RawPacket::pack(flags, hops, &[1; 16], None, 0, b"x").unwrap();
1286 assert!(matches!(
1287 RawPacket::unpack(&packed.raw),
1288 Err(PacketError::InvalidHopCount(value)) if value == hops
1289 ));
1290 }
1291 }
1292
1293 #[test]
1294 fn test_pack_exceeds_mtu() {
1295 let flags = PacketFlags {
1296 header_type: constants::HEADER_1,
1297 context_flag: constants::FLAG_UNSET,
1298 transport_type: constants::TRANSPORT_BROADCAST,
1299 destination_type: constants::DESTINATION_SINGLE,
1300 packet_type: constants::PACKET_TYPE_DATA,
1301 };
1302 let data = [0u8; 500]; let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, &data);
1304 assert!(result.is_err());
1305 }
1306
1307 #[test]
1308 fn test_header2_missing_transport_id() {
1309 let flags = PacketFlags {
1310 header_type: constants::HEADER_2,
1311 context_flag: constants::FLAG_UNSET,
1312 transport_type: constants::TRANSPORT_TRANSPORT,
1313 destination_type: constants::DESTINATION_SINGLE,
1314 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1315 };
1316 let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, b"data");
1317 assert!(result.is_err());
1318 }
1319
1320 #[test]
1321 fn test_hashable_part_header1_masks_upper_flags() {
1322 let dest_hash = [0xCC; 16];
1323 let flags = PacketFlags {
1324 header_type: constants::HEADER_1,
1325 context_flag: constants::FLAG_SET,
1326 transport_type: constants::TRANSPORT_BROADCAST,
1327 destination_type: constants::DESTINATION_SINGLE,
1328 packet_type: constants::PACKET_TYPE_DATA,
1329 };
1330
1331 let pkt =
1332 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, b"test").unwrap();
1333 let hashable = pkt.get_hashable_part();
1334
1335 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1337 assert_eq!(&hashable[1..], &pkt.raw[2..]);
1339 }
1340
1341 #[test]
1342 fn test_hashable_part_header2_strips_transport_id() {
1343 let dest_hash = [0xDD; 16];
1344 let transport_id = [0xEE; 16];
1345 let flags = PacketFlags {
1346 header_type: constants::HEADER_2,
1347 context_flag: constants::FLAG_UNSET,
1348 transport_type: constants::TRANSPORT_TRANSPORT,
1349 destination_type: constants::DESTINATION_SINGLE,
1350 packet_type: constants::PACKET_TYPE_ANNOUNCE,
1351 };
1352
1353 let pkt = RawPacket::pack(
1354 flags,
1355 0,
1356 &dest_hash,
1357 Some(&transport_id),
1358 constants::CONTEXT_NONE,
1359 b"data",
1360 )
1361 .unwrap();
1362 let hashable = pkt.get_hashable_part();
1363
1364 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1366 assert_eq!(&hashable[1..], &pkt.raw[18..]);
1368 }
1369}