Skip to main content

pim_protocol/
route_frame.rs

1//! Route advertisement frame definitions.
2
3use bytes::{Buf, BufMut, BytesMut};
4
5use pim_core::{FrameCodec, NodeId, PimError};
6
7/// A single route entry in a route advertisement.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct RouteEntry {
10    /// Destination node this route describes.
11    pub destination: NodeId,
12    /// Hop count to reach `destination`.
13    pub hops: u8,
14    /// bit 0: is_gateway
15    pub flags: u8,
16    /// Mesh IPv4 address assigned to `destination`.
17    pub mesh_ip: [u8; 4],
18}
19
20impl RouteEntry {
21    /// Return `true` if this route advertises gateway capability.
22    pub fn is_gateway(&self) -> bool {
23        self.flags & 0x01 != 0
24    }
25}
26
27/// Route advertisement broadcast to direct peers.
28///
29/// Layout: origin_id(16) + sequence(8) + entry_count(2) + entries(N * 22) + signature(64)
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct RouteUpdateFrame {
32    /// Node originating this advertisement.
33    pub origin_id: NodeId,
34    /// Monotonic sequence number used for replay protection.
35    pub sequence: u64,
36    /// Reachability entries being advertised.
37    pub entries: Vec<RouteEntry>,
38    /// Ed25519 signature over the frame contents.
39    pub signature: [u8; 64],
40}
41
42const HEADER_SIZE: usize = 16 + 8 + 2; // 26
43const ENTRY_SIZE: usize = 16 + 1 + 1 + 4; // 22
44const SIGNATURE_SIZE: usize = 64;
45const MAX_ENTRIES: u16 = 1000;
46
47impl FrameCodec for RouteUpdateFrame {
48    fn encode(&self, buf: &mut BytesMut) {
49        buf.put_slice(self.origin_id.as_bytes());
50        buf.put_u64(self.sequence);
51        buf.put_u16(self.entries.len() as u16);
52        for entry in &self.entries {
53            buf.put_slice(entry.destination.as_bytes());
54            buf.put_u8(entry.hops);
55            buf.put_u8(entry.flags);
56            buf.put_slice(&entry.mesh_ip);
57        }
58        buf.put_slice(&self.signature);
59    }
60
61    fn decode(buf: &mut BytesMut) -> Result<Self, PimError> {
62        if buf.len() < HEADER_SIZE {
63            return Err(PimError::Protocol(
64                "route update too short for header".into(),
65            ));
66        }
67
68        let mut origin_bytes = [0u8; 16];
69        origin_bytes.copy_from_slice(&buf[0..16]);
70        let origin_id = NodeId::from_bytes(origin_bytes);
71
72        let sequence = (&buf[16..24]).get_u64();
73        let entry_count = (&buf[24..26]).get_u16();
74
75        if entry_count > MAX_ENTRIES {
76            return Err(PimError::Protocol(format!(
77                "too many route entries: {entry_count}, max {MAX_ENTRIES}"
78            )));
79        }
80
81        let total = HEADER_SIZE + (entry_count as usize * ENTRY_SIZE) + SIGNATURE_SIZE;
82        if buf.len() < total {
83            return Err(PimError::Protocol(format!(
84                "route update truncated: need {total}, have {}",
85                buf.len()
86            )));
87        }
88
89        let mut entries = Vec::with_capacity(entry_count as usize);
90        let mut offset = HEADER_SIZE;
91        for _ in 0..entry_count {
92            let mut dest = [0u8; 16];
93            dest.copy_from_slice(&buf[offset..offset + 16]);
94            let hops = buf[offset + 16];
95            let flags = buf[offset + 17];
96            let mut mesh_ip = [0u8; 4];
97            mesh_ip.copy_from_slice(&buf[offset + 18..offset + 22]);
98            entries.push(RouteEntry {
99                destination: NodeId::from_bytes(dest),
100                hops,
101                flags,
102                mesh_ip,
103            });
104            offset += ENTRY_SIZE;
105        }
106
107        let mut signature = [0u8; 64];
108        signature.copy_from_slice(&buf[offset..offset + SIGNATURE_SIZE]);
109
110        buf.advance(total);
111
112        Ok(RouteUpdateFrame {
113            origin_id,
114            sequence,
115            entries,
116            signature,
117        })
118    }
119}
120
121#[cfg(test)]
122mod tests;