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 pub fn pack(
110 flags: PacketFlags,
111 hops: u8,
112 destination_hash: &[u8; 16],
113 transport_id: Option<&[u8; 16]>,
114 context: u8,
115 data: &[u8],
116 ) -> Result<Self, PacketError> {
117 Self::pack_with_max_mtu(
118 flags,
119 hops,
120 destination_hash,
121 transport_id,
122 context,
123 data,
124 constants::MTU,
125 )
126 }
127
128 pub fn pack_raw_with_hash(
130 flags: PacketFlags,
131 hops: u8,
132 destination_hash: &[u8; 16],
133 transport_id: Option<&[u8; 16]>,
134 context: u8,
135 data: &[u8],
136 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
137 Self::pack_raw_with_hash_with_max_mtu(
138 flags,
139 hops,
140 destination_hash,
141 transport_id,
142 context,
143 data,
144 constants::MTU,
145 )
146 }
147
148 pub fn pack_with_max_mtu(
150 flags: PacketFlags,
151 hops: u8,
152 destination_hash: &[u8; 16],
153 transport_id: Option<&[u8; 16]>,
154 context: u8,
155 data: &[u8],
156 max_mtu: usize,
157 ) -> Result<Self, PacketError> {
158 let (raw, packet_hash) = Self::pack_raw_with_hash_with_max_mtu(
159 flags,
160 hops,
161 destination_hash,
162 transport_id,
163 context,
164 data,
165 max_mtu,
166 )?;
167
168 Ok(RawPacket {
169 flags,
170 hops,
171 transport_id: transport_id.copied(),
172 destination_hash: *destination_hash,
173 context,
174 raw,
175 packet_hash,
176 rssi: None,
177 snr: None,
178 })
179 }
180
181 pub fn pack_raw_with_hash_with_max_mtu(
183 flags: PacketFlags,
184 hops: u8,
185 destination_hash: &[u8; 16],
186 transport_id: Option<&[u8; 16]>,
187 context: u8,
188 data: &[u8],
189 max_mtu: usize,
190 ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
191 let raw = Self::pack_raw_with_max_mtu(
192 flags,
193 hops,
194 destination_hash,
195 transport_id,
196 context,
197 data,
198 max_mtu,
199 )?;
200 let packet_hash = Self::compute_packet_hash(flags.header_type, &raw);
201 Ok((raw, packet_hash))
202 }
203
204 pub fn pack_raw_with_max_mtu(
207 flags: PacketFlags,
208 hops: u8,
209 destination_hash: &[u8; 16],
210 transport_id: Option<&[u8; 16]>,
211 context: u8,
212 data: &[u8],
213 max_mtu: usize,
214 ) -> Result<Vec<u8>, PacketError> {
215 if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
216 return Err(PacketError::MissingTransportId);
217 }
218
219 let mut raw = Vec::new();
220 raw.push(flags.pack());
221 raw.push(hops);
222
223 if let Some(transport_id) = transport_id {
224 if flags.header_type == constants::HEADER_2 {
225 raw.extend_from_slice(transport_id);
226 }
227 }
228
229 raw.extend_from_slice(destination_hash);
230 raw.push(context);
231 raw.extend_from_slice(data);
232
233 if raw.len() > max_mtu {
234 return Err(PacketError::ExceedsMtu);
235 }
236
237 Ok(raw)
238 }
239
240 pub fn unpack(raw: &[u8]) -> Result<Self, PacketError> {
242 Self::unpack_with::<true, _>(raw)
243 }
244
245 pub fn unpack_owned(raw: Vec<u8>) -> Result<Self, PacketError> {
248 Self::unpack_with::<true, _>(raw)
249 }
250
251 pub(crate) fn unpack_unhashed(raw: Vec<u8>) -> Result<Self, PacketError> {
255 Self::unpack_with::<false, _>(raw)
256 }
257
258 fn unpack_with<const HASH: bool, B>(input: B) -> Result<Self, PacketError>
259 where
260 B: AsRef<[u8]> + Into<Vec<u8>>,
261 {
262 let raw = input.as_ref();
263 if raw.len() < constants::HEADER_MINSIZE {
264 return Err(PacketError::TooShort);
265 }
266
267 let flags = PacketFlags::unpack(raw[0]);
268 let hops = raw[1];
269 if hops >= constants::PATHFINDER_M {
270 return Err(PacketError::InvalidHopCount(hops));
271 }
272
273 let dst_len = constants::TRUNCATED_HASHLENGTH / 8; if flags.header_type == constants::HEADER_2 {
276 let min_len = 2 + dst_len * 2 + 1;
278 if raw.len() < min_len {
279 return Err(PacketError::TooShort);
280 }
281
282 let mut transport_id = [0u8; 16];
283 transport_id.copy_from_slice(&raw[2..2 + dst_len]);
284
285 let mut destination_hash = [0u8; 16];
286 destination_hash.copy_from_slice(&raw[2 + dst_len..2 + 2 * dst_len]);
287
288 let context = raw[2 + 2 * dst_len];
289 if raw.len() == 2 + 2 * dst_len + 1 {
290 return Err(PacketError::ZeroLengthData);
291 }
292
293 let packet_hash = if HASH {
294 Self::compute_packet_hash(flags.header_type, raw)
295 } else {
296 [0; 32]
297 };
298
299 Ok(RawPacket {
300 flags,
301 hops,
302 transport_id: Some(transport_id),
303 destination_hash,
304 context,
305 raw: input.into(),
306 packet_hash,
307 rssi: None,
308 snr: None,
309 })
310 } else if flags.header_type == constants::HEADER_1 {
311 let min_len = 2 + dst_len + 1;
313 if raw.len() < min_len {
314 return Err(PacketError::TooShort);
315 }
316
317 let mut destination_hash = [0u8; 16];
318 destination_hash.copy_from_slice(&raw[2..2 + dst_len]);
319
320 let context = raw[2 + dst_len];
321 if raw.len() == 2 + dst_len + 1 {
322 return Err(PacketError::ZeroLengthData);
323 }
324
325 let packet_hash = if HASH {
326 Self::compute_packet_hash(flags.header_type, raw)
327 } else {
328 [0; 32]
329 };
330
331 Ok(RawPacket {
332 flags,
333 hops,
334 transport_id: None,
335 destination_hash,
336 context,
337 raw: input.into(),
338 packet_hash,
339 rssi: None,
340 snr: None,
341 })
342 } else {
343 Err(PacketError::InvalidHeaderType)
344 }
345 }
346
347 pub fn get_hashable_part(&self) -> Vec<u8> {
354 Self::compute_hashable_part(self.flags.header_type, &self.raw)
355 }
356
357 fn hashable_tail(header_type: u8, raw: &[u8]) -> &[u8] {
358 if header_type == constants::HEADER_2 {
359 &raw[(constants::TRUNCATED_HASHLENGTH / 8 + 2)..]
361 } else {
362 &raw[2..]
363 }
364 }
365
366 fn compute_hashable_part(header_type: u8, raw: &[u8]) -> Vec<u8> {
367 let tail = Self::hashable_tail(header_type, raw);
368 let mut hashable = Vec::with_capacity(1 + tail.len());
369 hashable.push(raw[0] & 0b00001111);
370 hashable.extend_from_slice(tail);
371 hashable
372 }
373
374 fn compute_packet_hash(header_type: u8, raw: &[u8]) -> [u8; 32] {
377 let mut hasher = rns_crypto::sha256::Sha256::new();
378 hasher.update(&[raw[0] & 0b00001111]);
379 hasher.update(Self::hashable_tail(header_type, raw));
380 hasher.digest()
381 }
382
383 pub(crate) fn hash_in_place(&mut self) {
385 self.packet_hash = Self::compute_packet_hash(self.flags.header_type, &self.raw);
386 }
387
388 pub fn get_hash(&self) -> [u8; 32] {
390 self.packet_hash
391 }
392
393 pub fn get_truncated_hash(&self) -> [u8; 16] {
398 let mut result = [0u8; 16];
399 result.copy_from_slice(&self.packet_hash[..16]);
400 result
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn raw_only_packing_preserves_wire_layout_and_limits() {
410 for header in [constants::HEADER_1, constants::HEADER_2] {
411 for transport in [None, Some([0xBB; 16])] {
412 for len in [0, 1, 464, 16348] {
413 let flags = PacketFlags {
414 header_type: header,
415 context_flag: constants::FLAG_UNSET,
416 transport_type: constants::TRANSPORT_BROADCAST,
417 destination_type: constants::DESTINATION_LINK,
418 packet_type: constants::PACKET_TYPE_DATA,
419 };
420 let data = vec![0xCC; len];
421 let mut expected = vec![flags.pack(), 7];
422 if header == constants::HEADER_2 {
423 expected.extend_from_slice(&[0xBB; 16]);
424 }
425 expected.extend_from_slice(&[0xAA; 16]);
426 expected.push(constants::CONTEXT_RESOURCE);
427 expected.extend_from_slice(&data);
428 for limit in [0, expected.len() - 1, expected.len(), expected.len() + 1] {
429 let result = RawPacket::pack_raw_with_max_mtu(
430 flags,
431 7,
432 &[0xAA; 16],
433 transport.as_ref(),
434 constants::CONTEXT_RESOURCE,
435 &data,
436 limit,
437 );
438 if header == constants::HEADER_2 && transport.is_none() {
439 assert!(matches!(result, Err(PacketError::MissingTransportId)));
440 } else if limit < expected.len() {
441 assert!(matches!(result, Err(PacketError::ExceedsMtu)));
442 } else {
443 assert_eq!(result.unwrap(), expected);
444 let (raw, hash) = RawPacket::pack_raw_with_hash_with_max_mtu(
445 flags,
446 7,
447 &[0xAA; 16],
448 transport.as_ref(),
449 constants::CONTEXT_RESOURCE,
450 &data,
451 limit,
452 )
453 .unwrap();
454 assert_eq!(raw, expected);
455 if len == 0 {
456 assert!(matches!(
457 RawPacket::unpack(&raw),
458 Err(PacketError::ZeroLengthData)
459 ));
460 } else {
461 assert_eq!(RawPacket::unpack(&raw).unwrap().packet_hash, hash);
462 }
463 }
464 }
465 }
466 }
467 }
468 }
469
470 #[test]
471 fn test_flags_pack_header1_data_single_broadcast() {
472 let flags = PacketFlags {
473 header_type: constants::HEADER_1,
474 context_flag: constants::FLAG_UNSET,
475 transport_type: constants::TRANSPORT_BROADCAST,
476 destination_type: constants::DESTINATION_SINGLE,
477 packet_type: constants::PACKET_TYPE_DATA,
478 };
479 assert_eq!(flags.pack(), 0x00);
480 }
481
482 #[test]
483 fn test_flags_pack_header2_announce_single_transport() {
484 let flags = PacketFlags {
485 header_type: constants::HEADER_2,
486 context_flag: constants::FLAG_UNSET,
487 transport_type: constants::TRANSPORT_TRANSPORT,
488 destination_type: constants::DESTINATION_SINGLE,
489 packet_type: constants::PACKET_TYPE_ANNOUNCE,
490 };
491 assert_eq!(flags.pack(), 0x51);
493 }
494
495 #[test]
496 fn test_flags_roundtrip() {
497 for byte in 0..=0x7Fu8 {
498 let flags = PacketFlags::unpack(byte);
499 assert_eq!(flags.pack(), byte);
500 }
501 }
502
503 #[test]
504 fn test_pack_header1() {
505 let dest_hash = [0xAA; 16];
506 let data = b"hello";
507 let flags = PacketFlags {
508 header_type: constants::HEADER_1,
509 context_flag: constants::FLAG_UNSET,
510 transport_type: constants::TRANSPORT_BROADCAST,
511 destination_type: constants::DESTINATION_SINGLE,
512 packet_type: constants::PACKET_TYPE_DATA,
513 };
514
515 let pkt =
516 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, data).unwrap();
517
518 assert_eq!(pkt.raw.len(), 24);
520 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"); }
526
527 #[test]
528 fn test_pack_header2() {
529 let dest_hash = [0xAA; 16];
530 let transport_id = [0xBB; 16];
531 let data = b"world";
532 let flags = PacketFlags {
533 header_type: constants::HEADER_2,
534 context_flag: constants::FLAG_UNSET,
535 transport_type: constants::TRANSPORT_TRANSPORT,
536 destination_type: constants::DESTINATION_SINGLE,
537 packet_type: constants::PACKET_TYPE_ANNOUNCE,
538 };
539
540 let pkt = RawPacket::pack(
541 flags,
542 3,
543 &dest_hash,
544 Some(&transport_id),
545 constants::CONTEXT_NONE,
546 data,
547 )
548 .unwrap();
549
550 assert_eq!(pkt.raw.len(), 40);
552 assert_eq!(pkt.raw[0], flags.pack());
553 assert_eq!(pkt.raw[1], 3);
554 assert_eq!(&pkt.raw[2..18], &transport_id);
555 assert_eq!(&pkt.raw[18..34], &dest_hash);
556 assert_eq!(pkt.raw[34], 0x00);
557 assert_eq!(&pkt.raw[35..], b"world");
558 }
559
560 #[test]
561 fn test_unpack_roundtrip_header1() {
562 let dest_hash = [0x11; 16];
563 let data = b"test data";
564 let flags = PacketFlags {
565 header_type: constants::HEADER_1,
566 context_flag: constants::FLAG_UNSET,
567 transport_type: constants::TRANSPORT_BROADCAST,
568 destination_type: constants::DESTINATION_SINGLE,
569 packet_type: constants::PACKET_TYPE_DATA,
570 };
571
572 let pkt = RawPacket::pack(
573 flags,
574 5,
575 &dest_hash,
576 None,
577 constants::CONTEXT_RESOURCE,
578 data,
579 )
580 .unwrap();
581 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
582
583 assert_eq!(unpacked.flags, flags);
584 assert_eq!(unpacked.hops, 5);
585 assert!(unpacked.transport_id.is_none());
586 assert_eq!(unpacked.destination_hash, dest_hash);
587 assert_eq!(unpacked.context, constants::CONTEXT_RESOURCE);
588 assert_eq!(unpacked.data(), data);
589 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
590 }
591
592 #[test]
593 fn test_unpack_roundtrip_header2() {
594 let dest_hash = [0x22; 16];
595 let transport_id = [0x33; 16];
596 let data = b"transported";
597 let flags = PacketFlags {
598 header_type: constants::HEADER_2,
599 context_flag: constants::FLAG_SET,
600 transport_type: constants::TRANSPORT_TRANSPORT,
601 destination_type: constants::DESTINATION_SINGLE,
602 packet_type: constants::PACKET_TYPE_ANNOUNCE,
603 };
604
605 let pkt = RawPacket::pack(
606 flags,
607 2,
608 &dest_hash,
609 Some(&transport_id),
610 constants::CONTEXT_NONE,
611 data,
612 )
613 .unwrap();
614 let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
615
616 assert_eq!(unpacked.flags, flags);
617 assert_eq!(unpacked.hops, 2);
618 assert_eq!(unpacked.transport_id.unwrap(), transport_id);
619 assert_eq!(unpacked.destination_hash, dest_hash);
620 assert_eq!(unpacked.context, constants::CONTEXT_NONE);
621 assert_eq!(unpacked.data(), data);
622 assert_eq!(unpacked.packet_hash, pkt.packet_hash);
623 }
624
625 #[test]
626 fn truncated_hash_is_derived_from_cached_packet_hash() {
627 let mut packet = RawPacket::pack(
628 PacketFlags {
629 header_type: constants::HEADER_1,
630 context_flag: constants::FLAG_UNSET,
631 transport_type: constants::TRANSPORT_BROADCAST,
632 destination_type: constants::DESTINATION_SINGLE,
633 packet_type: constants::PACKET_TYPE_DATA,
634 },
635 1,
636 &[0x42; 16],
637 None,
638 constants::CONTEXT_NONE,
639 b"cached hash",
640 )
641 .unwrap();
642 let expected: [u8; 16] = packet.packet_hash[..16].try_into().unwrap();
643
644 let last = packet.raw.len() - 1;
647 packet.raw[last] ^= 0xff;
648
649 assert_eq!(packet.get_truncated_hash(), expected);
650 }
651
652 #[test]
653 fn test_unpack_too_short() {
654 assert!(RawPacket::unpack(&[0x00; 5]).is_err());
655 }
656
657 #[test]
658 fn unpack_rejects_zero_length_data_for_both_header_types() {
659 for header_type in [constants::HEADER_1, constants::HEADER_2] {
660 let packet = RawPacket::pack(
661 PacketFlags {
662 header_type,
663 context_flag: constants::FLAG_UNSET,
664 transport_type: constants::TRANSPORT_BROADCAST,
665 destination_type: constants::DESTINATION_SINGLE,
666 packet_type: constants::PACKET_TYPE_DATA,
667 },
668 0,
669 &[0x42; 16],
670 (header_type == constants::HEADER_2).then_some(&[0x24; 16]),
671 constants::CONTEXT_NONE,
672 b"",
673 )
674 .unwrap();
675 assert!(matches!(
676 RawPacket::unpack(&packet.raw),
677 Err(PacketError::ZeroLengthData)
678 ));
679 }
680 }
681
682 #[test]
683 fn unpack_enforces_pathfinder_hop_boundary_but_pack_remains_permissive() {
684 let flags = PacketFlags {
685 header_type: constants::HEADER_1,
686 context_flag: constants::FLAG_UNSET,
687 transport_type: constants::TRANSPORT_BROADCAST,
688 destination_type: constants::DESTINATION_SINGLE,
689 packet_type: constants::PACKET_TYPE_DATA,
690 };
691 let accepted = RawPacket::pack(flags, 127, &[1; 16], None, 0, b"x").unwrap();
692 assert_eq!(RawPacket::unpack(&accepted.raw).unwrap().hops, 127);
693
694 for hops in [128, 255] {
695 let packed = RawPacket::pack(flags, hops, &[1; 16], None, 0, b"x").unwrap();
696 assert!(matches!(
697 RawPacket::unpack(&packed.raw),
698 Err(PacketError::InvalidHopCount(value)) if value == hops
699 ));
700 }
701 }
702
703 #[test]
704 fn test_pack_exceeds_mtu() {
705 let flags = PacketFlags {
706 header_type: constants::HEADER_1,
707 context_flag: constants::FLAG_UNSET,
708 transport_type: constants::TRANSPORT_BROADCAST,
709 destination_type: constants::DESTINATION_SINGLE,
710 packet_type: constants::PACKET_TYPE_DATA,
711 };
712 let data = [0u8; 500]; let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, &data);
714 assert!(result.is_err());
715 }
716
717 #[test]
718 fn test_header2_missing_transport_id() {
719 let flags = PacketFlags {
720 header_type: constants::HEADER_2,
721 context_flag: constants::FLAG_UNSET,
722 transport_type: constants::TRANSPORT_TRANSPORT,
723 destination_type: constants::DESTINATION_SINGLE,
724 packet_type: constants::PACKET_TYPE_ANNOUNCE,
725 };
726 let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, b"data");
727 assert!(result.is_err());
728 }
729
730 #[test]
731 fn test_hashable_part_header1_masks_upper_flags() {
732 let dest_hash = [0xCC; 16];
733 let flags = PacketFlags {
734 header_type: constants::HEADER_1,
735 context_flag: constants::FLAG_SET,
736 transport_type: constants::TRANSPORT_BROADCAST,
737 destination_type: constants::DESTINATION_SINGLE,
738 packet_type: constants::PACKET_TYPE_DATA,
739 };
740
741 let pkt =
742 RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, b"test").unwrap();
743 let hashable = pkt.get_hashable_part();
744
745 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
747 assert_eq!(&hashable[1..], &pkt.raw[2..]);
749 }
750
751 #[test]
752 fn test_hashable_part_header2_strips_transport_id() {
753 let dest_hash = [0xDD; 16];
754 let transport_id = [0xEE; 16];
755 let flags = PacketFlags {
756 header_type: constants::HEADER_2,
757 context_flag: constants::FLAG_UNSET,
758 transport_type: constants::TRANSPORT_TRANSPORT,
759 destination_type: constants::DESTINATION_SINGLE,
760 packet_type: constants::PACKET_TYPE_ANNOUNCE,
761 };
762
763 let pkt = RawPacket::pack(
764 flags,
765 0,
766 &dest_hash,
767 Some(&transport_id),
768 constants::CONTEXT_NONE,
769 b"data",
770 )
771 .unwrap();
772 let hashable = pkt.get_hashable_part();
773
774 assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
776 assert_eq!(&hashable[1..], &pkt.raw[18..]);
778 }
779}