mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Introduce a shared proto/codec module with a cursor Reader (short reads fail with
MessageTooShort { expected: position + needed, got: total }) and an append Writer,
and adopt them across the seven subsystem wire codecs, replacing the repetitive
manual slicing, try_into, and from_le_bytes extraction. Each existing length check
maps to the reader (an up-front minimum becomes require at position zero, so the
per-field expected values, including the tree-announce expected 99, are reproduced
exactly); the bloom exact-length check stays explicit since it also rejects
over-long payloads. Encoded bytes and decode decisions are unchanged.
311 lines
12 KiB
Rust
311 lines
12 KiB
Rust
//! Routing error-signal wire PDUs.
|
||
//!
|
||
//! Plaintext link-layer error signals emitted by a transit router that
|
||
//! cannot forward a `SessionDatagram`: `CoordsRequired` (coordinate cache
|
||
//! miss), `PathBroken` (routing failure / local minimum), and `MtuExceeded`
|
||
//! (next-hop transport MTU too small). Their `0x20`–`0x2F` discriminants form
|
||
//! the routing-owned [`RoutingSignalType`] registry, split off from the FSP
|
||
//! `SessionMessageType` (which keeps the encrypted-inner `0x10`–`0x1F` range).
|
||
//! The shared address-only coordinate helpers are imported downward from
|
||
//! `crate::proto::stp`, and [`Error`] from `crate::proto` (both
|
||
//! allowed `proto -> proto` dependencies).
|
||
|
||
use crate::NodeAddr;
|
||
use crate::proto::Error;
|
||
use crate::proto::codec::{Reader, Writer};
|
||
use crate::proto::stp::{
|
||
TreeCoordinate, decode_optional_coords, encode_coords, encode_empty_coords,
|
||
};
|
||
use core::fmt;
|
||
|
||
/// Plaintext link-layer routing error-signal message types (`0x20`–`0x2F`).
|
||
///
|
||
/// Emitted by transit routers that cannot forward a `SessionDatagram` and have
|
||
/// no end-to-end session with the source, so they are carried on the FSP
|
||
/// cleartext (U-flag) path rather than the AEAD-encrypted inner path. Split
|
||
/// from the FSP `SessionMessageType` (encrypted-inner `0x10`–`0x1F`): the two
|
||
/// decode sites are partitioned by byte-range and crypto context, so neither
|
||
/// registry references the other's range.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||
#[repr(u8)]
|
||
pub enum RoutingSignalType {
|
||
/// Router cache miss — needs coordinates.
|
||
CoordsRequired = 0x20,
|
||
/// Routing failure — local minimum or unreachable.
|
||
PathBroken = 0x21,
|
||
/// MTU exceeded — forwarded packet too large for next-hop transport.
|
||
MtuExceeded = 0x22,
|
||
}
|
||
|
||
impl RoutingSignalType {
|
||
/// Try to convert from a byte.
|
||
pub fn from_byte(b: u8) -> Option<Self> {
|
||
match b {
|
||
0x20 => Some(RoutingSignalType::CoordsRequired),
|
||
0x21 => Some(RoutingSignalType::PathBroken),
|
||
0x22 => Some(RoutingSignalType::MtuExceeded),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Convert to a byte.
|
||
pub fn to_byte(self) -> u8 {
|
||
self as u8
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for RoutingSignalType {
|
||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
let name = match self {
|
||
RoutingSignalType::CoordsRequired => "CoordsRequired",
|
||
RoutingSignalType::PathBroken => "PathBroken",
|
||
RoutingSignalType::MtuExceeded => "MtuExceeded",
|
||
};
|
||
write!(f, "{}", name)
|
||
}
|
||
}
|
||
|
||
/// Link-layer error signal indicating router cache miss.
|
||
///
|
||
/// Generated by a transit router when it cannot forward a SessionDatagram
|
||
/// due to missing cached coordinates for the destination. Carried inside
|
||
/// a new SessionDatagram addressed back to the original source
|
||
/// (src_addr=reporter, dest_addr=original_source). Plaintext — not
|
||
/// end-to-end encrypted, since the transit router has no session with
|
||
/// the source.
|
||
///
|
||
/// ## Wire Format
|
||
///
|
||
/// | Offset | Field | Size | Description |
|
||
/// |--------|----------|---------|------------------------------------|
|
||
/// | 0 | msg_type | 1 byte | 0x20 |
|
||
/// | 1 | flags | 1 byte | Reserved |
|
||
/// | 2 | dest_addr| 16 bytes| The node_addr we couldn't route to |
|
||
/// | 18 | reporter | 16 bytes| NodeAddr of reporting router |
|
||
///
|
||
/// Payload: 34 bytes
|
||
#[derive(Clone, Debug)]
|
||
pub struct CoordsRequired {
|
||
/// Destination that couldn't be routed.
|
||
pub dest_addr: NodeAddr,
|
||
/// Router reporting the miss.
|
||
pub reporter: NodeAddr,
|
||
}
|
||
|
||
/// Wire size of CoordsRequired payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16).
|
||
pub const COORDS_REQUIRED_SIZE: usize = 34;
|
||
|
||
impl CoordsRequired {
|
||
/// Create a new CoordsRequired error.
|
||
pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
|
||
Self {
|
||
dest_addr,
|
||
reporter,
|
||
}
|
||
}
|
||
|
||
/// Encode as wire format (4-byte FSP prefix + msg_type + body).
|
||
///
|
||
/// Error signals use phase=0x0 with U flag set.
|
||
pub fn encode(&self) -> Vec<u8> {
|
||
// Body: msg_type + flags(reserved) + dest_addr + reporter
|
||
let body_len = 1 + 1 + 16 + 16; // 34 bytes
|
||
let mut w = Writer::with_capacity(4 + body_len);
|
||
// FSP prefix: version 0, phase 0x0, U flag set
|
||
w.write_u8(0x00); // version 0, phase 0x0
|
||
w.write_u8(0x04); // U flag
|
||
let payload_len = body_len as u16;
|
||
w.write_u16_le(payload_len);
|
||
// msg_type byte (after prefix, before body)
|
||
w.write_u8(RoutingSignalType::CoordsRequired.to_byte());
|
||
w.write_u8(0x00); // reserved flags
|
||
w.write_bytes(self.dest_addr.as_bytes());
|
||
w.write_bytes(self.reporter.as_bytes());
|
||
w.into_vec()
|
||
}
|
||
|
||
/// Decode from wire format (after FSP prefix and msg_type byte consumed).
|
||
pub fn decode(payload: &[u8]) -> Result<Self, Error> {
|
||
// flags(1) + dest_addr(16) + reporter(16) = 33
|
||
let mut reader = Reader::new(payload);
|
||
reader.require(33)?;
|
||
// payload[0] is flags (reserved, ignored)
|
||
reader.advance(1);
|
||
let dest_bytes = reader.read_array::<16>()?;
|
||
let reporter_bytes = reader.read_array::<16>()?;
|
||
|
||
Ok(Self {
|
||
dest_addr: NodeAddr::from_bytes(dest_bytes),
|
||
reporter: NodeAddr::from_bytes(reporter_bytes),
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Error indicating routing failure (local minimum or unreachable).
|
||
///
|
||
/// Carried inside a SessionDatagram addressed back to the original source.
|
||
/// The reporting router creates a new SessionDatagram with src_addr=reporter
|
||
/// and dest_addr=original_source, so the `original_src` field from the old
|
||
/// design is no longer needed — it's the SessionDatagram's dest_addr.
|
||
///
|
||
/// ## Wire Format
|
||
///
|
||
/// | Offset | Field | Size | Description |
|
||
/// |--------|-------------------|----------|-------------------------------|
|
||
/// | 0 | msg_type | 1 byte | 0x21 |
|
||
/// | 1 | flags | 1 byte | Reserved |
|
||
/// | 2 | dest_addr | 16 bytes | The unreachable node_addr |
|
||
/// | 18 | reporter | 16 bytes | NodeAddr of reporting router |
|
||
/// | 34 | last_coords_count | 2 bytes | u16 LE |
|
||
/// | 36 | last_known_coords | 16 × n | Stale coords that failed |
|
||
#[derive(Clone, Debug)]
|
||
pub struct PathBroken {
|
||
/// Destination that couldn't be reached.
|
||
pub dest_addr: NodeAddr,
|
||
/// Node that detected the failure.
|
||
pub reporter: NodeAddr,
|
||
/// Optional: last known coordinates of destination.
|
||
pub last_known_coords: Option<TreeCoordinate>,
|
||
}
|
||
|
||
impl PathBroken {
|
||
/// Create a new PathBroken error.
|
||
pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
|
||
Self {
|
||
dest_addr,
|
||
reporter,
|
||
last_known_coords: None,
|
||
}
|
||
}
|
||
|
||
/// Add last known coordinates.
|
||
pub fn with_last_coords(mut self, coords: TreeCoordinate) -> Self {
|
||
self.last_known_coords = Some(coords);
|
||
self
|
||
}
|
||
|
||
/// Encode as wire format (4-byte FSP prefix + msg_type + body).
|
||
///
|
||
/// Error signals use phase=0x0 with U flag set.
|
||
pub fn encode(&self) -> Vec<u8> {
|
||
// Build body first to compute length
|
||
let mut body = Vec::new();
|
||
body.push(RoutingSignalType::PathBroken.to_byte());
|
||
body.push(0x00); // reserved flags
|
||
body.extend_from_slice(self.dest_addr.as_bytes());
|
||
body.extend_from_slice(self.reporter.as_bytes());
|
||
if let Some(ref coords) = self.last_known_coords {
|
||
encode_coords(coords, &mut body);
|
||
} else {
|
||
encode_empty_coords(&mut body);
|
||
}
|
||
|
||
// Prepend FSP prefix: version 0, phase 0x0, U flag set
|
||
let payload_len = body.len() as u16;
|
||
let mut buf = Vec::with_capacity(4 + body.len());
|
||
buf.push(0x00); // version 0, phase 0x0
|
||
buf.push(0x04); // U flag
|
||
buf.extend_from_slice(&payload_len.to_le_bytes());
|
||
buf.extend_from_slice(&body);
|
||
buf
|
||
}
|
||
|
||
/// Decode from wire format (after FSP prefix and msg_type byte consumed).
|
||
pub fn decode(payload: &[u8]) -> Result<Self, Error> {
|
||
// flags(1) + dest_addr(16) + reporter(16) + coords_count(2) = 35 minimum
|
||
let mut reader = Reader::new(payload);
|
||
reader.require(35)?;
|
||
// payload[0] is flags (reserved, ignored)
|
||
reader.advance(1);
|
||
let dest_bytes = reader.read_array::<16>()?;
|
||
let reporter_bytes = reader.read_array::<16>()?;
|
||
|
||
let (last_known_coords, _consumed) = decode_optional_coords(reader.rest())?;
|
||
|
||
Ok(Self {
|
||
dest_addr: NodeAddr::from_bytes(dest_bytes),
|
||
reporter: NodeAddr::from_bytes(reporter_bytes),
|
||
last_known_coords,
|
||
})
|
||
}
|
||
}
|
||
|
||
/// Error indicating a forwarded packet exceeded the next-hop transport MTU.
|
||
///
|
||
/// Generated by a transit router when `send_encrypted_link_message()`
|
||
/// fails with `TransportError::MtuExceeded`. The reporter includes the
|
||
/// bottleneck MTU so the source can immediately reduce its sending MTU.
|
||
///
|
||
/// ## Wire Format
|
||
///
|
||
/// | Offset | Field | Size | Description |
|
||
/// |--------|-----------|----------|------------------------------------|
|
||
/// | 0 | msg_type | 1 byte | 0x22 |
|
||
/// | 1 | flags | 1 byte | Reserved |
|
||
/// | 2 | dest_addr | 16 bytes | The destination we were forwarding |
|
||
/// | 18 | reporter | 16 bytes | NodeAddr of reporting router |
|
||
/// | 34 | mtu | 2 bytes | Bottleneck MTU (u16 LE) |
|
||
///
|
||
/// Payload: 36 bytes
|
||
#[derive(Clone, Debug)]
|
||
pub struct MtuExceeded {
|
||
/// Destination that the oversized packet was heading to.
|
||
pub dest_addr: NodeAddr,
|
||
/// Router that detected the MTU violation.
|
||
pub reporter: NodeAddr,
|
||
/// Transport MTU at the bottleneck hop.
|
||
pub mtu: u16,
|
||
}
|
||
|
||
/// Wire size of MtuExceeded payload: msg_type(1) + flags(1) + dest_addr(16) + reporter(16) + mtu(2).
|
||
pub const MTU_EXCEEDED_SIZE: usize = 36;
|
||
|
||
impl MtuExceeded {
|
||
/// Create a new MtuExceeded error.
|
||
pub fn new(dest_addr: NodeAddr, reporter: NodeAddr, mtu: u16) -> Self {
|
||
Self {
|
||
dest_addr,
|
||
reporter,
|
||
mtu,
|
||
}
|
||
}
|
||
|
||
/// Encode as wire format (4-byte FSP prefix + msg_type + body).
|
||
///
|
||
/// Error signals use phase=0x0 with U flag set.
|
||
pub fn encode(&self) -> Vec<u8> {
|
||
let body_len = MTU_EXCEEDED_SIZE; // 36 bytes
|
||
let mut w = Writer::with_capacity(4 + body_len);
|
||
// FSP prefix: version 0, phase 0x0, U flag set
|
||
w.write_u8(0x00); // version 0, phase 0x0
|
||
w.write_u8(0x04); // U flag
|
||
let payload_len = body_len as u16;
|
||
w.write_u16_le(payload_len);
|
||
// msg_type byte
|
||
w.write_u8(RoutingSignalType::MtuExceeded.to_byte());
|
||
w.write_u8(0x00); // reserved flags
|
||
w.write_bytes(self.dest_addr.as_bytes());
|
||
w.write_bytes(self.reporter.as_bytes());
|
||
w.write_u16_le(self.mtu);
|
||
w.into_vec()
|
||
}
|
||
|
||
/// Decode from wire format (after FSP prefix and msg_type byte consumed).
|
||
pub fn decode(payload: &[u8]) -> Result<Self, Error> {
|
||
// flags(1) + dest_addr(16) + reporter(16) + mtu(2) = 35
|
||
let mut reader = Reader::new(payload);
|
||
reader.require(35)?;
|
||
// payload[0] is flags (reserved, ignored)
|
||
reader.advance(1);
|
||
let dest_bytes = reader.read_array::<16>()?;
|
||
let reporter_bytes = reader.read_array::<16>()?;
|
||
let mtu = reader.read_u16_le()?;
|
||
|
||
Ok(Self {
|
||
dest_addr: NodeAddr::from_bytes(dest_bytes),
|
||
reporter: NodeAddr::from_bytes(reporter_bytes),
|
||
mtu,
|
||
})
|
||
}
|
||
}
|