Files
fips/src/proto/routing/wire.rs
T
Johnathan Corgan 5d13090d8f proto: add a bounds-checked byte reader/writer and adopt it across the wire codecs
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.
2026-07-08 19:00:25 +00:00

311 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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,
})
}
}