pim_protocol/
control_frame.rs1use bytes::{Buf, BufMut, Bytes, BytesMut};
4
5use pim_core::{FrameCodec, NodeId, PimError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u8)]
9pub enum ControlType {
11 IpRequest = 0x01,
13 IpAssign = 0x02,
15 Goodbye = 0x03,
17 Rekey = 0x04,
19 Ping = 0x05,
21 Pong = 0x06,
23 PeerInfo = 0x07,
25 PluginPayload = 0x08,
27}
28
29impl ControlType {
30 pub fn from_u8(v: u8) -> Result<Self, PimError> {
32 match v {
33 0x01 => Ok(Self::IpRequest),
34 0x02 => Ok(Self::IpAssign),
35 0x03 => Ok(Self::Goodbye),
36 0x04 => Ok(Self::Rekey),
37 0x05 => Ok(Self::Ping),
38 0x06 => Ok(Self::Pong),
39 0x07 => Ok(Self::PeerInfo),
40 0x08 => Ok(Self::PluginPayload),
41 other => Err(PimError::Protocol(format!(
42 "unknown control type: 0x{other:02x}"
43 ))),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum ControlFrame {
58 IpRequest {
60 requester_id: NodeId,
62 },
63 IpAssign {
65 assigned_ip: [u8; 4],
67 subnet_mask: u8,
69 gateway_ip: [u8; 4],
71 lease_seconds: u32,
73 },
74 Goodbye {
76 departing_id: NodeId,
78 reason: u8,
80 },
81 Rekey,
83 Ping {
85 nonce: u64,
87 },
88 Pong {
90 nonce: u64,
92 },
93 PeerInfo {
97 x25519_pub: [u8; 32],
99 friendly_name: String,
102 },
103 PluginPayload {
120 kind: String,
122 body: Bytes,
125 },
126}
127
128impl FrameCodec for ControlFrame {
129 fn encode(&self, buf: &mut BytesMut) {
130 match self {
131 ControlFrame::IpRequest { requester_id } => {
132 buf.put_u8(ControlType::IpRequest as u8);
133 buf.put_slice(requester_id.as_bytes());
134 }
135 ControlFrame::IpAssign {
136 assigned_ip,
137 subnet_mask,
138 gateway_ip,
139 lease_seconds,
140 } => {
141 buf.put_u8(ControlType::IpAssign as u8);
142 buf.put_slice(assigned_ip);
143 buf.put_u8(*subnet_mask);
144 buf.put_slice(gateway_ip);
145 buf.put_u32(*lease_seconds);
146 }
147 ControlFrame::Goodbye {
148 departing_id,
149 reason,
150 } => {
151 buf.put_u8(ControlType::Goodbye as u8);
152 buf.put_slice(departing_id.as_bytes());
153 buf.put_u8(*reason);
154 }
155 ControlFrame::Rekey => {
156 buf.put_u8(ControlType::Rekey as u8);
157 }
158 ControlFrame::Ping { nonce } => {
159 buf.put_u8(ControlType::Ping as u8);
160 buf.put_u64(*nonce);
161 }
162 ControlFrame::Pong { nonce } => {
163 buf.put_u8(ControlType::Pong as u8);
164 buf.put_u64(*nonce);
165 }
166 ControlFrame::PeerInfo {
167 x25519_pub,
168 friendly_name,
169 } => {
170 buf.put_u8(ControlType::PeerInfo as u8);
171 buf.put_slice(x25519_pub);
172 let name_bytes = friendly_name.as_bytes();
173 let name_len = name_bytes.len().min(255) as u8;
174 buf.put_u8(name_len);
175 buf.put_slice(&name_bytes[..name_len as usize]);
176 }
177 ControlFrame::PluginPayload { kind, body } => {
178 buf.put_u8(ControlType::PluginPayload as u8);
179 let kind_bytes = kind.as_bytes();
180 let kind_len = kind_bytes.len().min(255) as u8;
181 buf.put_u8(kind_len);
182 buf.put_slice(&kind_bytes[..kind_len as usize]);
183 let body_len = body.len().min(u16::MAX as usize) as u16;
184 buf.put_u16(body_len);
185 buf.put_slice(&body[..body_len as usize]);
186 }
187 }
188 }
189
190 fn decode(buf: &mut BytesMut) -> Result<Self, PimError> {
191 if buf.is_empty() {
192 return Err(PimError::Protocol("control frame empty".into()));
193 }
194
195 let control_type = ControlType::from_u8(buf[0])?;
196
197 match control_type {
198 ControlType::IpRequest => {
199 if buf.len() < 17 {
200 return Err(PimError::Protocol("IpRequest too short".into()));
201 }
202 let mut id = [0u8; 16];
203 id.copy_from_slice(&buf[1..17]);
204 buf.advance(17);
205 Ok(ControlFrame::IpRequest {
206 requester_id: NodeId::from_bytes(id),
207 })
208 }
209 ControlType::IpAssign => {
210 if buf.len() < 14 {
211 return Err(PimError::Protocol("IpAssign too short".into()));
213 }
214 let mut assigned_ip = [0u8; 4];
215 assigned_ip.copy_from_slice(&buf[1..5]);
216 let subnet_mask = buf[5];
217 let mut gateway_ip = [0u8; 4];
218 gateway_ip.copy_from_slice(&buf[6..10]);
219 let lease_seconds = (&buf[10..14]).get_u32();
220 buf.advance(14);
221 Ok(ControlFrame::IpAssign {
222 assigned_ip,
223 subnet_mask,
224 gateway_ip,
225 lease_seconds,
226 })
227 }
228 ControlType::Goodbye => {
229 if buf.len() < 18 {
230 return Err(PimError::Protocol("Goodbye too short".into()));
232 }
233 let mut id = [0u8; 16];
234 id.copy_from_slice(&buf[1..17]);
235 let reason = buf[17];
236 buf.advance(18);
237 Ok(ControlFrame::Goodbye {
238 departing_id: NodeId::from_bytes(id),
239 reason,
240 })
241 }
242 ControlType::Rekey => {
243 buf.advance(1);
244 Ok(ControlFrame::Rekey)
245 }
246 ControlType::Ping => {
247 if buf.len() < 9 {
248 return Err(PimError::Protocol("Ping too short".into()));
249 }
250 let nonce = (&buf[1..9]).get_u64();
251 buf.advance(9);
252 Ok(ControlFrame::Ping { nonce })
253 }
254 ControlType::Pong => {
255 if buf.len() < 9 {
256 return Err(PimError::Protocol("Pong too short".into()));
257 }
258 let nonce = (&buf[1..9]).get_u64();
259 buf.advance(9);
260 Ok(ControlFrame::Pong { nonce })
261 }
262 ControlType::PeerInfo => {
263 if buf.len() < 34 {
265 return Err(PimError::Protocol("PeerInfo too short".into()));
266 }
267 let mut x25519_pub = [0u8; 32];
268 x25519_pub.copy_from_slice(&buf[1..33]);
269 let name_len = buf[33] as usize;
270 let total = 34 + name_len;
271 if buf.len() < total {
272 return Err(PimError::Protocol(format!(
273 "PeerInfo truncated: need {total}, have {}",
274 buf.len()
275 )));
276 }
277 let friendly_name = match std::str::from_utf8(&buf[34..total]) {
278 Ok(s) => s.to_owned(),
279 Err(_) => {
280 return Err(PimError::Protocol(
281 "PeerInfo friendly_name not valid UTF-8".into(),
282 ))
283 }
284 };
285 buf.advance(total);
286 Ok(ControlFrame::PeerInfo {
287 x25519_pub,
288 friendly_name,
289 })
290 }
291 ControlType::PluginPayload => {
292 if buf.len() < 4 {
294 return Err(PimError::Protocol("PluginPayload too short".into()));
295 }
296 let kind_len = buf[1] as usize;
297 let body_len_off = 2 + kind_len;
298 if buf.len() < body_len_off + 2 {
299 return Err(PimError::Protocol(format!(
300 "PluginPayload header truncated: need {}, have {}",
301 body_len_off + 2,
302 buf.len()
303 )));
304 }
305 let kind = match std::str::from_utf8(&buf[2..body_len_off]) {
306 Ok(s) => s.to_owned(),
307 Err(_) => {
308 return Err(PimError::Protocol(
309 "PluginPayload kind not valid UTF-8".into(),
310 ))
311 }
312 };
313 let body_len = (&buf[body_len_off..body_len_off + 2]).get_u16() as usize;
314 let total = body_len_off + 2 + body_len;
315 if buf.len() < total {
316 return Err(PimError::Protocol(format!(
317 "PluginPayload truncated: need {total}, have {}",
318 buf.len()
319 )));
320 }
321 let body = Bytes::copy_from_slice(&buf[body_len_off + 2..total]);
322 buf.advance(total);
323 Ok(ControlFrame::PluginPayload { kind, body })
324 }
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests;