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,
};
pub const PARTICIPANT_CAPABILITY_BIT: u32 = 1;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParticipantSession {
capability: ParticipantCapabilityState,
}
#[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,
}
}
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 {
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,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParticipantIngress {
NotParticipant,
Request(ClientRequest),
Rejected(ParticipantTransportRejected),
InvalidGenericFrame,
}
#[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)
}
}
}
pub fn encode_server_value(value: ServerValue) -> Result<Frame, CodecError> {
let participant = ParticipantFrame::ServerValue(value);
encode_server_frame(&participant)
}
pub fn encode_server_push(push: ServerPush) -> Result<Frame, CodecError> {
let participant = ParticipantFrame::ServerPush(push);
encode_server_frame(&participant)
}
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,
})
}