Skip to main content

rns_core/
packet.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3use core::fmt;
4
5use crate::constants;
6use crate::transport::types::PacketBytes;
7
8/// Storage for a packet's wire bytes that can hand them over as shared
9/// [`PacketBytes`]: shared bytes are cloned, anything else is copied.
10pub 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    /// Hop counts at or above `PATHFINDER_M` are malformed, as upstream
40    /// `Packet.unpack` (Packet.py:250) rejects them before the transport
41    /// hop limiter is consulted; the receiving interface counts a protocol
42    /// violation (Transport.py:2008).
43    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// =============================================================================
60// PacketFlags: packs 5 fields into one byte
61// =============================================================================
62
63#[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    /// Pack fields into a single flags byte.
74    ///
75    /// Bit layout:
76    /// ```text
77    /// Bit 6:     header_type (1 bit)
78    /// Bit 5:     context_flag (1 bit)
79    /// Bit 4:     transport_type (1 bit)
80    /// Bits 3-2:  destination_type (2 bits)
81    /// Bits 1-0:  packet_type (2 bits)
82    /// ```
83    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    /// Unpack a flags byte into fields.
92    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// =============================================================================
104// RawPacket: wire-level packet representation
105// =============================================================================
106
107/// A packet's header fields and its wire bytes `raw`, owned (`Vec<u8>`, the
108/// default) or in other storage: a borrowed view (`&[u8]`) reads a packet
109/// without copying it, and shared bytes (`PacketBytes`) are cloned, not copied.
110#[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    /// Offset of the payload in the wire bytes of a packet with this header type.
125    #[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    /// [`RawPacket::is_unhashed_link_resource`] for wire bytes that are not
135    /// unpacked; `false` when they are too short to carry a context byte.
136    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    /// Pack fields into raw bytes.
160    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    /// Pack fields into raw bytes and packet hash without constructing a full RawPacket.
180    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    /// Pack fields into raw bytes with a caller-provided MTU limit.
200    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    /// Pack fields into raw bytes and packet hash with a caller-provided MTU limit.
233    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    /// Pack only wire bytes when the caller does not need a packet hash.
256    /// Validation and encoding match the hash-producing packing methods.
257    /// [`RawPacket::pack_raw_with_hash_with_max_mtu`] into shared bytes.
258    #[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    /// The packet hash of packed wire bytes with the given header type.
282    pub fn packet_hash_of(header_type: u8, raw: &[u8]) -> [u8; 32] {
283        Self::compute_packet_hash(header_type, raw)
284    }
285
286    /// [`RawPacket::pack_raw_shared_with_max_mtu`] with a payload of
287    /// `payload_len` bytes that `fill` writes in place, so a payload produced
288    /// for this packet (a ciphertext, say) needs no buffer of its own. `fill`
289    /// receives the payload zeroed.
290    #[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        // SAFETY: `out` is `len` writable bytes; zeroing initialises them all,
313        // and u8 and MaybeUninit<u8> share a layout.
314        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        // SAFETY: every byte was initialised by the zeroing above.
330        Ok(unsafe { shared.assume_init() })
331    }
332
333    /// [`RawPacket::pack_raw_with_max_mtu`] into shared bytes, allocated once
334    /// at their final size, for packets that are routed and sent as they are.
335    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            // SAFETY: `slots` is in bounds and exactly `bytes.len()` long; u8
359            // and MaybeUninit<u8> share a layout.
360            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        // SAFETY: every one of the `len` bytes was written above.
378        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        // flags, hops, [transport id], destination hash, context, data
396        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        // Sized once: growing from empty cost an allocation and several
403        // reallocations per packet.
404        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    /// Unpack raw bytes into fields.
419    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    /// Unpack wire bytes the caller owns, keeping them as `raw` instead of
424    /// copying them.
425    pub fn unpack_owned(raw: Vec<u8>) -> Result<Self, PacketError> {
426        Self::parse::<true>(raw)
427    }
428
429    /// Unpack owned wire bytes into fields, leaving `packet_hash` zeroed.
430    /// Callers must hash the packet with [`RawPacket::hash_in_place`] before
431    /// anything reads `packet_hash`.
432    pub(crate) fn unpack_unhashed(raw: Vec<u8>) -> Result<Self, PacketError> {
433        Self::parse::<false>(raw)
434    }
435
436    /// Unpack owned wire bytes of a packet this node sends. A link resource
437    /// part, part request or proof is left unhashed, its `packet_hash`
438    /// zeroed, because nothing reads it (see
439    /// [`RawPacket::is_unhashed_link_resource`]).
440    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    /// [`RawPacket::unpack_owned`] for shared bytes, kept as they are.
449    pub fn unpack_shared(raw: PacketBytes) -> Result<RawPacket<PacketBytes>, PacketError> {
450        RawPacket::<PacketBytes>::parse::<true>(raw)
451    }
452
453    /// [`RawPacket::unpack`] for shared bytes, leaving `packet_hash` zeroed
454    /// (see [`RawPacket::unpack_unhashed`]).
455    pub(crate) fn unpack_shared_unhashed(
456        raw: PacketBytes,
457    ) -> Result<RawPacket<PacketBytes>, PacketError> {
458        RawPacket::<PacketBytes>::parse::<false>(raw)
459    }
460
461    /// [`RawPacket::unpack_outbound`] for shared bytes, kept as they are.
462    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    /// Unpack wire bytes whose packet hash the caller already computed from
471    /// these exact bytes (for example a delivered packet's hash), instead of
472    /// hashing them again. The hash is trusted, not checked.
473    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            // Skip transport_id: raw[2..18] is transport_id (16 bytes)
480            &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    /// SHA-256 of the hashable part, fed from the wire bytes without building
495    /// the concatenated buffer that [`RawPacket::get_hashable_part`] returns.
496    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    /// The payload: the wire bytes after the header.
506    #[inline]
507    pub fn data(&self) -> &[u8] {
508        &self.raw.as_ref()[<RawPacket>::data_offset(self.flags.header_type)..]
509    }
510
511    /// Whether this packet is link resource traffic whose hash nothing
512    /// reads: a part, a part request, or a resource proof. The packet filter
513    /// admits these contexts before deduplication (`Transport.packet_filter`),
514    /// no proof or receipt covers them, and their handlers read only the
515    /// payload (the receiver locates a part by its map hash). Their hashes
516    /// cannot mark any other packet as a duplicate either, since the context
517    /// byte is hashed, so they are neither computed nor remembered.
518    #[inline]
519    pub fn is_unhashed_link_resource(&self) -> bool {
520        <RawPacket>::is_unhashed_link_resource_header(&self.flags, self.context)
521    }
522
523    /// Whether a link peer never proves this packet: link data in a context
524    /// with its own protocol handling (requests and responses, resource
525    /// control and parts, keepalives, identification, RTT and close). Only
526    /// plain link data and channel messages are proven, so a sent packet in
527    /// one of these contexts has no receipt to wait for.
528    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    /// Get the hashable part of the packet.
549    ///
550    /// From Python Packet.py:354-361:
551    /// - Take raw[0] & 0x0F (mask out upper 4 bits of flags)
552    /// - For HEADER_1: append raw[2:]
553    /// - For HEADER_2: skip transport_id: append raw[18:]
554    pub fn get_hashable_part(&self) -> Vec<u8> {
555        <RawPacket>::compute_hashable_part(self.flags.header_type, self.raw.as_ref())
556    }
557
558    /// Compute `packet_hash` from the packet's wire bytes.
559    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    /// Full SHA-256 hash of the hashable part.
565    pub fn get_hash(&self) -> [u8; 32] {
566        self.packet_hash
567    }
568
569    /// Truncated hash (first 16 bytes) of the cached packet hash.
570    ///
571    /// Packet construction and unpacking compute `packet_hash` once; routing
572    /// paths use this prefix instead of hashing the raw packet again.
573    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    /// A view of this packet borrowing its bytes.
579    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    /// This packet with its bytes copied into an owned buffer.
594    pub fn to_owned_packet(&self) -> RawPacket {
595        self.as_view().map_raw(<[u8]>::to_vec)
596    }
597
598    /// This packet with its bytes converted by `f`, fields unchanged.
599    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    /// Parse `input`, keeping it as `raw`.
614    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; // 16
627
628        if flags.header_type == constants::HEADER_2 {
629            // HEADER_2: [flags:1][hops:1][transport_id:16][dest_hash:16][context:1][data:*]
630            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            // HEADER_1: [flags:1][hops:1][dest_hash:16][context:1][data:*]
665            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    /// [`RawPacket::unpack_prehashed`] as a view of `raw`, without copying it.
703    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        // The hash is trusted, not recomputed.
740        let other = RawPacket::unpack_prehashed(&packet.raw, [7; 32]).unwrap();
741        assert_eq!(other.packet_hash, [7; 32]);
742        // Structural errors are the same.
743        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        // Bytes too short to carry a context byte are not a part.
838        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        // Only link data qualifies.
915        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        // 0b01010001 = 0x51
1082        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        // Verify layout: [flags:1][hops:1][dest:16][context:1][data:5] = 24 bytes
1109        assert_eq!(pkt.raw.len(), 24);
1110        assert_eq!(pkt.raw[0], 0x00); // flags
1111        assert_eq!(pkt.raw[1], 0x00); // hops
1112        assert_eq!(&pkt.raw[2..18], &dest_hash); // dest hash
1113        assert_eq!(pkt.raw[18], 0x00); // context
1114        assert_eq!(&pkt.raw[19..], b"hello"); // data
1115    }
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        // Layout: [flags:1][hops:1][transport:16][dest:16][context:1][data:5] = 40 bytes
1141        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        // Changing the retained wire buffer demonstrates that this accessor is
1235        // a prefix operation, not a second hash computation.
1236        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]; // way too much data
1303        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        // First byte should have upper 4 bits masked out
1336        assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1337        // Rest should be raw[2:]
1338        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        // First byte: flags masked
1365        assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
1366        // Should skip transport_id: raw[18:] = dest_hash + context + data
1367        assert_eq!(&hashable[1..], &pkt.raw[18..]);
1368    }
1369}