liminal-server 0.3.0

Standalone server for the liminal messaging bus
Documentation
use liminal::protocol::Frame;
use liminal_protocol::wire::{
    AuthenticationState, ClientRequest, CodecError, FRAME_MAX, InboundGateContext,
    InboundGateError, NegotiatedParticipantCapability, PARTICIPANT_FRAME_TYPE,
    ParticipantCapabilityState, ParticipantFrame, ParticipantTransportRejected, ReceiverDirection,
    ServerPush, ServerValue, TransportRejectionReason, ValidatedFrameLimit, encode, encoded_len,
    gate_inbound,
};

/// Reserved connection-capability bit for `participant-v1`.
///
/// Production advertises this bit only after a complete semantic and durable
/// participant service is installed. The transport gate can still decode and
/// reject participant frames while the bit remains unadvertised.
pub const PARTICIPANT_CAPABILITY_BIT: u32 = 1;

/// Participant capability negotiated on one authenticated connection.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParticipantSession {
    capability: ParticipantCapabilityState,
}

/// Rejects an oversized participant frame from its generic header before the
/// connection buffers the declared body.
///
/// The shared gate owns both the negotiated/pre-capability limit and the exact
/// rejection payload. An incomplete frame within the limit returns `None` and
/// remains in the ordinary incremental decoder.
#[must_use]
pub fn preflight_generic_bytes(
    input: &[u8],
    authenticated: bool,
    session: ParticipantSession,
) -> Option<ParticipantTransportRejected> {
    if input.first().copied() != Some(PARTICIPANT_FRAME_TYPE) || input.len() < 10 {
        return None;
    }
    match gate_inbound(input, session.gate_context(authenticated)) {
        Err(InboundGateError::ParticipantRejected(rejection))
            if matches!(
                rejection.reason,
                TransportRejectionReason::FrameTooLarge { .. }
            ) =>
        {
            Some(rejection)
        }
        Ok(_)
        | Err(
            InboundGateError::NotParticipantFrame { .. } | InboundGateError::ParticipantRejected(_),
        ) => None,
    }
}

/// Normalizes the operator's raw configured `WF` to the generic frame ceiling.
///
/// # Errors
///
/// Returns [`CodecError::InvalidFrameLimit`] when the configured value is below
/// the shared protocol's minimum complete-frame size. Values above the generic
/// `u32` payload ceiling are clamped to [`FRAME_MAX`] exactly as the participant
/// contract defines.
pub fn normalize_configured_frame_limit(
    configured_wf: u64,
) -> Result<ValidatedFrameLimit, CodecError> {
    ValidatedFrameLimit::new(configured_wf.min(FRAME_MAX))
}

impl Default for ParticipantSession {
    fn default() -> Self {
        Self {
            capability: ParticipantCapabilityState::Missing,
        }
    }
}

impl ParticipantSession {
    /// Stores the server's supported v1 capability and normalized configured
    /// complete-frame limit after a successful handshake.
    pub const fn negotiate_v1(&mut self, limit: ValidatedFrameLimit) {
        self.capability =
            ParticipantCapabilityState::Negotiated(NegotiatedParticipantCapability::v1(limit));
    }

    const fn gate_context(self, authenticated: bool) -> InboundGateContext {
        InboundGateContext {
            receiver: ReceiverDirection::Server,
            authentication: if authenticated {
                AuthenticationState::Authenticated
            } else {
                AuthenticationState::Unauthenticated
            },
            participant_capability: self.capability,
        }
    }
}

/// Result of classifying one preserved generic frame through the shared gate.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParticipantIngress {
    /// The generic frame is not assigned to participant traffic.
    NotParticipant,
    /// A structurally valid and authorized client request.
    Request(ClientRequest),
    /// Exact pre-semantic rejection selected by `liminal-protocol`.
    Rejected(ParticipantTransportRejected),
    /// A locally fabricated generic frame could not be represented canonically.
    InvalidGenericFrame,
}

/// Applies the shared participant gate to one generic frame preserved by
/// `liminal`'s forward-compatible decoder.
#[must_use]
pub fn gate_generic_frame(
    frame: &Frame,
    authenticated: bool,
    session: ParticipantSession,
) -> ParticipantIngress {
    let Frame::Unknown {
        type_id,
        flags,
        stream_id,
        payload,
    } = frame
    else {
        return ParticipantIngress::NotParticipant;
    };
    if *type_id != PARTICIPANT_FRAME_TYPE {
        return ParticipantIngress::NotParticipant;
    }
    let Ok(payload_length) = u32::try_from(payload.len()) else {
        return ParticipantIngress::InvalidGenericFrame;
    };
    let Some(capacity) = 10_usize.checked_add(payload.len()) else {
        return ParticipantIngress::InvalidGenericFrame;
    };
    let mut complete = Vec::with_capacity(capacity);
    complete.push(*type_id);
    complete.push(*flags);
    complete.extend_from_slice(&stream_id.to_be_bytes());
    complete.extend_from_slice(&payload_length.to_be_bytes());
    complete.extend_from_slice(payload);

    match gate_inbound(&complete, session.gate_context(authenticated)) {
        Ok(ParticipantFrame::ClientRequest(request)) => ParticipantIngress::Request(request),
        Ok(ParticipantFrame::ServerValue(_) | ParticipantFrame::ServerPush(_)) => {
            ParticipantIngress::InvalidGenericFrame
        }
        Err(InboundGateError::NotParticipantFrame { .. }) => ParticipantIngress::NotParticipant,
        Err(InboundGateError::ParticipantRejected(rejection)) => {
            ParticipantIngress::Rejected(rejection)
        }
    }
}

/// Encodes one crate-produced semantic value into the existing generic frame.
///
/// # Errors
///
/// Returns the shared codec error if the typed value cannot be encoded.
pub fn encode_server_value(value: ServerValue) -> Result<Frame, CodecError> {
    let participant = ParticipantFrame::ServerValue(value);
    encode_server_frame(&participant)
}

/// Encodes one crate-produced server push into the participant generic frame on
/// the protocol-owned generic stream zero.
///
/// # Errors
///
/// Returns the shared codec error if the typed push cannot be encoded.
pub fn encode_server_push(push: ServerPush) -> Result<Frame, CodecError> {
    let participant = ParticipantFrame::ServerPush(push);
    encode_server_frame(&participant)
}

/// Applies the one canonical participant codec to either server-to-client arm,
/// then preserves its already encoded participant payload in the generic frame.
fn encode_server_frame(participant: &ParticipantFrame) -> Result<Frame, CodecError> {
    let needed = encoded_len(participant)?;
    let mut complete = vec![0_u8; needed];
    let written = encode(participant, &mut complete)?;
    complete.truncate(written);

    let payload = complete
        .get(10..)
        .ok_or(CodecError::LengthOverflow)?
        .to_vec();
    Ok(Frame::Unknown {
        type_id: PARTICIPANT_FRAME_TYPE,
        flags: 0,
        stream_id: 0,
        payload,
    })
}