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