Skip to main content

fips_core/protocol/session/
control_messages.rs

1use super::{SessionMessageType, decode_optional_coords, encode_coords, encode_empty_coords};
2use crate::NodeAddr;
3use crate::protocol::ProtocolError;
4use crate::tree::TreeCoordinate;
5
6/// Path MTU notification (msg_type 0x13).
7///
8/// Sent by a node that discovers a path MTU value (from transit router
9/// feedback or ICMP Packet Too Big). Allows the remote endpoint to
10/// adjust its sending MTU.
11///
12/// ## Wire Format (2 bytes body, after inner header stripped)
13///
14/// ```text
15/// [0-1]   path_mtu: u16 LE
16/// ```
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct PathMtuNotification {
19    /// Discovered path MTU in bytes.
20    pub path_mtu: u16,
21}
22
23/// Body size for PathMtuNotification.
24pub const PATH_MTU_NOTIFICATION_SIZE: usize = 2;
25
26impl PathMtuNotification {
27    /// Create a new path MTU notification.
28    pub fn new(path_mtu: u16) -> Self {
29        Self { path_mtu }
30    }
31
32    /// Encode to wire format (2 bytes body).
33    pub fn encode(&self) -> Vec<u8> {
34        self.path_mtu.to_le_bytes().to_vec()
35    }
36
37    /// Decode from body (after FSP inner header has been stripped).
38    pub fn decode(body: &[u8]) -> Result<Self, ProtocolError> {
39        if body.len() < PATH_MTU_NOTIFICATION_SIZE {
40            return Err(ProtocolError::MessageTooShort {
41                expected: PATH_MTU_NOTIFICATION_SIZE,
42                got: body.len(),
43            });
44        }
45        Ok(Self {
46            path_mtu: u16::from_le_bytes([body[0], body[1]]),
47        })
48    }
49}
50
51// ============================================================================
52// Error Messages
53// ============================================================================
54
55/// Link-layer error signal indicating router cache miss.
56///
57/// Generated by a transit router when it cannot forward a SessionDatagram
58/// due to missing cached coordinates for the destination. Carried inside
59/// a new SessionDatagram addressed back to the original source
60/// (src_addr=reporter, dest_addr=original_source). Plaintext — not
61/// end-to-end encrypted, since the transit router has no session with
62/// the source.
63///
64/// ## Wire Format
65///
66/// | Offset | Field    | Size     | Description                        |
67/// |--------|----------|---------|------------------------------------|
68/// | 0      | msg_type | 1 byte  | 0x20                               |
69/// | 1      | flags    | 1 byte  | Reserved                           |
70/// | 2      | dest_addr| 16 bytes| The node_addr we couldn't route to |
71/// | 18     | reporter | 16 bytes| NodeAddr of reporting router       |
72///
73/// Payload: 34 bytes
74#[derive(Clone, Debug)]
75pub struct CoordsRequired {
76    /// Destination that couldn't be routed.
77    pub dest_addr: NodeAddr,
78    /// Router reporting the miss.
79    pub reporter: NodeAddr,
80}
81
82/// Wire size of CoordsRequired payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16).
83pub const COORDS_REQUIRED_SIZE: usize = 34;
84
85impl CoordsRequired {
86    /// Create a new CoordsRequired error.
87    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
88        Self {
89            dest_addr,
90            reporter,
91        }
92    }
93
94    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
95    ///
96    /// Error signals use phase=0x0 with U flag set.
97    pub fn encode(&self) -> Vec<u8> {
98        // Body: msg_type + flags(reserved) + dest_addr + reporter
99        let body_len = 1 + 1 + 16 + 16; // 34 bytes
100        let mut buf = Vec::with_capacity(4 + body_len);
101        // FSP prefix: version 0, phase 0x0, U flag set
102        buf.push(0x00); // version 0, phase 0x0
103        buf.push(0x04); // U flag
104        let payload_len = body_len as u16;
105        buf.extend_from_slice(&payload_len.to_le_bytes());
106        // msg_type byte (after prefix, before body)
107        buf.push(SessionMessageType::CoordsRequired.to_byte());
108        buf.push(0x00); // reserved flags
109        buf.extend_from_slice(self.dest_addr.as_bytes());
110        buf.extend_from_slice(self.reporter.as_bytes());
111        buf
112    }
113
114    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
115    pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
116        // flags(1) + dest_addr(16) + reporter(16) = 33
117        if payload.len() < 33 {
118            return Err(ProtocolError::MessageTooShort {
119                expected: 33,
120                got: payload.len(),
121            });
122        }
123        // payload[0] is flags (reserved, ignored)
124        let mut dest_bytes = [0u8; 16];
125        dest_bytes.copy_from_slice(&payload[1..17]);
126        let mut reporter_bytes = [0u8; 16];
127        reporter_bytes.copy_from_slice(&payload[17..33]);
128
129        Ok(Self {
130            dest_addr: NodeAddr::from_bytes(dest_bytes),
131            reporter: NodeAddr::from_bytes(reporter_bytes),
132        })
133    }
134}
135
136/// Error indicating routing failure (local minimum or unreachable).
137///
138/// Carried inside a SessionDatagram addressed back to the original source.
139/// The reporting router creates a new SessionDatagram with src_addr=reporter
140/// and dest_addr=original_source, so the `original_src` field from the old
141/// design is no longer needed — it's the SessionDatagram's dest_addr.
142///
143/// ## Wire Format
144///
145/// | Offset | Field             | Size     | Description                   |
146/// |--------|-------------------|----------|-------------------------------|
147/// | 0      | msg_type          | 1 byte   | 0x21                          |
148/// | 1      | flags             | 1 byte   | Reserved                      |
149/// | 2      | dest_addr         | 16 bytes | The unreachable node_addr     |
150/// | 18     | reporter          | 16 bytes | NodeAddr of reporting router   |
151/// | 34     | last_coords_count | 2 bytes  | u16 LE                        |
152/// | 36     | last_known_coords | 16 × n   | Stale coords that failed      |
153#[derive(Clone, Debug)]
154pub struct PathBroken {
155    /// Destination that couldn't be reached.
156    pub dest_addr: NodeAddr,
157    /// Node that detected the failure.
158    pub reporter: NodeAddr,
159    /// Optional: last known coordinates of destination.
160    pub last_known_coords: Option<TreeCoordinate>,
161}
162
163impl PathBroken {
164    /// Create a new PathBroken error.
165    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
166        Self {
167            dest_addr,
168            reporter,
169            last_known_coords: None,
170        }
171    }
172
173    /// Add last known coordinates.
174    pub fn with_last_coords(mut self, coords: TreeCoordinate) -> Self {
175        self.last_known_coords = Some(coords);
176        self
177    }
178
179    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
180    ///
181    /// Error signals use phase=0x0 with U flag set.
182    pub fn encode(&self) -> Vec<u8> {
183        // Build body first to compute length
184        let mut body = Vec::new();
185        body.push(SessionMessageType::PathBroken.to_byte());
186        body.push(0x00); // reserved flags
187        body.extend_from_slice(self.dest_addr.as_bytes());
188        body.extend_from_slice(self.reporter.as_bytes());
189        if let Some(ref coords) = self.last_known_coords {
190            encode_coords(coords, &mut body);
191        } else {
192            encode_empty_coords(&mut body);
193        }
194
195        // Prepend FSP prefix: version 0, phase 0x0, U flag set
196        let payload_len = body.len() as u16;
197        let mut buf = Vec::with_capacity(4 + body.len());
198        buf.push(0x00); // version 0, phase 0x0
199        buf.push(0x04); // U flag
200        buf.extend_from_slice(&payload_len.to_le_bytes());
201        buf.extend_from_slice(&body);
202        buf
203    }
204
205    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
206    pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
207        // flags(1) + dest_addr(16) + reporter(16) + coords_count(2) = 35 minimum
208        if payload.len() < 35 {
209            return Err(ProtocolError::MessageTooShort {
210                expected: 35,
211                got: payload.len(),
212            });
213        }
214        // payload[0] is flags (reserved, ignored)
215        let mut dest_bytes = [0u8; 16];
216        dest_bytes.copy_from_slice(&payload[1..17]);
217        let mut reporter_bytes = [0u8; 16];
218        reporter_bytes.copy_from_slice(&payload[17..33]);
219
220        let (last_known_coords, _consumed) = decode_optional_coords(&payload[33..])?;
221
222        Ok(Self {
223            dest_addr: NodeAddr::from_bytes(dest_bytes),
224            reporter: NodeAddr::from_bytes(reporter_bytes),
225            last_known_coords,
226        })
227    }
228}
229
230/// Error indicating a forwarded packet exceeded the next-hop transport MTU.
231///
232/// Generated by a transit router when dataplane FMP-link output fails with
233/// `TransportError::MtuExceeded`. The reporter includes the
234/// bottleneck MTU so the source can immediately reduce its sending MTU.
235///
236/// ## Wire Format
237///
238/// | Offset | Field     | Size     | Description                        |
239/// |--------|-----------|----------|------------------------------------|
240/// | 0      | msg_type  | 1 byte   | 0x22                               |
241/// | 1      | flags     | 1 byte   | Reserved                           |
242/// | 2      | dest_addr | 16 bytes | The destination we were forwarding |
243/// | 18     | reporter  | 16 bytes | NodeAddr of reporting router       |
244/// | 34     | mtu       | 2 bytes  | Bottleneck MTU (u16 LE)            |
245///
246/// Payload: 36 bytes
247#[derive(Clone, Debug)]
248pub struct MtuExceeded {
249    /// Destination that the oversized packet was heading to.
250    pub dest_addr: NodeAddr,
251    /// Router that detected the MTU violation.
252    pub reporter: NodeAddr,
253    /// Transport MTU at the bottleneck hop.
254    pub mtu: u16,
255}
256
257/// Wire size of MtuExceeded payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16) + mtu(2).
258pub const MTU_EXCEEDED_SIZE: usize = 36;
259
260impl MtuExceeded {
261    /// Create a new MtuExceeded error.
262    pub fn new(dest_addr: NodeAddr, reporter: NodeAddr, mtu: u16) -> Self {
263        Self {
264            dest_addr,
265            reporter,
266            mtu,
267        }
268    }
269
270    /// Encode as wire format (4-byte FSP prefix + msg_type + body).
271    ///
272    /// Error signals use phase=0x0 with U flag set.
273    pub fn encode(&self) -> Vec<u8> {
274        let body_len = MTU_EXCEEDED_SIZE; // 36 bytes
275        let mut buf = Vec::with_capacity(4 + body_len);
276        // FSP prefix: version 0, phase 0x0, U flag set
277        buf.push(0x00); // version 0, phase 0x0
278        buf.push(0x04); // U flag
279        let payload_len = body_len as u16;
280        buf.extend_from_slice(&payload_len.to_le_bytes());
281        // msg_type byte
282        buf.push(SessionMessageType::MtuExceeded.to_byte());
283        buf.push(0x00); // reserved flags
284        buf.extend_from_slice(self.dest_addr.as_bytes());
285        buf.extend_from_slice(self.reporter.as_bytes());
286        buf.extend_from_slice(&self.mtu.to_le_bytes());
287        buf
288    }
289
290    /// Decode from wire format (after FSP prefix and msg_type byte consumed).
291    pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
292        // flags(1) + dest_addr(16) + reporter(16) + mtu(2) = 35
293        if payload.len() < 35 {
294            return Err(ProtocolError::MessageTooShort {
295                expected: 35,
296                got: payload.len(),
297            });
298        }
299        // payload[0] is flags (reserved, ignored)
300        let mut dest_bytes = [0u8; 16];
301        dest_bytes.copy_from_slice(&payload[1..17]);
302        let mut reporter_bytes = [0u8; 16];
303        reporter_bytes.copy_from_slice(&payload[17..33]);
304        let mtu = u16::from_le_bytes([payload[33], payload[34]]);
305
306        Ok(Self {
307            dest_addr: NodeAddr::from_bytes(dest_bytes),
308            reporter: NodeAddr::from_bytes(reporter_bytes),
309            mtu,
310        })
311    }
312}