Merge refactor-sans-io: routing sans-IO on the next line

This commit is contained in:
Johnathan Corgan
2026-07-07 04:29:58 +00:00
19 changed files with 1848 additions and 894 deletions
+3 -4
View File
@@ -39,10 +39,9 @@ pub use negotiation::{
FMP_FEAT_WANTS_SR, NEGOTIATION_HEADER_SIZE, NegotiationPayload, NodeProfile, TlvEntry,
};
pub use session::{
COORDS_REQUIRED_SIZE, CoordsRequired, FspFlags, FspInnerFlags, MTU_EXCEEDED_SIZE, MtuExceeded,
PATH_MTU_NOTIFICATION_SIZE, PathBroken, PathMtuNotification, SESSION_RECEIVER_REPORT_SIZE,
SESSION_SENDER_REPORT_SIZE, SessionAck, SessionFlags, SessionMessageType, SessionMsg3,
SessionReceiverReport, SessionSenderReport, SessionSetup,
FspFlags, FspInnerFlags, PATH_MTU_NOTIFICATION_SIZE, PathMtuNotification,
SESSION_RECEIVER_REPORT_SIZE, SESSION_SENDER_REPORT_SIZE, SessionAck, SessionFlags,
SessionMessageType, SessionMsg3, SessionReceiverReport, SessionSenderReport, SessionSetup,
};
pub(crate) use session::{coords_wire_size, decode_optional_coords, encode_coords};
pub use tree::TreeAnnounce;
+1 -390
View File
@@ -182,7 +182,7 @@ pub(crate) fn decode_optional_coords(
}
/// Encode a count of zero (for empty/absent coordinate fields).
fn encode_empty_coords(buf: &mut Vec<u8>) {
pub(crate) fn encode_empty_coords(buf: &mut Vec<u8>) {
buf.extend_from_slice(&0u16.to_le_bytes());
}
@@ -858,269 +858,6 @@ impl PathMtuNotification {
}
}
// ============================================================================
// Error Messages
// ============================================================================
/// 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 buf = Vec::with_capacity(4 + body_len);
// FSP prefix: version 0, phase 0x0, U flag set
buf.push(0x00); // version 0, phase 0x0
buf.push(0x04); // U flag
let payload_len = body_len as u16;
buf.extend_from_slice(&payload_len.to_le_bytes());
// msg_type byte (after prefix, before body)
buf.push(SessionMessageType::CoordsRequired.to_byte());
buf.push(0x00); // reserved flags
buf.extend_from_slice(self.dest_addr.as_bytes());
buf.extend_from_slice(self.reporter.as_bytes());
buf
}
/// Decode from wire format (after FSP prefix and msg_type byte consumed).
pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
// flags(1) + dest_addr(16) + reporter(16) = 33
if payload.len() < 33 {
return Err(ProtocolError::MessageTooShort {
expected: 33,
got: payload.len(),
});
}
// payload[0] is flags (reserved, ignored)
let mut dest_bytes = [0u8; 16];
dest_bytes.copy_from_slice(&payload[1..17]);
let mut reporter_bytes = [0u8; 16];
reporter_bytes.copy_from_slice(&payload[17..33]);
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(SessionMessageType::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, ProtocolError> {
// flags(1) + dest_addr(16) + reporter(16) + coords_count(2) = 35 minimum
if payload.len() < 35 {
return Err(ProtocolError::MessageTooShort {
expected: 35,
got: payload.len(),
});
}
// payload[0] is flags (reserved, ignored)
let mut dest_bytes = [0u8; 16];
dest_bytes.copy_from_slice(&payload[1..17]);
let mut reporter_bytes = [0u8; 16];
reporter_bytes.copy_from_slice(&payload[17..33]);
let (last_known_coords, _consumed) = decode_optional_coords(&payload[33..])?;
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 buf = Vec::with_capacity(4 + body_len);
// FSP prefix: version 0, phase 0x0, U flag set
buf.push(0x00); // version 0, phase 0x0
buf.push(0x04); // U flag
let payload_len = body_len as u16;
buf.extend_from_slice(&payload_len.to_le_bytes());
// msg_type byte
buf.push(SessionMessageType::MtuExceeded.to_byte());
buf.push(0x00); // reserved flags
buf.extend_from_slice(self.dest_addr.as_bytes());
buf.extend_from_slice(self.reporter.as_bytes());
buf.extend_from_slice(&self.mtu.to_le_bytes());
buf
}
/// Decode from wire format (after FSP prefix and msg_type byte consumed).
pub fn decode(payload: &[u8]) -> Result<Self, ProtocolError> {
// flags(1) + dest_addr(16) + reporter(16) + mtu(2) = 35
if payload.len() < 35 {
return Err(ProtocolError::MessageTooShort {
expected: 35,
got: payload.len(),
});
}
// payload[0] is flags (reserved, ignored)
let mut dest_bytes = [0u8; 16];
dest_bytes.copy_from_slice(&payload[1..17]);
let mut reporter_bytes = [0u8; 16];
reporter_bytes.copy_from_slice(&payload[17..33]);
let mtu = u16::from_le_bytes([payload[33], payload[34]]);
Ok(Self {
dest_addr: NodeAddr::from_bytes(dest_bytes),
reporter: NodeAddr::from_bytes(reporter_bytes),
mtu,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1198,28 +935,6 @@ mod tests {
assert!(!setup.flags.bidirectional);
}
// ===== CoordsRequired Tests =====
#[test]
fn test_coords_required() {
let err = CoordsRequired::new(make_node_addr(1), make_node_addr(2));
assert_eq!(err.dest_addr, make_node_addr(1));
assert_eq!(err.reporter, make_node_addr(2));
}
// ===== PathBroken Tests =====
#[test]
fn test_path_broken() {
let err = PathBroken::new(make_node_addr(2), make_node_addr(3))
.with_last_coords(make_coords(&[2, 0]));
assert_eq!(err.dest_addr, make_node_addr(2));
assert_eq!(err.reporter, make_node_addr(3));
assert!(err.last_known_coords.is_some());
}
// ===== Encode/Decode Roundtrip Tests =====
#[test]
@@ -1275,55 +990,6 @@ mod tests {
assert_eq!(decoded.handshake_payload, handshake);
}
#[test]
fn test_coords_required_encode_decode() {
let err = CoordsRequired::new(make_node_addr(0xAA), make_node_addr(0xBB));
let encoded = err.encode();
// 4 prefix + 1 msg_type + 1 flags + 16 dest + 16 reporter = 38
assert_eq!(encoded.len(), 4 + COORDS_REQUIRED_SIZE);
// Check FSP prefix: phase 0x0, U flag
assert_eq!(encoded[0], 0x00);
assert_eq!(encoded[1], 0x04); // U flag
// msg_type after prefix
assert_eq!(encoded[4], 0x20);
// decode after prefix + msg_type consumed
let decoded = CoordsRequired::decode(&encoded[5..]).unwrap();
assert_eq!(decoded.dest_addr, err.dest_addr);
assert_eq!(decoded.reporter, err.reporter);
}
#[test]
fn test_path_broken_encode_decode_no_coords() {
let err = PathBroken::new(make_node_addr(0xCC), make_node_addr(0xDD));
let encoded = err.encode();
// Check FSP prefix
assert_eq!(encoded[0], 0x00);
assert_eq!(encoded[1], 0x04); // U flag
assert_eq!(encoded[4], 0x21); // msg_type
let decoded = PathBroken::decode(&encoded[5..]).unwrap();
assert_eq!(decoded.dest_addr, err.dest_addr);
assert_eq!(decoded.reporter, err.reporter);
assert!(decoded.last_known_coords.is_none());
}
#[test]
fn test_path_broken_encode_decode_with_coords() {
let coords = make_coords(&[0xCC, 0xDD, 0xEE]);
let err = PathBroken::new(make_node_addr(0x11), make_node_addr(0x22))
.with_last_coords(coords.clone());
let encoded = err.encode();
let decoded = PathBroken::decode(&encoded[5..]).unwrap();
assert_eq!(decoded.dest_addr, err.dest_addr);
assert_eq!(decoded.reporter, err.reporter);
assert_eq!(decoded.last_known_coords.unwrap(), coords);
}
#[test]
fn test_session_setup_decode_too_short() {
assert!(SessionSetup::decode(&[]).is_err());
@@ -1334,18 +1000,6 @@ mod tests {
assert!(SessionAck::decode(&[]).is_err());
}
#[test]
fn test_coords_required_decode_too_short() {
assert!(CoordsRequired::decode(&[]).is_err());
assert!(CoordsRequired::decode(&[0x00; 10]).is_err());
}
#[test]
fn test_path_broken_decode_too_short() {
assert!(PathBroken::decode(&[]).is_err());
assert!(PathBroken::decode(&[0x00; 20]).is_err());
}
#[test]
fn test_session_setup_deep_coords() {
// Depth-10 coordinate (11 entries: self + 10 ancestors)
@@ -1562,49 +1216,6 @@ mod tests {
// ===== MtuExceeded Tests =====
#[test]
fn test_mtu_exceeded_encode_size() {
let err = MtuExceeded::new(make_node_addr(0xAA), make_node_addr(0xBB), 1400);
let encoded = err.encode();
// 4 prefix + 36 body = 40
assert_eq!(encoded.len(), 4 + MTU_EXCEEDED_SIZE);
}
#[test]
fn test_mtu_exceeded_encode_decode() {
let err = MtuExceeded::new(make_node_addr(0xAA), make_node_addr(0xBB), 1400);
let encoded = err.encode();
// Check FSP prefix: phase 0x0, U flag
assert_eq!(encoded[0], 0x00);
assert_eq!(encoded[1], 0x04); // U flag
// msg_type after prefix
assert_eq!(encoded[4], 0x22);
// decode after prefix + msg_type consumed
let decoded = MtuExceeded::decode(&encoded[5..]).unwrap();
assert_eq!(decoded.dest_addr, err.dest_addr);
assert_eq!(decoded.reporter, err.reporter);
assert_eq!(decoded.mtu, 1400);
}
#[test]
fn test_mtu_exceeded_decode_too_short() {
assert!(MtuExceeded::decode(&[]).is_err());
assert!(MtuExceeded::decode(&[0x00; 20]).is_err());
assert!(MtuExceeded::decode(&[0x00; 34]).is_err()); // exactly 1 byte short
}
#[test]
fn test_mtu_exceeded_boundary_mtu_values() {
for mtu in [0u16, 1280, 1500, u16::MAX] {
let err = MtuExceeded::new(make_node_addr(1), make_node_addr(2), mtu);
let encoded = err.encode();
let decoded = MtuExceeded::decode(&encoded[5..]).unwrap();
assert_eq!(decoded.mtu, mtu);
}
}
#[test]
fn test_mtu_exceeded_message_type_value() {
assert_eq!(SessionMessageType::MtuExceeded.to_byte(), 0x22);