Skip to main content

rns_core/
packet.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4use crate::constants;
5use crate::hash;
6
7#[derive(Debug)]
8pub enum PacketError {
9    TooShort,
10    ExceedsMtu,
11    MissingTransportId,
12    InvalidHeaderType,
13    ZeroLengthData,
14    /// Hop counts at or above PATHFINDER_M are invalid on received wire packets.
15    InvalidHopCount(u8),
16}
17
18impl fmt::Display for PacketError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            PacketError::TooShort => write!(f, "Packet too short"),
22            PacketError::ExceedsMtu => write!(f, "Packet exceeds MTU"),
23            PacketError::MissingTransportId => write!(f, "HEADER_2 requires transport_id"),
24            PacketError::InvalidHeaderType => write!(f, "Invalid header type"),
25            PacketError::ZeroLengthData => write!(f, "Zero-length data field"),
26            PacketError::InvalidHopCount(hops) => write!(f, "Invalid hop count: {}", hops),
27        }
28    }
29}
30
31// =============================================================================
32// PacketFlags: packs 5 fields into one byte
33// =============================================================================
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct PacketFlags {
37    pub header_type: u8,
38    pub context_flag: u8,
39    pub transport_type: u8,
40    pub destination_type: u8,
41    pub packet_type: u8,
42}
43
44impl PacketFlags {
45    /// Pack fields into a single flags byte.
46    ///
47    /// Bit layout:
48    /// ```text
49    /// Bit 6:     header_type (1 bit)
50    /// Bit 5:     context_flag (1 bit)
51    /// Bit 4:     transport_type (1 bit)
52    /// Bits 3-2:  destination_type (2 bits)
53    /// Bits 1-0:  packet_type (2 bits)
54    /// ```
55    pub fn pack(&self) -> u8 {
56        (self.header_type << 6)
57            | (self.context_flag << 5)
58            | (self.transport_type << 4)
59            | (self.destination_type << 2)
60            | self.packet_type
61    }
62
63    /// Unpack a flags byte into fields.
64    pub fn unpack(byte: u8) -> Self {
65        PacketFlags {
66            header_type: (byte & 0b01000000) >> 6,
67            context_flag: (byte & 0b00100000) >> 5,
68            transport_type: (byte & 0b00010000) >> 4,
69            destination_type: (byte & 0b00001100) >> 2,
70            packet_type: byte & 0b00000011,
71        }
72    }
73}
74
75// =============================================================================
76// RawPacket: wire-level packet representation
77// =============================================================================
78
79#[derive(Debug, Clone)]
80pub struct RawPacket {
81    pub flags: PacketFlags,
82    pub hops: u8,
83    pub transport_id: Option<[u8; 16]>,
84    pub destination_hash: [u8; 16],
85    pub context: u8,
86    pub data: Vec<u8>,
87    pub raw: Vec<u8>,
88    pub packet_hash: [u8; 32],
89    pub rssi: Option<i16>,
90    pub snr: Option<f32>,
91}
92
93impl RawPacket {
94    /// Pack fields into raw bytes.
95    pub fn pack(
96        flags: PacketFlags,
97        hops: u8,
98        destination_hash: &[u8; 16],
99        transport_id: Option<&[u8; 16]>,
100        context: u8,
101        data: &[u8],
102    ) -> Result<Self, PacketError> {
103        Self::pack_with_max_mtu(
104            flags,
105            hops,
106            destination_hash,
107            transport_id,
108            context,
109            data,
110            constants::MTU,
111        )
112    }
113
114    /// Pack fields into raw bytes and packet hash without constructing a full RawPacket.
115    pub fn pack_raw_with_hash(
116        flags: PacketFlags,
117        hops: u8,
118        destination_hash: &[u8; 16],
119        transport_id: Option<&[u8; 16]>,
120        context: u8,
121        data: &[u8],
122    ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
123        Self::pack_raw_with_hash_with_max_mtu(
124            flags,
125            hops,
126            destination_hash,
127            transport_id,
128            context,
129            data,
130            constants::MTU,
131        )
132    }
133
134    /// Pack fields into raw bytes with a caller-provided MTU limit.
135    pub fn pack_with_max_mtu(
136        flags: PacketFlags,
137        hops: u8,
138        destination_hash: &[u8; 16],
139        transport_id: Option<&[u8; 16]>,
140        context: u8,
141        data: &[u8],
142        max_mtu: usize,
143    ) -> Result<Self, PacketError> {
144        let (raw, packet_hash) = Self::pack_raw_with_hash_with_max_mtu(
145            flags,
146            hops,
147            destination_hash,
148            transport_id,
149            context,
150            data,
151            max_mtu,
152        )?;
153
154        Ok(RawPacket {
155            flags,
156            hops,
157            transport_id: transport_id.copied(),
158            destination_hash: *destination_hash,
159            context,
160            data: data.to_vec(),
161            raw,
162            packet_hash,
163            rssi: None,
164            snr: None,
165        })
166    }
167
168    /// Pack fields into raw bytes and packet hash with a caller-provided MTU limit.
169    pub fn pack_raw_with_hash_with_max_mtu(
170        flags: PacketFlags,
171        hops: u8,
172        destination_hash: &[u8; 16],
173        transport_id: Option<&[u8; 16]>,
174        context: u8,
175        data: &[u8],
176        max_mtu: usize,
177    ) -> Result<(Vec<u8>, [u8; 32]), PacketError> {
178        if flags.header_type == constants::HEADER_2 && transport_id.is_none() {
179            return Err(PacketError::MissingTransportId);
180        }
181
182        let mut raw = Vec::new();
183        raw.push(flags.pack());
184        raw.push(hops);
185
186        if let Some(transport_id) = transport_id {
187            if flags.header_type == constants::HEADER_2 {
188                raw.extend_from_slice(transport_id);
189            }
190        }
191
192        raw.extend_from_slice(destination_hash);
193        raw.push(context);
194        raw.extend_from_slice(data);
195
196        if raw.len() > max_mtu {
197            return Err(PacketError::ExceedsMtu);
198        }
199
200        let packet_hash = hash::full_hash(&Self::compute_hashable_part(flags.header_type, &raw));
201        Ok((raw, packet_hash))
202    }
203
204    /// Unpack raw bytes into fields.
205    pub fn unpack(raw: &[u8]) -> Result<Self, PacketError> {
206        if raw.len() < constants::HEADER_MINSIZE {
207            return Err(PacketError::TooShort);
208        }
209
210        let flags = PacketFlags::unpack(raw[0]);
211        let hops = raw[1];
212        if hops >= constants::PATHFINDER_M {
213            return Err(PacketError::InvalidHopCount(hops));
214        }
215
216        let dst_len = constants::TRUNCATED_HASHLENGTH / 8; // 16
217
218        if flags.header_type == constants::HEADER_2 {
219            // HEADER_2: [flags:1][hops:1][transport_id:16][dest_hash:16][context:1][data:*]
220            let min_len = 2 + dst_len * 2 + 1;
221            if raw.len() < min_len {
222                return Err(PacketError::TooShort);
223            }
224
225            let mut transport_id = [0u8; 16];
226            transport_id.copy_from_slice(&raw[2..2 + dst_len]);
227
228            let mut destination_hash = [0u8; 16];
229            destination_hash.copy_from_slice(&raw[2 + dst_len..2 + 2 * dst_len]);
230
231            let context = raw[2 + 2 * dst_len];
232            let data = raw[2 + 2 * dst_len + 1..].to_vec();
233            if data.is_empty() {
234                return Err(PacketError::ZeroLengthData);
235            }
236
237            let packet_hash = hash::full_hash(&Self::compute_hashable_part(flags.header_type, raw));
238
239            Ok(RawPacket {
240                flags,
241                hops,
242                transport_id: Some(transport_id),
243                destination_hash,
244                context,
245                data,
246                raw: raw.to_vec(),
247                packet_hash,
248                rssi: None,
249                snr: None,
250            })
251        } else if flags.header_type == constants::HEADER_1 {
252            // HEADER_1: [flags:1][hops:1][dest_hash:16][context:1][data:*]
253            let min_len = 2 + dst_len + 1;
254            if raw.len() < min_len {
255                return Err(PacketError::TooShort);
256            }
257
258            let mut destination_hash = [0u8; 16];
259            destination_hash.copy_from_slice(&raw[2..2 + dst_len]);
260
261            let context = raw[2 + dst_len];
262            let data = raw[2 + dst_len + 1..].to_vec();
263            if data.is_empty() {
264                return Err(PacketError::ZeroLengthData);
265            }
266
267            let packet_hash = hash::full_hash(&Self::compute_hashable_part(flags.header_type, raw));
268
269            Ok(RawPacket {
270                flags,
271                hops,
272                transport_id: None,
273                destination_hash,
274                context,
275                data,
276                raw: raw.to_vec(),
277                packet_hash,
278                rssi: None,
279                snr: None,
280            })
281        } else {
282            Err(PacketError::InvalidHeaderType)
283        }
284    }
285
286    /// Get the hashable part of the packet.
287    ///
288    /// From Python Packet.py:354-361:
289    /// - Take raw[0] & 0x0F (mask out upper 4 bits of flags)
290    /// - For HEADER_1: append raw[2:]
291    /// - For HEADER_2: skip transport_id: append raw[18:]
292    pub fn get_hashable_part(&self) -> Vec<u8> {
293        Self::compute_hashable_part(self.flags.header_type, &self.raw)
294    }
295
296    fn compute_hashable_part(header_type: u8, raw: &[u8]) -> Vec<u8> {
297        let mut hashable = Vec::new();
298        hashable.push(raw[0] & 0b00001111);
299        if header_type == constants::HEADER_2 {
300            // Skip transport_id: raw[2..18] is transport_id (16 bytes)
301            hashable.extend_from_slice(&raw[(constants::TRUNCATED_HASHLENGTH / 8 + 2)..]);
302        } else {
303            hashable.extend_from_slice(&raw[2..]);
304        }
305        hashable
306    }
307
308    /// Full SHA-256 hash of the hashable part.
309    pub fn get_hash(&self) -> [u8; 32] {
310        self.packet_hash
311    }
312
313    /// Truncated hash (first 16 bytes) of the cached packet hash.
314    ///
315    /// Packet construction and unpacking compute `packet_hash` once; routing
316    /// paths use this prefix instead of hashing the raw packet again.
317    pub fn get_truncated_hash(&self) -> [u8; 16] {
318        let mut result = [0u8; 16];
319        result.copy_from_slice(&self.packet_hash[..16]);
320        result
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_flags_pack_header1_data_single_broadcast() {
330        let flags = PacketFlags {
331            header_type: constants::HEADER_1,
332            context_flag: constants::FLAG_UNSET,
333            transport_type: constants::TRANSPORT_BROADCAST,
334            destination_type: constants::DESTINATION_SINGLE,
335            packet_type: constants::PACKET_TYPE_DATA,
336        };
337        assert_eq!(flags.pack(), 0x00);
338    }
339
340    #[test]
341    fn test_flags_pack_header2_announce_single_transport() {
342        let flags = PacketFlags {
343            header_type: constants::HEADER_2,
344            context_flag: constants::FLAG_UNSET,
345            transport_type: constants::TRANSPORT_TRANSPORT,
346            destination_type: constants::DESTINATION_SINGLE,
347            packet_type: constants::PACKET_TYPE_ANNOUNCE,
348        };
349        // 0b01010001 = 0x51
350        assert_eq!(flags.pack(), 0x51);
351    }
352
353    #[test]
354    fn test_flags_roundtrip() {
355        for byte in 0..=0x7Fu8 {
356            let flags = PacketFlags::unpack(byte);
357            assert_eq!(flags.pack(), byte);
358        }
359    }
360
361    #[test]
362    fn test_pack_header1() {
363        let dest_hash = [0xAA; 16];
364        let data = b"hello";
365        let flags = PacketFlags {
366            header_type: constants::HEADER_1,
367            context_flag: constants::FLAG_UNSET,
368            transport_type: constants::TRANSPORT_BROADCAST,
369            destination_type: constants::DESTINATION_SINGLE,
370            packet_type: constants::PACKET_TYPE_DATA,
371        };
372
373        let pkt =
374            RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, data).unwrap();
375
376        // Verify layout: [flags:1][hops:1][dest:16][context:1][data:5] = 24 bytes
377        assert_eq!(pkt.raw.len(), 24);
378        assert_eq!(pkt.raw[0], 0x00); // flags
379        assert_eq!(pkt.raw[1], 0x00); // hops
380        assert_eq!(&pkt.raw[2..18], &dest_hash); // dest hash
381        assert_eq!(pkt.raw[18], 0x00); // context
382        assert_eq!(&pkt.raw[19..], b"hello"); // data
383    }
384
385    #[test]
386    fn test_pack_header2() {
387        let dest_hash = [0xAA; 16];
388        let transport_id = [0xBB; 16];
389        let data = b"world";
390        let flags = PacketFlags {
391            header_type: constants::HEADER_2,
392            context_flag: constants::FLAG_UNSET,
393            transport_type: constants::TRANSPORT_TRANSPORT,
394            destination_type: constants::DESTINATION_SINGLE,
395            packet_type: constants::PACKET_TYPE_ANNOUNCE,
396        };
397
398        let pkt = RawPacket::pack(
399            flags,
400            3,
401            &dest_hash,
402            Some(&transport_id),
403            constants::CONTEXT_NONE,
404            data,
405        )
406        .unwrap();
407
408        // Layout: [flags:1][hops:1][transport:16][dest:16][context:1][data:5] = 40 bytes
409        assert_eq!(pkt.raw.len(), 40);
410        assert_eq!(pkt.raw[0], flags.pack());
411        assert_eq!(pkt.raw[1], 3);
412        assert_eq!(&pkt.raw[2..18], &transport_id);
413        assert_eq!(&pkt.raw[18..34], &dest_hash);
414        assert_eq!(pkt.raw[34], 0x00);
415        assert_eq!(&pkt.raw[35..], b"world");
416    }
417
418    #[test]
419    fn test_unpack_roundtrip_header1() {
420        let dest_hash = [0x11; 16];
421        let data = b"test data";
422        let flags = PacketFlags {
423            header_type: constants::HEADER_1,
424            context_flag: constants::FLAG_UNSET,
425            transport_type: constants::TRANSPORT_BROADCAST,
426            destination_type: constants::DESTINATION_SINGLE,
427            packet_type: constants::PACKET_TYPE_DATA,
428        };
429
430        let pkt = RawPacket::pack(
431            flags,
432            5,
433            &dest_hash,
434            None,
435            constants::CONTEXT_RESOURCE,
436            data,
437        )
438        .unwrap();
439        let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
440
441        assert_eq!(unpacked.flags, flags);
442        assert_eq!(unpacked.hops, 5);
443        assert!(unpacked.transport_id.is_none());
444        assert_eq!(unpacked.destination_hash, dest_hash);
445        assert_eq!(unpacked.context, constants::CONTEXT_RESOURCE);
446        assert_eq!(unpacked.data, data);
447        assert_eq!(unpacked.packet_hash, pkt.packet_hash);
448    }
449
450    #[test]
451    fn test_unpack_roundtrip_header2() {
452        let dest_hash = [0x22; 16];
453        let transport_id = [0x33; 16];
454        let data = b"transported";
455        let flags = PacketFlags {
456            header_type: constants::HEADER_2,
457            context_flag: constants::FLAG_SET,
458            transport_type: constants::TRANSPORT_TRANSPORT,
459            destination_type: constants::DESTINATION_SINGLE,
460            packet_type: constants::PACKET_TYPE_ANNOUNCE,
461        };
462
463        let pkt = RawPacket::pack(
464            flags,
465            2,
466            &dest_hash,
467            Some(&transport_id),
468            constants::CONTEXT_NONE,
469            data,
470        )
471        .unwrap();
472        let unpacked = RawPacket::unpack(&pkt.raw).unwrap();
473
474        assert_eq!(unpacked.flags, flags);
475        assert_eq!(unpacked.hops, 2);
476        assert_eq!(unpacked.transport_id.unwrap(), transport_id);
477        assert_eq!(unpacked.destination_hash, dest_hash);
478        assert_eq!(unpacked.context, constants::CONTEXT_NONE);
479        assert_eq!(unpacked.data, data);
480        assert_eq!(unpacked.packet_hash, pkt.packet_hash);
481    }
482
483    #[test]
484    fn truncated_hash_is_derived_from_cached_packet_hash() {
485        let mut packet = RawPacket::pack(
486            PacketFlags {
487                header_type: constants::HEADER_1,
488                context_flag: constants::FLAG_UNSET,
489                transport_type: constants::TRANSPORT_BROADCAST,
490                destination_type: constants::DESTINATION_SINGLE,
491                packet_type: constants::PACKET_TYPE_DATA,
492            },
493            1,
494            &[0x42; 16],
495            None,
496            constants::CONTEXT_NONE,
497            b"cached hash",
498        )
499        .unwrap();
500        let expected: [u8; 16] = packet.packet_hash[..16].try_into().unwrap();
501
502        // Changing the retained wire buffer demonstrates that this accessor is
503        // a prefix operation, not a second hash computation.
504        let last = packet.raw.len() - 1;
505        packet.raw[last] ^= 0xff;
506
507        assert_eq!(packet.get_truncated_hash(), expected);
508    }
509
510    #[test]
511    fn test_unpack_too_short() {
512        assert!(RawPacket::unpack(&[0x00; 5]).is_err());
513    }
514
515    #[test]
516    fn unpack_rejects_zero_length_data_for_both_header_types() {
517        for header_type in [constants::HEADER_1, constants::HEADER_2] {
518            let packet = RawPacket::pack(
519                PacketFlags {
520                    header_type,
521                    context_flag: constants::FLAG_UNSET,
522                    transport_type: constants::TRANSPORT_BROADCAST,
523                    destination_type: constants::DESTINATION_SINGLE,
524                    packet_type: constants::PACKET_TYPE_DATA,
525                },
526                0,
527                &[0x42; 16],
528                (header_type == constants::HEADER_2).then_some(&[0x24; 16]),
529                constants::CONTEXT_NONE,
530                b"",
531            )
532            .unwrap();
533            assert!(matches!(
534                RawPacket::unpack(&packet.raw),
535                Err(PacketError::ZeroLengthData)
536            ));
537        }
538    }
539
540    #[test]
541    fn unpack_enforces_pathfinder_hop_boundary_but_pack_remains_permissive() {
542        let flags = PacketFlags {
543            header_type: constants::HEADER_1,
544            context_flag: constants::FLAG_UNSET,
545            transport_type: constants::TRANSPORT_BROADCAST,
546            destination_type: constants::DESTINATION_SINGLE,
547            packet_type: constants::PACKET_TYPE_DATA,
548        };
549        let accepted = RawPacket::pack(flags, 127, &[1; 16], None, 0, b"x").unwrap();
550        assert_eq!(RawPacket::unpack(&accepted.raw).unwrap().hops, 127);
551
552        for hops in [128, 255] {
553            let packed = RawPacket::pack(flags, hops, &[1; 16], None, 0, b"x").unwrap();
554            assert!(matches!(
555                RawPacket::unpack(&packed.raw),
556                Err(PacketError::InvalidHopCount(value)) if value == hops
557            ));
558        }
559    }
560
561    #[test]
562    fn test_pack_exceeds_mtu() {
563        let flags = PacketFlags {
564            header_type: constants::HEADER_1,
565            context_flag: constants::FLAG_UNSET,
566            transport_type: constants::TRANSPORT_BROADCAST,
567            destination_type: constants::DESTINATION_SINGLE,
568            packet_type: constants::PACKET_TYPE_DATA,
569        };
570        let data = [0u8; 500]; // way too much data
571        let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, &data);
572        assert!(result.is_err());
573    }
574
575    #[test]
576    fn test_header2_missing_transport_id() {
577        let flags = PacketFlags {
578            header_type: constants::HEADER_2,
579            context_flag: constants::FLAG_UNSET,
580            transport_type: constants::TRANSPORT_TRANSPORT,
581            destination_type: constants::DESTINATION_SINGLE,
582            packet_type: constants::PACKET_TYPE_ANNOUNCE,
583        };
584        let result = RawPacket::pack(flags, 0, &[0; 16], None, 0, b"data");
585        assert!(result.is_err());
586    }
587
588    #[test]
589    fn test_hashable_part_header1_masks_upper_flags() {
590        let dest_hash = [0xCC; 16];
591        let flags = PacketFlags {
592            header_type: constants::HEADER_1,
593            context_flag: constants::FLAG_SET,
594            transport_type: constants::TRANSPORT_BROADCAST,
595            destination_type: constants::DESTINATION_SINGLE,
596            packet_type: constants::PACKET_TYPE_DATA,
597        };
598
599        let pkt =
600            RawPacket::pack(flags, 0, &dest_hash, None, constants::CONTEXT_NONE, b"test").unwrap();
601        let hashable = pkt.get_hashable_part();
602
603        // First byte should have upper 4 bits masked out
604        assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
605        // Rest should be raw[2:]
606        assert_eq!(&hashable[1..], &pkt.raw[2..]);
607    }
608
609    #[test]
610    fn test_hashable_part_header2_strips_transport_id() {
611        let dest_hash = [0xDD; 16];
612        let transport_id = [0xEE; 16];
613        let flags = PacketFlags {
614            header_type: constants::HEADER_2,
615            context_flag: constants::FLAG_UNSET,
616            transport_type: constants::TRANSPORT_TRANSPORT,
617            destination_type: constants::DESTINATION_SINGLE,
618            packet_type: constants::PACKET_TYPE_ANNOUNCE,
619        };
620
621        let pkt = RawPacket::pack(
622            flags,
623            0,
624            &dest_hash,
625            Some(&transport_id),
626            constants::CONTEXT_NONE,
627            b"data",
628        )
629        .unwrap();
630        let hashable = pkt.get_hashable_part();
631
632        // First byte: flags masked
633        assert_eq!(hashable[0], pkt.raw[0] & 0x0F);
634        // Should skip transport_id: raw[18:] = dest_hash + context + data
635        assert_eq!(&hashable[1..], &pkt.raw[18..]);
636    }
637}