use std::collections::BTreeMap;
use liminal::protocol::{
Frame, MessageEnvelope, PUBLISH_ACK_POSITION_VERSION, PUBLISH_DELIVERED_FLAG, ProtocolError,
ProtocolVersion, SchemaId as ProtocolSchemaId, WorkerRegisterOutcome, WorkerRegistration,
negotiate_version,
};
use liminal_protocol::reason_code::PUBLISH_UNRESOLVED_CODE;
use liminal_protocol::wire::PARTICIPANT_FRAME_TYPE;
use super::link_route::{link_message_frame_response, link_open_frame_response};
use super::services::{
ChannelOperation, ConnectionServices, access_to_server_error, reserved_namespace_refusal,
};
use super::state::{ConnectionProcessState, FrameAction};
use super::supervisor::{ConnectionRuntime, PushBinding, PushReplyDisposition};
use super::worker_route::Request;
use crate::ServerError;
use crate::auth_lys::routes::{RouteClassification, classify_participant_route};
use crate::auth_lys::{
DeliberateDecision, HotAction, RefusalCause, SubjectBinding, SubjectBindingError,
};
use crate::auth_membership::{ConnectionMembership, MembershipAction};
use crate::auth_pass::{PassVerifier, VerifiedHolder, VerifiedPass};
use crate::auth_revocation::RevocationRefusal;
use crate::config::types::ServiceProfile;
use crate::console::FlowRole;
use crate::server::participant::{
ConnectionFateClass, PARTICIPANT_CAPABILITY_BIT, ParticipantConnectionContext,
ParticipantDispatch, ParticipantIngress, ParticipantSemanticHandler, constant_time_eq,
dispatch_generic_frame, encode_server_value, gate_generic_frame,
};
use liminal_protocol::credential::WireCredential;
use liminal_protocol::wire::{
ClientRequest, ConversationId, ParticipantTransportRejected, ServerValue,
TransportRejectionReason,
};
const SERVER_ERROR_CODE: u16 = 0xFFFF;
const SUPPORTED_PROTOCOLS: [ProtocolVersion; 2] =
[ProtocolVersion::new(1, 0), PUBLISH_ACK_POSITION_VERSION];
pub(super) fn apply_frame(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
frame: Frame,
) -> FrameAction {
if rejects_unauthenticated(runtime, state, &frame) {
return FrameAction::Close;
}
match frame {
Frame::Connect {
min_version,
max_version,
auth_token,
..
} => connect_once(pid, runtime, state, min_version, max_version, &auth_token),
Frame::Disconnect { .. } => {
FrameAction::CloseWithFate(ConnectionFateClass::CleanDisconnect)
}
Frame::Ping { .. } => FrameAction::Respond(Frame::Pong { flags: 0 }),
Frame::Publish {
stream_id,
channel,
envelope,
idempotency_key,
..
} => publish_frame_response(
pid,
runtime,
state,
stream_id,
&channel,
&envelope,
idempotency_key.as_deref(),
),
Frame::Subscribe {
stream_id,
channel,
accepted_schemas: schemas,
max_in_flight: window,
..
} => subscribe_frame_response(pid, runtime, state, stream_id, &channel, &schemas, window),
Frame::Unsubscribe {
stream_id,
subscription_id,
..
} => unsubscribe_frame_response(pid, runtime, state, stream_id, subscription_id),
Frame::ConversationOpen {
stream_id,
conversation_id,
subject,
..
} => conversation_open(pid, runtime, state, stream_id, conversation_id, &subject),
Frame::ConversationMessage {
flags,
stream_id,
conversation_id,
envelope,
} => {
let request = Request::new(flags, stream_id, conversation_id);
super::service_tap::conversation_message(pid, runtime, state, request, &envelope)
}
Frame::ConversationClose {
stream_id,
conversation_id,
..
} => conversation_close(runtime.services(), state, stream_id, conversation_id),
Frame::PushReply {
correlation_id,
payload,
..
} => push_reply_response(pid, runtime, state, correlation_id, payload),
Frame::WorkerRegister { registration, .. } => {
worker_register_frame_response(pid, runtime, state, registration)
}
Frame::LinkOpen {
stream_id, offer, ..
} => link_open_frame_response(pid, runtime, state, stream_id, &offer),
Frame::LinkMessage {
stream_id,
subject,
sealed,
..
} => link_message_frame_response(pid, runtime, state, stream_id, &subject, &sealed),
frame @ Frame::Unknown {
type_id: PARTICIPANT_FRAME_TYPE,
..
} => participant_frame_response(runtime, state, &frame),
frame @ (Frame::Accept { .. } | Frame::Defer { .. } | Frame::Reject { .. }) => {
apply_pressure_frame(pid, runtime, state, &frame)
}
frame @ (Frame::Push { .. }
| Frame::Deliver { .. }
| Frame::WorkerRegisterAck { .. }
| Frame::LinkOpenAck { .. }
| Frame::DisconnectReason { .. }
| Frame::Unknown { .. }
| Frame::ConnectAck { .. }
| Frame::ConnectError { .. }
| Frame::SubscribeAck { .. }
| Frame::SubscribeError { .. }
| Frame::PublishAck { .. }
| Frame::PublishError { .. }
| Frame::ConversationError { .. }
| Frame::Pong { .. }) => ignore_stray_inbound_frame(&frame),
}
}
pub(super) fn rejects_unauthenticated(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
frame: &Frame,
) -> bool {
(runtime.auth_token().is_some()
|| runtime.pass_verifier().is_some()
|| runtime.revocation().is_some())
&& !state.authenticated
&& !permitted_before_auth(frame)
}
fn push_reply_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
correlation_id: u64,
payload: Vec<u8>,
) -> FrameAction {
let refusal = match runtime.push_binding(pid, correlation_id) {
PushBinding::Unbound => None,
PushBinding::Subjectless => state.lys_session.is_some().then(|| {
"the push carries no subject binding; a Lys-admitted connection's reply is decided on the pushed subject".to_owned()
}),
PushBinding::Subject(subject) => lys_refusal(runtime, state, &subject, HotAction::Serve)
.or_else(|| membership_refusal(state, &subject, MembershipAction::Post)),
};
if let Some(reason) = refusal {
tracing::warn!(
pid,
correlation_id,
reason = reason.as_str(),
"refused PushReply on the subject its push was made on"
);
log_push_disposition(
pid,
correlation_id,
runtime.refuse_push(pid, correlation_id, reason),
);
return FrameAction::NoResponse;
}
log_push_disposition(
pid,
correlation_id,
runtime.resolve_push(pid, correlation_id, payload),
);
FrameAction::NoResponse
}
fn log_push_disposition(pid: u64, correlation_id: u64, disposition: PushReplyDisposition) {
match disposition {
PushReplyDisposition::Resolved => {}
PushReplyDisposition::SenderMismatch => tracing::warn!(
pid,
correlation_id,
reply_disposition = "sender_mismatch",
"discarded PushReply from a connection other than its recipient"
),
PushReplyDisposition::UnknownCorrelation => tracing::debug!(
pid,
correlation_id,
reply_disposition = "unknown_correlation",
"discarded PushReply for a missing slot"
),
PushReplyDisposition::ReceiverDropped => tracing::debug!(
pid,
correlation_id,
reply_disposition = "receiver_dropped",
"discarded PushReply after its receiver was abandoned"
),
}
}
fn apply_pressure_frame(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
frame: &Frame,
) -> FrameAction {
let (stream, reference, response) = match frame {
Frame::Accept {
stream_id,
referenced_message_id,
..
} => (*stream_id, referenced_message_id.as_str(), "accept"),
Frame::Defer {
stream_id,
referenced_message_id,
..
} => (*stream_id, referenced_message_id.as_str(), "defer"),
Frame::Reject {
stream_id,
referenced_message_id,
..
} => (*stream_id, referenced_message_id.as_str(), "reject"),
other => return ignore_stray_inbound_frame(other),
};
pressure_frame_response(pid, runtime, state, stream, reference, response)
}
fn pressure_frame_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
reference: &str,
response: &'static str,
) -> FrameAction {
let action = pressure_response(runtime.services(), stream_id);
if !runtime.services().supports_channel_operations() {
return action;
}
let subjects: Vec<String> = state
.subscriptions
.values()
.filter(|subscription| subscription.stream_id() == stream_id)
.map(|subscription| subscription.channel().to_owned())
.collect();
for subject in &subjects {
if let Some(reason) = lys_refusal(runtime, state, subject, HotAction::Subscribe)
.or_else(|| membership_refusal(state, subject, MembershipAction::Read))
{
tracing::warn!(
pid,
stream_id,
response,
reason = reason.as_str(),
"refused backpressure signal on its subscription's subject"
);
crate::metrics::pressure_refused();
return action;
}
}
let matched = state.subscriptions.values_mut().find_map(|subscription| {
if subscription.stream_id() == stream_id
&& subscription.match_pressure(reference, response == "defer")
{
Some(subscription)
} else {
None
}
});
if let Some(subscription) = matched {
if matches!(subscription.channel(), "liminal.events" | "liminal.cost") {
crate::metrics::self_stream_pressure_suppressed();
} else {
runtime.pressure(pid, response);
}
} else {
crate::metrics::self_stream_pressure_unmatched();
}
action
}
fn ignore_stray_inbound_frame(frame: &Frame) -> FrameAction {
warn_ignored_inbound_frame(ignored_inbound_frame_kind(frame));
FrameAction::NoResponse
}
const fn ignored_inbound_frame_kind(frame: &Frame) -> &'static str {
match frame {
Frame::Push { .. } => "push",
Frame::Deliver { .. } => "deliver",
Frame::WorkerRegisterAck { .. } => "worker_register_ack",
Frame::LinkOpenAck { .. } => "link_open_ack",
Frame::DisconnectReason { .. } => "disconnect_reason",
Frame::Unknown { .. } => "unknown",
Frame::ConnectAck { .. } => "connect_ack",
Frame::ConnectError { .. } => "connect_error",
Frame::SubscribeAck { .. } => "subscribe_ack",
Frame::SubscribeError { .. } => "subscribe_error",
Frame::PublishAck { .. } => "publish_ack",
Frame::PublishError { .. } => "publish_error",
Frame::ConversationError { .. } => "conversation_error",
Frame::Pong { .. } => "pong",
_ => "other",
}
}
fn warn_ignored_inbound_frame(kind: &'static str) {
tracing::warn!(
frame_kind = kind,
"ignored stray inbound frame; server-to-client-only or unknown frame kinds get no response and no teardown"
);
}
fn warn_partial_activation(stage: &'static str, service_installed: bool) {
tracing::warn!(
stage,
service_installed,
connection_incarnation_present = !service_installed,
"closing on partial participant activation; a complete participant service \
and a durable connection incarnation are one invariant and exactly one is \
present (internal configuration drift, not a client fault)"
);
}
fn connect_once(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
min_version: ProtocolVersion,
max_version: ProtocolVersion,
auth_token: &[u8],
) -> FrameAction {
if state.authenticated {
FrameAction::Close
} else {
connect_response(pid, runtime, state, min_version, max_version, auth_token)
}
}
fn participant_frame_response(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
frame: &Frame,
) -> FrameAction {
match (runtime.participant_service(), state.connection_incarnation) {
(Some(service), Some(connection_incarnation)) => {
if let Some(refused) = participant_lys_refusal(runtime, state, service, frame) {
return refused;
}
match dispatch_generic_frame(
frame,
state.authenticated,
state.participant_session,
ParticipantConnectionContext::new(connection_incarnation, state.mount),
state.pass_principal.as_ref(),
&mut state.participant_conversations,
service,
) {
ParticipantDispatch::NotParticipant => FrameAction::NoResponse,
ParticipantDispatch::Respond(response) => FrameAction::Respond(response),
ParticipantDispatch::RespondThenClose(response) => {
FrameAction::RespondThenClose(response)
}
ParticipantDispatch::Fatal(error) => {
tracing::warn!(%error, "participant request failed closed");
FrameAction::Close
}
}
}
(None, None) => participant_frame_without_service(state, frame),
(Some(_), None) | (None, Some(_)) => {
warn_partial_activation(
"participant frame dispatch",
runtime.participant_service().is_some(),
);
FrameAction::Close
}
}
}
fn participant_lys_refusal(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
service: &dyn ParticipantSemanticHandler,
frame: &Frame,
) -> Option<FrameAction> {
if state.lys_session.is_none() && state.revocation.is_none() {
return None;
}
let ParticipantIngress::Request(request) =
gate_generic_frame(frame, state.authenticated, state.participant_session)
else {
return None;
};
let refusal = participant_access_refusal(runtime, state, service, &request)?;
tracing::warn!(
route = ?request.discriminant(),
reason = refusal.as_str(),
"refused participant request on its conversation's subject"
);
let rejected = ServerValue::ParticipantTransportRejected(ParticipantTransportRejected {
reason: TransportRejectionReason::AccessRefused { refusal },
});
match encode_server_value(rejected) {
Ok(frame) => Some(FrameAction::Respond(frame)),
Err(error) => {
tracing::warn!(
error = ?error,
"a participant access refusal could not be encoded; closing"
);
Some(FrameAction::Close)
}
}
}
fn participant_access_refusal(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
service: &dyn ParticipantSemanticHandler,
request: &ClientRequest,
) -> Option<String> {
let action = match classify_participant_route(request.discriminant()) {
RouteClassification::Hot { action, .. } | RouteClassification::Cleanup { action, .. } => {
action
}
RouteClassification::Admission
| RouteClassification::Control
| RouteClassification::ServerOnly
| RouteClassification::ParticipantEnvelope
| RouteClassification::Deliberate { .. }
| RouteClassification::Crossing { .. }
| RouteClassification::Unknown => {
return Some(format!(
"participant route {:?} has no hot or cleanup class; refused",
request.discriminant()
));
}
};
participant_conversations(request)
.into_iter()
.find_map(|conversation_id| {
let subject = service.conversation_subject(conversation_id).or_else(|| {
runtime
.membership_gate()
.and_then(|gate| gate.channel_for_conversation(conversation_id))
.map(str::to_owned)
});
subject.map_or_else(
|| {
Some(format!(
"no subject mapping for conversation {conversation_id}; a Lys-admitted connection's participant request is decided on its conversation's subject"
))
},
|subject| {
lys_refusal(runtime, state, &subject, action).or_else(|| {
membership_refusal(state, &subject, MembershipAction::for_hot(action))
})
},
)
})
}
fn participant_conversations(request: &ClientRequest) -> Vec<ConversationId> {
match request {
ClientRequest::Enrollment(request) => vec![request.conversation_id],
ClientRequest::CredentialAttach(request) => vec![request.conversation_id],
ClientRequest::Detach(request) => vec![request.conversation_id],
ClientRequest::ParticipantAck(request) => vec![request.conversation_id],
ClientRequest::Leave(request) => vec![request.conversation_id],
ClientRequest::MarkerAck(request) => vec![request.conversation_id],
ClientRequest::RecordAdmission(request) => vec![request.conversation_id],
ClientRequest::SettlementQuery(request) => vec![request.conversation_id],
ClientRequest::ObserverRecovery(request) => request
.observer_refusals
.iter()
.map(|refusal| refusal.conversation_id)
.collect(),
}
}
fn participant_frame_without_service(state: &ConnectionProcessState, frame: &Frame) -> FrameAction {
match gate_generic_frame(frame, state.authenticated, state.participant_session) {
ParticipantIngress::NotParticipant => FrameAction::NoResponse,
ParticipantIngress::Request(_) | ParticipantIngress::InvalidGenericFrame => {
FrameAction::Close
}
ParticipantIngress::Rejected(rejection) => {
let reason = format!("{:?}", rejection.reason);
match encode_server_value(
liminal_protocol::wire::ServerValue::ParticipantTransportRejected(rejection),
) {
Ok(response) => FrameAction::RespondThenClose(response),
Err(error) => {
tracing::warn!(
rejection_reason = %reason,
?error,
"participant transport rejection could not be encoded; closing \
without telling the client why"
);
FrameAction::Close
}
}
}
}
}
fn pressure_response(services: &dyn ConnectionServices, stream_id: u32) -> FrameAction {
if services.supports_channel_operations() {
FrameAction::NoResponse
} else {
FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(unsupported_by_front_door("backpressure signaling")),
})
}
}
fn unsupported_by_front_door(operation: &str) -> String {
ServerError::UnsupportedOperation {
operation: operation.to_owned(),
profile: ServiceProfile::WORKER_FRONT_DOOR,
}
.to_string()
}
fn worker_register_frame_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
registration: WorkerRegistration,
) -> FrameAction {
match state.verified_holder.as_ref() {
Some(holder) if !holder.is_named(®istration.identity) => {
return worker_register_rejected(REGISTRATION_NOT_HOLDER.to_owned());
}
None if runtime
.pass_verifier()
.is_some_and(PassVerifier::requires_verified_worker) =>
{
return worker_register_rejected(REGISTRATION_WITHOUT_HOLDER.to_owned());
}
Some(_) | None => {}
}
for namespace in ®istration.namespaces {
if let Some(refused) = lys_decision(runtime, state, namespace, HotAction::Serve, |reason| {
Frame::WorkerRegisterAck {
flags: 0,
outcome: WorkerRegisterOutcome::Rejected { reason },
}
}) {
return refused;
}
}
let process = state.service_process.clone();
if let Some(process) = process.as_ref()
&& !process.serves(®istration.task_queue)
{
return worker_register_rejected(
"service_process_scope: a service process registers its own subjects only".to_owned(),
);
}
let action =
worker_register_response(pid, runtime, registration, state.verified_holder.as_ref());
if let Some(process) = process
&& matches!(
action,
FrameAction::Respond(Frame::WorkerRegisterAck {
outcome: WorkerRegisterOutcome::Accepted,
..
})
)
&& let Some(lifecycle) = runtime.lifecycle()
&& let Err(error) =
lifecycle.mark_live(&process.service, unix_seconds_now().saturating_mul(1000))
{
tracing::warn!(service = %process.service, %error, "a live service could not be recorded");
}
if matches!(
action,
FrameAction::Respond(Frame::WorkerRegisterAck {
outcome: WorkerRegisterOutcome::Accepted,
..
})
) {
state.worker_flows.get_or_insert_with(BTreeMap::new);
}
action
}
fn worker_register_response(
pid: u64,
runtime: &ConnectionRuntime,
registration: WorkerRegistration,
holder: Option<&VerifiedHolder>,
) -> FrameAction {
if let Some(notifier) = runtime.notifier() {
if let Err(error) = notifier.on_worker_registered_by(pid, ®istration, holder) {
return worker_register_rejected(error.to_string());
}
}
if let Err(error) = runtime.set_registration(pid, registration) {
if let Some(notifier) = runtime.notifier() {
notifier.on_worker_unregistered(pid);
}
return worker_register_rejected(error.to_string());
}
FrameAction::Respond(Frame::WorkerRegisterAck {
flags: 0,
outcome: WorkerRegisterOutcome::Accepted,
})
}
const REGISTRATION_NOT_HOLDER: &str = "registration identity is not the verified holder";
const REGISTRATION_WITHOUT_HOLDER: &str = "worker registration requires a verified holder";
const fn worker_register_rejected(reason: String) -> FrameAction {
FrameAction::Respond(Frame::WorkerRegisterAck {
flags: 0,
outcome: WorkerRegisterOutcome::Rejected { reason },
})
}
const fn permitted_before_auth(frame: &Frame) -> bool {
matches!(
frame,
Frame::Connect { .. }
| Frame::Disconnect { .. }
| Frame::Ping { .. }
| Frame::Push { .. }
| Frame::Deliver { .. }
| Frame::WorkerRegisterAck { .. }
| Frame::LinkOpenAck { .. }
| Frame::DisconnectReason { .. }
| Frame::Unknown { .. }
| Frame::ConnectAck { .. }
| Frame::ConnectError { .. }
| Frame::SubscribeAck { .. }
| Frame::SubscribeError { .. }
| Frame::PublishAck { .. }
| Frame::PublishError { .. }
| Frame::ConversationError { .. }
| Frame::Pong { .. }
)
}
fn lys_connect_refused(reason: &str) -> FrameAction {
let error = ProtocolError::AuthenticationFailure {
message: Some(format!("lys pass admission refused: {reason}")),
};
FrameAction::RespondThenClose(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
})
}
pub(super) fn unix_seconds_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_secs())
}
fn lys_decision(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
channel: &str,
action: HotAction,
refused: impl FnOnce(String) -> Frame,
) -> Option<FrameAction> {
lys_refusal(runtime, state, channel, action)
.or_else(|| membership_refusal(state, channel, MembershipAction::for_hot(action)))
.map(|message| FrameAction::Respond(refused(message)))
}
fn membership_refusal(
state: &ConnectionProcessState,
channel: &str,
action: MembershipAction,
) -> Option<String> {
let refusal = state.membership.check(channel, action).err()?;
tracing::warn!(
refusal = refusal.name(),
action = action.as_str(),
"refused a membership-protected act"
);
Some(refusal.to_string())
}
fn lys_refusal(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
channel: &str,
action: HotAction,
) -> Option<String> {
if let Some(process) = state.service_process.as_ref()
&& !(action == HotAction::Serve && process.serves(channel))
{
return Some(
"service_process_scope: a service process serves its own subjects and nothing else"
.to_owned(),
);
}
if let Some(grants) = state.revocation.as_ref()
&& let Err(refusal) = grants.allows(action, channel)
{
return Some(refusal.name().to_owned());
}
let session = state.lys_session.as_ref()?;
let Some((_, network_id)) = runtime.lys_admitter() else {
return Some("no lys admitter is installed".to_owned());
};
let subject = match SubjectBinding::new(
network_id.to_owned(),
channel.to_owned(),
channel.to_owned(),
) {
Ok(subject) => subject,
Err(SubjectBindingError(member)) => {
return Some(format!("subject binding refused: {member}"));
}
};
let now = unix_seconds_now();
let error = match session.decide(&subject, action, now) {
Ok(_receipt) => return None,
Err(error) => error,
};
let placed_after_pass = error.cause == RefusalCause::MissingGrant
&& session.issued_at().is_some_and(|issued| {
runtime.lifecycle().is_some_and(|lifecycle| {
lifecycle.placed_since(channel, issued.saturating_mul(1000))
})
});
if placed_after_pass
&& let Ok(DeliberateDecision::Outright(_receipt)) =
session.decide_live(subject.resource(), action.into(), now)
{
return None;
}
Some(serde_json::to_string(&error.refusal).unwrap_or_else(|_| error.cause.to_string()))
}
fn pass_allows_channel(state: &ConnectionProcessState, channel: &str) -> bool {
state
.verified_holder
.as_ref()
.is_none_or(|holder| holder.allows_channel(channel))
}
fn publish_frame_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
channel: &str,
envelope: &MessageEnvelope,
idempotency_key: Option<&str>,
) -> FrameAction {
if let Some(refused) = lys_decision(runtime, state, channel, HotAction::Publish, |message| {
Frame::PublishError {
flags: 0,
stream_id,
reason_code: 0x0202,
message: Some(message),
}
}) {
return refused;
}
if !pass_allows_channel(state, channel) {
return FrameAction::Respond(Frame::PublishError {
flags: 0,
stream_id,
reason_code: 0x0202,
message: Some(format!("publish refused for channel {channel}")),
});
}
if crate::console::tappable(channel)
&& runtime.notifier_channel_publish(pid, channel, &envelope.payload)
{
FrameAction::NoResponse
} else {
let action = publish_response(
pid,
runtime.services(),
state.negotiated_version,
stream_id,
channel,
envelope,
idempotency_key,
);
flowed(pid, runtime, state, channel, FlowRole::Publishes, action)
}
}
fn flowed(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
channel: &str,
role: FlowRole,
action: FrameAction,
) -> FrameAction {
let bit = flow_bit(role);
if let Some(flows) = state.worker_flows.as_mut()
&& flows.get(channel).is_none_or(|known| known & bit == 0)
&& matches!(
action,
FrameAction::Respond(Frame::PublishAck { .. } | Frame::SubscribeAck { .. })
)
{
*flows.entry(channel.to_owned()).or_default() |= bit;
runtime.flow(pid, channel, role, true);
}
action
}
const fn flow_bit(role: FlowRole) -> u8 {
match role {
FlowRole::Publishes => 1,
FlowRole::Subscribes => 2,
}
}
fn subscribe_frame_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
channel: &str,
accepted_schemas: &[ProtocolSchemaId],
max_in_flight: u32,
) -> FrameAction {
if let Some(refused) = lys_decision(runtime, state, channel, HotAction::Subscribe, |message| {
Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: 0x0201,
message: Some(message),
}
}) {
return refused;
}
if pass_allows_channel(state, channel) {
let action = subscribe_response(
pid,
runtime,
state,
stream_id,
channel,
accepted_schemas,
max_in_flight,
);
flowed(pid, runtime, state, channel, FlowRole::Subscribes, action)
} else {
FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: 0x0201,
message: Some(format!("subscribe refused for channel {channel}")),
})
}
}
fn authentication_failed(message: String) -> FrameAction {
let error = ProtocolError::AuthenticationFailure {
message: Some(message),
};
FrameAction::RespondThenClose(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
})
}
fn admit_credential(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
(min_version, max_version): (ProtocolVersion, ProtocolVersion),
auth_token: &[u8],
) -> Result<(), Box<FrameAction>> {
if let Some(grant) = runtime
.lifecycle()
.and_then(|lifecycle| lifecycle.admit_process(auth_token))
{
state.service_process = Some(grant);
return Ok(());
}
let bearer_matches = runtime
.auth_token()
.is_some_and(|expected| constant_time_eq(expected, auth_token));
if !bearer_matches {
if let Ok(WireCredential::LysPass(pass)) = WireCredential::classify(auth_token) {
let Some((admitter, _)) = runtime.lys_admitter() else {
return Err(Box::new(lys_connect_refused(
"no lys admitter is installed",
)));
};
match admitter.admit(pass, unix_seconds_now()) {
Ok(session) => {
state.verified_holder =
Some(VerifiedHolder::from_lys_session(session.as_ref()));
state.lys_session = Some(std::sync::Arc::from(session));
}
Err(refusal) => return Err(Box::new(lys_connect_refused(&refusal.reason))),
}
} else if let Some(verifier) = runtime.pass_verifier() {
match verifier.verify(auth_token) {
Ok(VerifiedPass { principal, holder }) => {
revocation_connect(
pid,
runtime,
state,
(min_version, max_version),
auth_token,
holder.participant(),
)?;
runtime.pass_attached(pid, state.mount, holder.participant());
state.pass_principal = Some(principal);
state.verified_holder = Some(holder);
}
Err(failure) => {
return Err(Box::new(authentication_failed(format!(
"connection pass {} check failed",
failure.check_name()
))));
}
}
} else if runtime.auth_token().is_some() {
return Err(Box::new(authentication_failed(
"connection authentication token rejected".to_owned(),
)));
}
}
if runtime.revocation().is_some() && state.revocation.is_none() {
let holder = state
.verified_holder
.as_ref()
.map(|holder| holder.participant().to_vec());
holder.map_or_else(
|| {
Err(Box::new(revocation_connect_refused(
RevocationRefusal::BindingMissing,
)))
},
|holder| {
revocation_connect(
pid,
runtime,
state,
(min_version, max_version),
auth_token,
&holder,
)
},
)?;
}
Ok(())
}
fn connect_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
min_version: ProtocolVersion,
max_version: ProtocolVersion,
auth_token: &[u8],
) -> FrameAction {
if let Err(refused) =
admit_credential(pid, runtime, state, (min_version, max_version), auth_token)
{
return *refused;
}
let membership = ConnectionMembership::bind(
runtime.membership_gate().cloned(),
pid,
state.lys_session.as_deref(),
);
if state.lys_session.is_some()
&& let Err(refusal) = membership.admit_connect()
{
return lys_connect_refused(refusal.name());
}
state.membership = membership;
let selected_version =
match negotiate_version(min_version, max_version, supported_protocols(runtime)) {
Ok(selected_version) => selected_version,
Err(error) => {
return FrameAction::RespondThenClose(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
});
}
};
let (participant_session, capabilities) =
match (runtime.participant_service(), state.connection_incarnation) {
(Some(service), Some(_)) => {
let mut participant_session =
crate::server::participant::ParticipantSession::default();
participant_session.negotiate_v1(service.frame_limit());
(participant_session, PARTICIPANT_CAPABILITY_BIT)
}
(None, None) => (crate::server::participant::ParticipantSession::default(), 0),
(Some(_), None) | (None, Some(_)) => {
warn_partial_activation(
"connection handshake",
runtime.participant_service().is_some(),
);
return FrameAction::Close;
}
};
if let Some(principal) = state.pass_principal.as_ref() {
if let Err(error) = runtime.attach_pass_principal(pid, principal) {
tracing::warn!(
%error,
connection_pid = pid,
"pass-stamped connection could not record its principal; closing"
);
return FrameAction::Close;
}
}
state.authenticated = true;
state.negotiated_version = Some(selected_version);
state.participant_session = participant_session;
FrameAction::Respond(Frame::ConnectAck {
flags: 0,
selected_version,
capabilities,
})
}
pub(super) fn publish_response(
pid: u64,
services: &dyn ConnectionServices,
negotiated_version: Option<ProtocolVersion>,
stream_id: u32,
channel: &str,
envelope: &MessageEnvelope,
idempotency_key: Option<&str>,
) -> FrameAction {
if let Some(error) = reserved_namespace_refusal(channel) {
return FrameAction::Respond(Frame::PublishError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(error.to_string()),
});
}
if let Err(refusal) = services.admit_channel(ChannelOperation::Publish, channel) {
return FrameAction::Respond(Frame::PublishError {
flags: 0,
stream_id,
reason_code: refusal.reason_code(),
message: Some(access_to_server_error(&refusal).to_string()),
});
}
match services.publish(pid, channel, envelope, idempotency_key) {
Ok(outcome) => FrameAction::Respond(Frame::PublishAck {
flags: if outcome.delivered {
PUBLISH_DELIVERED_FLAG
} else {
0
},
stream_id,
message_id: outcome.message_id,
durable_position: outcome.durable_position.filter(|_| {
negotiated_version.is_some_and(|version| version >= PUBLISH_ACK_POSITION_VERSION)
}),
}),
Err(error) => FrameAction::Respond(Frame::PublishError {
flags: 0,
stream_id,
reason_code: if matches!(error, ServerError::PublishUnresolved { .. }) {
PUBLISH_UNRESOLVED_CODE
} else {
SERVER_ERROR_CODE
},
message: Some(error.to_string()),
}),
}
}
fn subscribe_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
channel: &str,
accepted_schemas: &[ProtocolSchemaId],
max_in_flight: u32,
) -> FrameAction {
let limits = runtime.limits();
let max_subscriptions = limits.max_subscriptions_per_connection;
if state.subscriptions.len() >= max_subscriptions {
return FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(
ServerError::ConnectionCapReached {
operation: "subscribe".to_owned(),
cap: "max_subscriptions_per_connection",
limit: max_subscriptions,
}
.to_string(),
),
});
}
let services = runtime.services();
if let Err(refusal) = services.admit_channel(ChannelOperation::Subscribe, channel) {
return FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: refusal.reason_code(),
message: Some(access_to_server_error(&refusal).to_string()),
});
}
let budget = state
.inbox_budget
.get_or_insert_with(|| {
liminal::channel::ConnectionInboxBudget::new(limits.max_connection_inbox_bytes)
})
.clone();
let notifier: Option<liminal::channel::InboxNotifier> = runtime.ready_waker(pid).map(|waker| {
std::sync::Arc::new(move || {
waker.fire();
}) as liminal::channel::InboxNotifier
});
let capacity = usize::try_from(max_in_flight)
.ok()
.filter(|window| *window > 0)
.map(|window| liminal::pressure::ConsumerCapacity {
max_in_flight: window,
max_buffer_depth: liminal::channel::DEFAULT_MAX_BUFFER_DEPTH,
})
.map(|declared| {
liminal::channel::clamp_capacity_to_depth_cap(
declared,
limits.max_subscription_inbox_depth,
)
});
let install = liminal::channel::InboxInstall {
budget,
depth_cap: limits.max_subscription_inbox_depth,
notifier,
capacity,
};
match services.subscribe(channel, accepted_schemas, Some(install)) {
Ok(mut subscription) => {
subscription.set_stream_id(stream_id);
subscription.set_channel(channel.to_owned());
if let Some(subscriber) = subscription.subscriber_pid() {
runtime.receiver(pid, subscriber);
}
subscription.set_pressure_ledger_depth(limits.max_subscription_inbox_depth);
let subscription_id = subscription.id();
let selected_schema = subscription.selected_schema();
state.subscriptions.insert(subscription_id, subscription);
FrameAction::Respond(Frame::SubscribeAck {
flags: 0,
stream_id,
subscription_id,
selected_schema,
})
}
Err(error) => FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(error.to_string()),
}),
}
}
fn unsubscribe_frame_response(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
subscription_id: u64,
) -> FrameAction {
let line = state
.subscriptions
.get(&subscription_id)
.filter(|_| state.worker_flows.is_some())
.map(|subscription| subscription.channel().to_owned());
let action = unsubscribe_response(runtime.services(), state, stream_id, subscription_id);
let bit = flow_bit(FlowRole::Subscribes);
if let Some(line) = line
&& !state
.subscriptions
.values()
.any(|subscription| subscription.channel() == line)
&& let Some(flows) = state.worker_flows.as_mut()
&& let Some(known) = flows.get_mut(&line)
&& *known & bit != 0
{
*known &= !bit;
if *known == 0 {
flows.remove(&line);
}
runtime.flow(pid, &line, FlowRole::Subscribes, false);
}
action
}
fn unsubscribe_response(
services: &dyn ConnectionServices,
state: &mut ConnectionProcessState,
stream_id: u32,
subscription_id: u64,
) -> FrameAction {
if let Some(subscription) = state.subscriptions.remove(&subscription_id) {
state.delivery_seqs.remove(&subscription_id);
state.held_deliveries.remove(&subscription_id);
if let Err(error) = services.unsubscribe(subscription) {
tracing::warn!(subscription_id, %error, "liminal unsubscribe failed");
}
return FrameAction::NoResponse;
}
if services.supports_channel_operations() {
FrameAction::NoResponse
} else {
FrameAction::Respond(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: SERVER_ERROR_CODE,
message: Some(unsupported_by_front_door("unsubscribe")),
})
}
}
fn conversation_open(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
stream_id: u32,
conversation_id: u64,
subject: &str,
) -> FrameAction {
if let Some(refused) = lys_decision(runtime, state, subject, HotAction::Call, |message| {
Frame::ConversationError {
flags: 0,
stream_id,
conversation_id,
reason_code: SERVER_ERROR_CODE,
message: Some(message),
}
}) {
return refused;
}
if state.conversations.contains_key(&conversation_id) {
return conversation_error(
stream_id,
conversation_id,
"conversation id is already open on this connection; close it before \
reopening or use a fresh id",
);
}
let max_conversations = runtime.limits().max_conversations_per_connection;
if state.conversations.len() >= max_conversations {
return conversation_error(
stream_id,
conversation_id,
&ServerError::ConnectionCapReached {
operation: "conversation open".to_owned(),
cap: "max_conversations_per_connection",
limit: max_conversations,
}
.to_string(),
);
}
match runtime
.services()
.open_conversation(conversation_id, subject)
{
Ok(conversation) => {
let reply_notifier: std::sync::Arc<dyn Fn() + Send + Sync> =
match runtime.ready_waker(pid) {
Some(waker) => std::sync::Arc::new(move || {
waker.fire();
}),
None => std::sync::Arc::new(|| {}),
};
conversation.register_reply_notifier(reply_notifier);
state.conversations.insert(conversation_id, conversation);
FrameAction::NoResponse
}
Err(error) => FrameAction::Respond(Frame::ConversationError {
flags: 0,
stream_id,
conversation_id,
reason_code: SERVER_ERROR_CODE,
message: Some(error.to_string()),
}),
}
}
fn route_conversation_message(
runtime: &ConnectionRuntime,
state: &ConnectionProcessState,
request: Request,
subject: &str,
envelope: &MessageEnvelope,
) -> Option<FrameAction> {
let Request {
flags,
stream_id,
conversation_id,
} = request;
if super::lifecycle_route::is_lifecycle(subject) {
return Some(super::lifecycle_route::lifecycle_frame_response(
runtime,
state,
flags,
stream_id,
conversation_id,
subject,
envelope,
));
}
if crate::services::broker::serves_subject(subject) {
return Some(super::broker_route::broker_frame_response(
runtime, state, request, subject, envelope,
));
}
if crate::services::service_of(subject).is_some() {
return Some(super::service_route::service_frame_response(
runtime,
state,
flags,
stream_id,
conversation_id,
subject,
envelope,
));
}
None
}
#[path = "apply/service_message.rs"]
mod service_message;
pub(super) use service_message::conversation_message;
fn conversation_error(stream_id: u32, conversation_id: u64, message: &str) -> FrameAction {
FrameAction::Respond(Frame::ConversationError {
flags: 0,
stream_id,
conversation_id,
reason_code: SERVER_ERROR_CODE,
message: Some(message.to_owned()),
})
}
fn conversation_close(
services: &dyn ConnectionServices,
state: &mut ConnectionProcessState,
stream_id: u32,
conversation_id: u64,
) -> FrameAction {
if let Some(conversation) = state.conversations.remove(&conversation_id) {
state.pending_replies.remove_conversation(conversation_id);
if let Err(error) = services.close_conversation(conversation) {
tracing::warn!(conversation_id, %error, "liminal conversation close failed");
}
return FrameAction::NoResponse;
}
if services.supports_channel_operations() {
FrameAction::NoResponse
} else {
conversation_error(
stream_id,
conversation_id,
&unsupported_by_front_door("conversation close"),
)
}
}
#[cfg(test)]
#[path = "process_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "apply_worker_holder_tests.rs"]
mod worker_holder_tests;
#[cfg(test)]
#[path = "apply_lys_routes_tests.rs"]
mod lys_routes_tests;
#[cfg(test)]
#[path = "apply_admission_tests.rs"]
mod admission_tests;
#[cfg(test)]
#[path = "apply_capacity_tests.rs"]
mod capacity_tests;
#[cfg(test)]
#[path = "apply_publish_outcome_tests.rs"]
mod publish_outcome_tests;
const REVOCATION_PROTOCOLS: [ProtocolVersion; 3] = [
ProtocolVersion::new(1, 0),
PUBLISH_ACK_POSITION_VERSION,
liminal::protocol::REVOCATION_CLOSE_VERSION,
];
fn supported_protocols(runtime: &ConnectionRuntime) -> &'static [ProtocolVersion] {
if runtime.revocation().is_some() {
&REVOCATION_PROTOCOLS
} else {
&SUPPORTED_PROTOCOLS
}
}
fn revocation_connect_refused(refusal: crate::auth_revocation::RevocationRefusal) -> FrameAction {
let error = ProtocolError::AuthenticationFailure {
message: Some(refusal.name().to_owned()),
};
FrameAction::RespondThenClose(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
})
}
fn revocation_connect(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
(min_version, max_version): (ProtocolVersion, ProtocolVersion),
credential: &[u8],
holder: &[u8],
) -> Result<(), Box<FrameAction>> {
let Some(authority) = runtime.revocation() else {
return Ok(());
};
if state.revocation.is_some() {
return Err(Box::new(FrameAction::Close));
}
let closes_with_reason = negotiate_version(min_version, max_version, &REVOCATION_PROTOCOLS)
.is_ok_and(|selected| selected >= liminal::protocol::REVOCATION_CLOSE_VERSION);
if !closes_with_reason {
return Err(Box::new(revocation_connect_refused(
RevocationRefusal::ProtocolUnsupported,
)));
}
let grants = authority
.admit(pid, credential, holder)
.map_err(|refusal| Box::new(revocation_connect_refused(refusal)))?;
state.revocation_swept = grants.changes();
state.revocation = Some(grants);
Ok(())
}
pub(super) fn retire_withdrawn_rights(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
) -> Vec<Frame> {
let Some(changes) = state
.revocation
.as_ref()
.map(crate::auth_revocation::ConnectionGrants::changes)
else {
return Vec::new();
};
if changes == state.revocation_swept {
return Vec::new();
}
let Some(grants) = state.revocation.clone() else {
return Vec::new();
};
state.revocation_swept = changes;
let services = runtime.services();
let withdrawn: Vec<(u64, u32)> = state
.subscriptions
.iter()
.filter(|(_, subscription)| !grants.permits(HotAction::Subscribe, subscription.channel()))
.map(|(id, subscription)| (*id, subscription.stream_id()))
.collect();
let mut frames = Vec::with_capacity(withdrawn.len());
for (subscription_id, stream_id) in withdrawn {
unsubscribe_response(services, state, stream_id, subscription_id);
frames.push(Frame::SubscribeError {
flags: 0,
stream_id,
reason_code: 0x0201,
message: Some(RevocationRefusal::GrantRevoked.name().to_owned()),
});
}
let calls: Vec<u64> = state
.conversations
.iter()
.filter(|(_, conversation)| !grants.permits(HotAction::Call, conversation.subject()))
.map(|(id, _)| *id)
.collect();
for conversation_id in calls {
tracing::warn!(
connection_pid = grants.pid(),
conversation_id,
reason = RevocationRefusal::GrantRevoked.name(),
"conversation retired: its call right was revoked"
);
conversation_close(services, state, 0, conversation_id);
}
frames
}
#[cfg(test)]
#[path = "apply_revocation_tests.rs"]
mod revocation_tests;
#[cfg(test)]
#[path = "apply_membership_tests.rs"]
mod membership_tests;