fips-core 0.3.75

Reusable FIPS mesh, endpoint, transport, and protocol library
Documentation
use super::{SessionMessageType, decode_optional_coords, encode_coords, encode_empty_coords};
use crate::NodeAddr;
use crate::protocol::ProtocolError;
use crate::tree::TreeCoordinate;

/// Path MTU notification (msg_type 0x13).
///
/// Sent by a node that discovers a path MTU value (from transit router
/// feedback or ICMP Packet Too Big). Allows the remote endpoint to
/// adjust its sending MTU.
///
/// ## Wire Format (2 bytes body, after inner header stripped)
///
/// ```text
/// [0-1]   path_mtu: u16 LE
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathMtuNotification {
    /// Discovered path MTU in bytes.
    pub path_mtu: u16,
}

/// Body size for PathMtuNotification.
pub const PATH_MTU_NOTIFICATION_SIZE: usize = 2;

impl PathMtuNotification {
    /// Create a new path MTU notification.
    pub fn new(path_mtu: u16) -> Self {
        Self { path_mtu }
    }

    /// Encode to wire format (2 bytes body).
    pub fn encode(&self) -> Vec<u8> {
        self.path_mtu.to_le_bytes().to_vec()
    }

    /// Decode from body (after FSP inner header has been stripped).
    pub fn decode(body: &[u8]) -> Result<Self, ProtocolError> {
        if body.len() < PATH_MTU_NOTIFICATION_SIZE {
            return Err(ProtocolError::MessageTooShort {
                expected: PATH_MTU_NOTIFICATION_SIZE,
                got: body.len(),
            });
        }
        Ok(Self {
            path_mtu: u16::from_le_bytes([body[0], body[1]]),
        })
    }
}

// ============================================================================
// 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,
        })
    }
}