Skip to main content

pim_protocol/
control_frame.rs

1//! Control-plane messages carried inside transport frames.
2
3use bytes::{Buf, BufMut, BytesMut};
4
5use pim_core::{FrameCodec, NodeId, PimError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9/// Discriminator for [`ControlFrame`] payloads.
10pub enum ControlType {
11    /// Client requests a mesh IPv4 assignment from a gateway.
12    IpRequest = 0x01,
13    /// Gateway assigns a mesh IPv4 configuration lease to a client.
14    IpAssign = 0x02,
15    /// Peer is leaving and wants its state cleaned up promptly.
16    Goodbye = 0x03,
17    /// Session keys should be renegotiated.
18    Rekey = 0x04,
19    /// RTT probe request.
20    Ping = 0x05,
21    /// RTT probe response.
22    Pong = 0x06,
23}
24
25impl ControlType {
26    /// Decode a raw control-type tag from the wire.
27    pub fn from_u8(v: u8) -> Result<Self, PimError> {
28        match v {
29            0x01 => Ok(Self::IpRequest),
30            0x02 => Ok(Self::IpAssign),
31            0x03 => Ok(Self::Goodbye),
32            0x04 => Ok(Self::Rekey),
33            0x05 => Ok(Self::Ping),
34            0x06 => Ok(Self::Pong),
35            other => Err(PimError::Protocol(format!(
36                "unknown control type: 0x{other:02x}"
37            ))),
38        }
39    }
40}
41
42/// Multiplexed control message.
43///
44/// Layout: control_type(1) + body (variable, depends on type)
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum ControlFrame {
47    /// Request an address lease from a gateway.
48    IpRequest {
49        /// Node requesting a mesh IP allocation.
50        requester_id: NodeId,
51    },
52    /// Lease configuration returned by a gateway.
53    IpAssign {
54        /// Assigned mesh IPv4 address.
55        assigned_ip: [u8; 4],
56        /// CIDR prefix length for the assigned subnet.
57        subnet_mask: u8,
58        /// Mesh IPv4 address of the serving gateway.
59        gateway_ip: [u8; 4],
60        /// Lease duration in seconds.
61        lease_seconds: u32,
62    },
63    /// Graceful disconnect notification.
64    Goodbye {
65        /// Node that is departing.
66        departing_id: NodeId,
67        /// Implementation-defined reason code.
68        reason: u8,
69    },
70    /// Request that the session be rekeyed.
71    Rekey,
72    /// Ping carrying an opaque nonce.
73    Ping {
74        /// Opaque value echoed by the corresponding pong.
75        nonce: u64,
76    },
77    /// Pong echoing a ping nonce.
78    Pong {
79        /// Opaque value copied from the ping request.
80        nonce: u64,
81    },
82}
83
84impl FrameCodec for ControlFrame {
85    fn encode(&self, buf: &mut BytesMut) {
86        match self {
87            ControlFrame::IpRequest { requester_id } => {
88                buf.put_u8(ControlType::IpRequest as u8);
89                buf.put_slice(requester_id.as_bytes());
90            }
91            ControlFrame::IpAssign {
92                assigned_ip,
93                subnet_mask,
94                gateway_ip,
95                lease_seconds,
96            } => {
97                buf.put_u8(ControlType::IpAssign as u8);
98                buf.put_slice(assigned_ip);
99                buf.put_u8(*subnet_mask);
100                buf.put_slice(gateway_ip);
101                buf.put_u32(*lease_seconds);
102            }
103            ControlFrame::Goodbye {
104                departing_id,
105                reason,
106            } => {
107                buf.put_u8(ControlType::Goodbye as u8);
108                buf.put_slice(departing_id.as_bytes());
109                buf.put_u8(*reason);
110            }
111            ControlFrame::Rekey => {
112                buf.put_u8(ControlType::Rekey as u8);
113            }
114            ControlFrame::Ping { nonce } => {
115                buf.put_u8(ControlType::Ping as u8);
116                buf.put_u64(*nonce);
117            }
118            ControlFrame::Pong { nonce } => {
119                buf.put_u8(ControlType::Pong as u8);
120                buf.put_u64(*nonce);
121            }
122        }
123    }
124
125    fn decode(buf: &mut BytesMut) -> Result<Self, PimError> {
126        if buf.is_empty() {
127            return Err(PimError::Protocol("control frame empty".into()));
128        }
129
130        let control_type = ControlType::from_u8(buf[0])?;
131
132        match control_type {
133            ControlType::IpRequest => {
134                if buf.len() < 17 {
135                    return Err(PimError::Protocol("IpRequest too short".into()));
136                }
137                let mut id = [0u8; 16];
138                id.copy_from_slice(&buf[1..17]);
139                buf.advance(17);
140                Ok(ControlFrame::IpRequest {
141                    requester_id: NodeId::from_bytes(id),
142                })
143            }
144            ControlType::IpAssign => {
145                if buf.len() < 14 {
146                    // 1 + 4 + 1 + 4 + 4
147                    return Err(PimError::Protocol("IpAssign too short".into()));
148                }
149                let mut assigned_ip = [0u8; 4];
150                assigned_ip.copy_from_slice(&buf[1..5]);
151                let subnet_mask = buf[5];
152                let mut gateway_ip = [0u8; 4];
153                gateway_ip.copy_from_slice(&buf[6..10]);
154                let lease_seconds = (&buf[10..14]).get_u32();
155                buf.advance(14);
156                Ok(ControlFrame::IpAssign {
157                    assigned_ip,
158                    subnet_mask,
159                    gateway_ip,
160                    lease_seconds,
161                })
162            }
163            ControlType::Goodbye => {
164                if buf.len() < 18 {
165                    // 1 + 16 + 1
166                    return Err(PimError::Protocol("Goodbye too short".into()));
167                }
168                let mut id = [0u8; 16];
169                id.copy_from_slice(&buf[1..17]);
170                let reason = buf[17];
171                buf.advance(18);
172                Ok(ControlFrame::Goodbye {
173                    departing_id: NodeId::from_bytes(id),
174                    reason,
175                })
176            }
177            ControlType::Rekey => {
178                buf.advance(1);
179                Ok(ControlFrame::Rekey)
180            }
181            ControlType::Ping => {
182                if buf.len() < 9 {
183                    return Err(PimError::Protocol("Ping too short".into()));
184                }
185                let nonce = (&buf[1..9]).get_u64();
186                buf.advance(9);
187                Ok(ControlFrame::Ping { nonce })
188            }
189            ControlType::Pong => {
190                if buf.len() < 9 {
191                    return Err(PimError::Protocol("Pong too short".into()));
192                }
193                let nonce = (&buf[1..9]).get_u64();
194                buf.advance(9);
195                Ok(ControlFrame::Pong { nonce })
196            }
197        }
198    }
199}
200
201#[cfg(test)]
202mod tests;