liminal-server 0.24.9

Standalone server for the liminal messaging bus
Documentation
//! The inbound bound that holds before a connection is admitted.
//!
//! Until a `Connect` has been accepted, the only frame a connection can
//! legitimately need is that `Connect`. Every other frame either tears an
//! authenticated server's connection down or is a pre-handshake courtesy
//! (`Ping`, `Disconnect`) a few bytes long. Buffering an unadmitted peer's
//! message up to the generic `u32` frame ceiling would let any socket that
//! completed a TCP or WebSocket handshake make the server hold gigabytes
//! before a single credential is checked. Both transports therefore refuse,
//! by name and before buffering, any inbound frame or reassembled message
//! larger than [`PRE_ADMISSION_FRAME_BOUND`] until the `Connect` succeeds; the
//! negotiated bound applies from then on exactly as before.

use liminal::protocol::{Frame, ProtocolError, ProtocolVersion};
use liminal_protocol::wire::GENERIC_HEADER_LEN;

/// Bytes a complete `Connect` frame needs besides its opaque auth token: the
/// generic header, the minimum and maximum protocol versions, and the token's
/// `u32` length prefix. Derived from the codec's own field widths so it moves
/// with them.
pub const CONNECT_FIXED_BYTES: usize =
    GENERIC_HEADER_LEN + 2 * ProtocolVersion::WIRE_LEN + core::mem::size_of::<u32>();

/// Largest complete inbound frame (or reassembled WebSocket message) accepted
/// from a connection whose `Connect` has not yet succeeded.
///
/// A `Connect` is [`CONNECT_FIXED_BYTES`] plus its token. The token is either
/// the operator's bearer token, held to [`MAX_CONNECT_AUTH_TOKEN_BYTES`] at
/// configuration load, or a registry pass: 134 fixed bytes plus its
/// participant id, its live label, and eight bytes per scoped conversation. A
/// pass naming several thousand conversations fits, which is two orders of
/// magnitude beyond any pass minted today; the bound is still four orders of
/// magnitude below the generic frame ceiling it replaces before admission.
pub const PRE_ADMISSION_FRAME_BOUND: usize = 64 * 1024;

/// Largest auth token a `Connect` can carry and still fit
/// [`PRE_ADMISSION_FRAME_BOUND`]. A configured bearer token longer than this
/// could never be presented, so configuration validation refuses it.
pub const MAX_CONNECT_AUTH_TOKEN_BYTES: usize = PRE_ADMISSION_FRAME_BOUND - CONNECT_FIXED_BYTES;

/// The name every pre-admission oversize refusal carries, on every
/// transport: the TCP `ConnectError` message and the WebSocket close reason
/// both begin with it, so a client reads one name whichever door it used.
pub const REFUSAL_NAME: &str = "FrameTooLarge before admission";

/// An inbound frame or message refused because it exceeded
/// [`PRE_ADMISSION_FRAME_BOUND`] before the connection was admitted.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PreAdmissionOversize {
    /// The size the peer declared or the reassembly would have reached,
    /// saturated at `usize::MAX` on a target too narrow to hold it.
    pub attempted: usize,
    /// The bound in force when it was refused.
    pub bound: usize,
}

impl std::fmt::Display for PreAdmissionOversize {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            formatter,
            "{REFUSAL_NAME}: {} bytes refused, bound {}",
            self.attempted, self.bound
        )
    }
}

/// The byte-stream transport's answer to a refusal: a `ConnectError` whose
/// message is the refusal by name.
pub fn connect_error_frame(refusal: PreAdmissionOversize) -> Frame {
    let error = ProtocolError::CodecError {
        message: Some(refusal.to_string()),
    };
    Frame::ConnectError {
        flags: 0,
        reason_code: error.reason_code(),
        message: error.message().map(str::to_owned),
    }
}

/// Checks the complete-frame size a buffered generic header declares against
/// the pre-admission bound.
///
/// Returns `Ok` once `admitted`, while fewer than a header's bytes are
/// buffered, or when the declared frame fits. The check reads only the
/// header's length field, so it refuses before the declared body is buffered.
///
/// # Errors
/// Returns [`PreAdmissionOversize`] when an unadmitted connection's buffered
/// header declares a complete frame larger than [`PRE_ADMISSION_FRAME_BOUND`].
pub fn check_declared_frame(buffer: &[u8], admitted: bool) -> Result<(), PreAdmissionOversize> {
    if admitted {
        return Ok(());
    }
    let Some(length_field) = buffer.get(GENERIC_HEADER_LEN - 4..GENERIC_HEADER_LEN) else {
        return Ok(());
    };
    let mut declared = [0_u8; 4];
    declared.copy_from_slice(length_field);
    let attempted = usize::try_from(u32::from_be_bytes(declared)).map_or(usize::MAX, |payload| {
        payload.saturating_add(GENERIC_HEADER_LEN)
    });
    if attempted > PRE_ADMISSION_FRAME_BOUND {
        return Err(PreAdmissionOversize {
            attempted,
            bound: PRE_ADMISSION_FRAME_BOUND,
        });
    }
    Ok(())
}