Skip to main content

rns_core/
packet.rs

1use core::fmt;
2
3use alloc::vec::Vec;
4use sha2::Digest;
5
6use crate::buffer::StaticBuffer;
7use crate::crypt::fernet::{FERNET_MAX_PADDING_SIZE, FERNET_OVERHEAD_SIZE};
8use crate::error::RnsError;
9use crate::hash::AddressHash;
10use crate::hash::Hash;
11use crate::hash::ADDRESS_HASH_SIZE;
12
13// Match Python Reticulum default MTU (500) minus max header and IFAC sizes.
14// 500 - (2 + 1 + 16*2) - 1 = 464
15pub const PACKET_MDU: usize = 464usize;
16pub const LXMF_MAX_PAYLOAD: usize = PACKET_MDU - FERNET_OVERHEAD_SIZE - FERNET_MAX_PADDING_SIZE;
17pub const PACKET_IFAC_MAX_LENGTH: usize = 64usize;
18
19#[derive(Debug, PartialEq, Eq, Copy, Clone)]
20pub enum IfacFlag {
21    Open = 0b0,
22    Authenticated = 0b1,
23}
24
25impl From<u8> for IfacFlag {
26    fn from(value: u8) -> Self {
27        match value {
28            0 => IfacFlag::Open,
29            1 => IfacFlag::Authenticated,
30            _ => IfacFlag::Open,
31        }
32    }
33}
34
35#[derive(Debug, PartialEq, Eq, Copy, Clone)]
36pub enum HeaderType {
37    Type1 = 0b0,
38    Type2 = 0b1,
39}
40
41impl From<u8> for HeaderType {
42    fn from(value: u8) -> Self {
43        match value & 0b1 {
44            0 => HeaderType::Type1,
45            1 => HeaderType::Type2,
46            _ => HeaderType::Type1,
47        }
48    }
49}
50
51#[derive(Debug, PartialEq, Eq, Copy, Clone)]
52pub enum PropagationType {
53    Broadcast = 0b0,
54    Transport = 0b1,
55}
56
57impl From<u8> for PropagationType {
58    fn from(value: u8) -> Self {
59        match value & 0b1 {
60            0b0 => PropagationType::Broadcast,
61            0b1 => PropagationType::Transport,
62            _ => PropagationType::Broadcast,
63        }
64    }
65}
66
67#[derive(Debug, PartialEq, Eq, Copy, Clone)]
68pub enum ContextFlag {
69    Unset = 0b0,
70    Set = 0b1,
71}
72
73impl From<u8> for ContextFlag {
74    fn from(value: u8) -> Self {
75        match value & 0b1 {
76            0b0 => ContextFlag::Unset,
77            0b1 => ContextFlag::Set,
78            _ => ContextFlag::Unset,
79        }
80    }
81}
82
83#[derive(Debug, PartialEq, Eq, Copy, Clone)]
84pub enum DestinationType {
85    Single = 0b00,
86    Group = 0b01,
87    Plain = 0b10,
88    Link = 0b11,
89}
90
91impl From<u8> for DestinationType {
92    fn from(value: u8) -> Self {
93        match value & 0b11 {
94            0b00 => DestinationType::Single,
95            0b01 => DestinationType::Group,
96            0b10 => DestinationType::Plain,
97            0b11 => DestinationType::Link,
98            _ => DestinationType::Single,
99        }
100    }
101}
102
103#[derive(Debug, PartialEq, Eq, Copy, Clone)]
104pub enum PacketType {
105    Data = 0b00,
106    Announce = 0b01,
107    LinkRequest = 0b10,
108    Proof = 0b11,
109}
110
111impl From<u8> for PacketType {
112    fn from(value: u8) -> Self {
113        match value & 0b11 {
114            0b00 => PacketType::Data,
115            0b01 => PacketType::Announce,
116            0b10 => PacketType::LinkRequest,
117            0b11 => PacketType::Proof,
118            _ => PacketType::Data,
119        }
120    }
121}
122
123#[derive(Debug, PartialEq, Eq, Copy, Clone)]
124pub enum PacketContext {
125    None = 0x00,                    // Generic data packet
126    Resource = 0x01,                // Packet is part of a resource
127    ResourceAdvrtisement = 0x02,    // Packet is a resource advertisement
128    ResourceRequest = 0x03,         // Packet is a resource part request
129    ResourceHashUpdate = 0x04,      // Packet is a resource hashmap update
130    ResourceProof = 0x05,           // Packet is a resource proof
131    ResourceInitiatorCancel = 0x06, // Packet is a resource initiator cancel message
132    ResourceReceiverCancel = 0x07,  // Packet is a resource receiver cancel message
133    CacheRequest = 0x08,            // Packet is a cache request
134    Request = 0x09,                 // Packet is a request
135    Response = 0x0A,                // Packet is a response to a request
136    PathResponse = 0x0B,            // Packet is a response to a path request
137    Command = 0x0C,                 // Packet is a command
138    CommandStatus = 0x0D,           // Packet is a status of an executed command
139    Channel = 0x0E,                 // Packet contains link channel data
140    KeepAlive = 0xFA,               // Packet is a keepalive packet
141    LinkIdentify = 0xFB,            // Packet is a link peer identification proof
142    LinkClose = 0xFC,               // Packet is a link close message
143    LinkProof = 0xFD,               // Packet is a link packet proof
144    LinkRTT = 0xFE,                 // Packet is a link request round-trip time measurement
145    LinkRequestProof = 0xFF,        // Packet is a link request proof
146}
147
148impl From<u8> for PacketContext {
149    fn from(value: u8) -> Self {
150        match value {
151            0x01 => PacketContext::Resource,
152            0x02 => PacketContext::ResourceAdvrtisement,
153            0x03 => PacketContext::ResourceRequest,
154            0x04 => PacketContext::ResourceHashUpdate,
155            0x05 => PacketContext::ResourceProof,
156            0x06 => PacketContext::ResourceInitiatorCancel,
157            0x07 => PacketContext::ResourceReceiverCancel,
158            0x08 => PacketContext::CacheRequest,
159            0x09 => PacketContext::Request,
160            0x0A => PacketContext::Response,
161            0x0B => PacketContext::PathResponse,
162            0x0C => PacketContext::Command,
163            0x0D => PacketContext::CommandStatus,
164            0x0E => PacketContext::Channel,
165            0xFA => PacketContext::KeepAlive,
166            0xFB => PacketContext::LinkIdentify,
167            0xFC => PacketContext::LinkClose,
168            0xFD => PacketContext::LinkProof,
169            0xFE => PacketContext::LinkRTT,
170            0xFF => PacketContext::LinkRequestProof,
171            _ => PacketContext::None,
172        }
173    }
174}
175
176#[derive(Debug, PartialEq, Eq, Copy, Clone)]
177pub struct Header {
178    pub ifac_flag: IfacFlag,
179    pub header_type: HeaderType,
180    pub context_flag: ContextFlag,
181    pub propagation_type: PropagationType,
182    pub destination_type: DestinationType,
183    pub packet_type: PacketType,
184    pub hops: u8,
185}
186
187impl Default for Header {
188    fn default() -> Self {
189        Self {
190            ifac_flag: IfacFlag::Open,
191            header_type: HeaderType::Type1,
192            context_flag: ContextFlag::Unset,
193            propagation_type: PropagationType::Broadcast,
194            destination_type: DestinationType::Single,
195            packet_type: PacketType::Data,
196            hops: 0,
197        }
198    }
199}
200
201impl Header {
202    pub fn to_meta(&self) -> u8 {
203        (self.ifac_flag as u8) << 7
204            | (self.header_type as u8) << 6
205            | (self.context_flag as u8) << 5
206            | (self.propagation_type as u8) << 4
207            | (self.destination_type as u8) << 2
208            | (self.packet_type as u8)
209    }
210
211    pub fn from_meta(meta: u8) -> Self {
212        Self {
213            ifac_flag: IfacFlag::from(meta >> 7),
214            header_type: HeaderType::from(meta >> 6),
215            context_flag: ContextFlag::from(meta >> 5),
216            propagation_type: PropagationType::from(meta >> 4),
217            destination_type: DestinationType::from(meta >> 2),
218            packet_type: PacketType::from(meta),
219            hops: 0,
220        }
221    }
222}
223
224impl fmt::Display for Header {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(
227            f,
228            "{:b}{:b}{:b}{:b}{:0>2b}{:0>2b}.{}",
229            self.ifac_flag as u8,
230            self.header_type as u8,
231            self.context_flag as u8,
232            self.propagation_type as u8,
233            self.destination_type as u8,
234            self.packet_type as u8,
235            self.hops,
236        )
237    }
238}
239
240pub type PacketDataBuffer = StaticBuffer<PACKET_MDU>;
241
242#[derive(Debug, PartialEq, Eq, Copy, Clone)]
243pub struct PacketIfac {
244    pub access_code: [u8; PACKET_IFAC_MAX_LENGTH],
245    pub length: usize,
246}
247
248impl PacketIfac {
249    pub fn new_from_slice(slice: &[u8]) -> Self {
250        let mut access_code = [0u8; PACKET_IFAC_MAX_LENGTH];
251        access_code[..slice.len()].copy_from_slice(slice);
252        Self { access_code, length: slice.len() }
253    }
254
255    pub fn as_slice(&self) -> &[u8] {
256        &self.access_code[..self.length]
257    }
258}
259
260#[derive(Debug, PartialEq, Eq, Copy, Clone)]
261pub struct Packet {
262    pub header: Header,
263    pub ifac: Option<PacketIfac>,
264    pub destination: AddressHash,
265    pub transport: Option<AddressHash>,
266    pub context: PacketContext,
267    pub data: PacketDataBuffer,
268}
269
270impl Packet {
271    pub const LXMF_MAX_PAYLOAD: usize = LXMF_MAX_PAYLOAD;
272
273    pub fn from_bytes(bytes: &[u8]) -> Result<Self, RnsError> {
274        let min_len = 2 + ADDRESS_HASH_SIZE + 1;
275        if bytes.len() < min_len {
276            return Err(RnsError::InvalidArgument);
277        }
278
279        let flags = bytes[0];
280        let hops = bytes[1];
281
282        let mut header = Header::from_meta(flags);
283        header.hops = hops;
284
285        let mut idx = 2;
286
287        let transport = if header.header_type == HeaderType::Type2 {
288            if bytes.len() < idx + ADDRESS_HASH_SIZE {
289                return Err(RnsError::InvalidArgument);
290            }
291            let mut raw = [0u8; ADDRESS_HASH_SIZE];
292            raw.copy_from_slice(&bytes[idx..idx + ADDRESS_HASH_SIZE]);
293            idx += ADDRESS_HASH_SIZE;
294            Some(AddressHash::new(raw))
295        } else {
296            None
297        };
298
299        if bytes.len() < idx + ADDRESS_HASH_SIZE + 1 {
300            return Err(RnsError::InvalidArgument);
301        }
302
303        let mut dest_raw = [0u8; ADDRESS_HASH_SIZE];
304        dest_raw.copy_from_slice(&bytes[idx..idx + ADDRESS_HASH_SIZE]);
305        idx += ADDRESS_HASH_SIZE;
306        let destination = AddressHash::new(dest_raw);
307
308        let context = PacketContext::from(bytes[idx]);
309        idx += 1;
310
311        let data = PacketDataBuffer::new_from_slice(&bytes[idx..]);
312
313        Ok(Self { header, ifac: None, destination, transport, context, data })
314    }
315
316    pub fn to_bytes(&self) -> Result<Vec<u8>, RnsError> {
317        let mut out = Vec::with_capacity(2 + ADDRESS_HASH_SIZE + 1 + self.data.len());
318
319        out.push(self.header.to_meta());
320        out.push(self.header.hops);
321
322        if self.header.header_type == HeaderType::Type2 {
323            let transport = self.transport.ok_or(RnsError::InvalidArgument)?;
324            out.extend_from_slice(transport.as_slice());
325        }
326
327        out.extend_from_slice(self.destination.as_slice());
328        out.push(self.context as u8);
329        out.extend_from_slice(self.data.as_slice());
330
331        Ok(out)
332    }
333
334    pub fn hash(&self) -> Hash {
335        Hash::new(
336            Hash::generator()
337                .chain_update([self.header.to_meta() & 0b00001111])
338                .chain_update(self.destination.as_slice())
339                .chain_update([self.context as u8])
340                .chain_update(self.data.as_slice())
341                .finalize()
342                .into(),
343        )
344    }
345
346    pub fn fragment_for_lxmf(data: &[u8]) -> Result<Vec<Packet>, RnsError> {
347        let mut out = Vec::new();
348        for chunk in data.chunks(Self::LXMF_MAX_PAYLOAD) {
349            let packet = Packet { data: StaticBuffer::new_from_slice(chunk), ..Default::default() };
350            out.push(packet);
351        }
352        Ok(out)
353    }
354}
355
356impl Default for Packet {
357    fn default() -> Self {
358        Self {
359            header: Default::default(),
360            destination: AddressHash::new_empty(),
361            data: Default::default(),
362            ifac: None,
363            transport: None,
364            context: crate::packet::PacketContext::None,
365        }
366    }
367}
368
369impl fmt::Display for Packet {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        write!(f, "[{}", self.header)?;
372
373        if let Some(transport) = self.transport {
374            write!(f, " {}", transport)?;
375        }
376
377        write!(f, " {}", self.destination)?;
378
379        write!(f, " 0x[{}]]", self.data.len())?;
380
381        Ok(())
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::{
388        ContextFlag, DestinationType, Header, HeaderType, IfacFlag, PacketType, PropagationType,
389    };
390
391    #[test]
392    fn header_meta_roundtrip_preserves_context_and_transport_bits() {
393        let header = Header {
394            ifac_flag: IfacFlag::Open,
395            header_type: HeaderType::Type1,
396            context_flag: ContextFlag::Set,
397            propagation_type: PropagationType::Transport,
398            destination_type: DestinationType::Single,
399            packet_type: PacketType::Announce,
400            hops: 0,
401        };
402
403        let meta = header.to_meta();
404        assert_eq!(meta & 0b0010_0000, 0b0010_0000);
405        assert_eq!(meta & 0b0001_0000, 0b0001_0000);
406
407        let decoded = Header::from_meta(meta);
408        assert_eq!(decoded.context_flag, ContextFlag::Set);
409        assert_eq!(decoded.propagation_type, PropagationType::Transport);
410    }
411}