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 PM2 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}