use std::time::Instant;
use liminal::protocol::{
CONVERSATION_REPLY_REQUESTED_FLAG, Frame, MessageEnvelope, PUBLISH_DELIVERED_FLAG,
ProtocolError, ProtocolVersion, SchemaId as ProtocolSchemaId, WorkerRegisterOutcome,
WorkerRegistration, negotiate_version,
};
use super::services::ConnectionServices;
use super::state::{ConnectionProcessState, FrameAction};
use super::supervisor::ConnectionRuntime;
use crate::ServerError;
use crate::config::types::ServiceProfile;
const SERVER_ERROR_CODE: u16 = 0xFFFF;
const SUPPORTED_PROTOCOL: ProtocolVersion = ProtocolVersion::new(1, 0);
pub(super) fn apply_frame(
pid: u64,
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
frame: Frame,
) -> FrameAction {
if runtime.auth_token().is_some() && !state.authenticated && !permitted_before_auth(&frame) {
return FrameAction::Close;
}
let services = runtime.services();
match frame {
Frame::Connect {
min_version,
max_version,
auth_token,
..
} => connect_response(runtime, state, min_version, max_version, &auth_token),
Frame::Disconnect { .. } => FrameAction::Close,
Frame::Ping { .. } => FrameAction::Respond(Frame::Pong { flags: 0 }),
Frame::Publish {
stream_id,
channel,
envelope,
idempotency_key,
..
} => {
if runtime.notifier_channel_publish(pid, &channel, &envelope.payload) {
FrameAction::NoResponse
} else {
publish_response(
services,
stream_id,
&channel,
&envelope,
idempotency_key.as_deref(),
)
}
}
Frame::Subscribe {
stream_id,
channel,
accepted_schemas,
..
} => subscribe_response(pid, runtime, state, stream_id, &channel, &accepted_schemas),
Frame::Unsubscribe {
stream_id,
subscription_id,
..
} => unsubscribe_response(services, 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,
} => conversation_message(
services,
state,
flags,
stream_id,
conversation_id,
&envelope,
),
Frame::ConversationClose {
stream_id,
conversation_id,
..
} => conversation_close(services, state, stream_id, conversation_id),
Frame::PushReply {
correlation_id,
payload,
..
} => {
runtime.resolve_push(correlation_id, payload);
FrameAction::NoResponse
}
Frame::WorkerRegister { registration, .. } => {
worker_register_response(pid, runtime, registration)
}
Frame::Accept { stream_id, .. }
| Frame::Defer { stream_id, .. }
| Frame::Reject { stream_id, .. } => pressure_response(services, stream_id),
Frame::Push { .. }
| Frame::Deliver { .. }
| Frame::WorkerRegisterAck { .. }
| Frame::Unknown { .. }
| Frame::ConnectAck { .. }
| Frame::ConnectError { .. }
| Frame::SubscribeAck { .. }
| Frame::SubscribeError { .. }
| Frame::PublishAck { .. }
| Frame::PublishError { .. }
| Frame::ConversationError { .. }
| Frame::Pong { .. } => FrameAction::NoResponse,
}
}
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_response(
pid: u64,
runtime: &ConnectionRuntime,
registration: WorkerRegistration,
) -> FrameAction {
if let Some(notifier) = runtime.notifier() {
if let Err(error) = notifier.on_worker_registered(pid, ®istration) {
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 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::Unknown { .. }
| Frame::ConnectAck { .. }
| Frame::ConnectError { .. }
| Frame::SubscribeAck { .. }
| Frame::SubscribeError { .. }
| Frame::PublishAck { .. }
| Frame::PublishError { .. }
| Frame::ConversationError { .. }
| Frame::Pong { .. }
)
}
fn connect_response(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
min_version: ProtocolVersion,
max_version: ProtocolVersion,
auth_token: &[u8],
) -> FrameAction {
if let Some(expected) = runtime.auth_token() {
if !constant_time_eq(expected, auth_token) {
let error = ProtocolError::AuthenticationFailure {
message: Some("connection authentication token rejected".to_owned()),
};
return FrameAction::RespondThenClose(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
});
}
}
state.authenticated = true;
match negotiate_version(min_version, max_version, &[SUPPORTED_PROTOCOL]) {
Ok(selected_version) => FrameAction::Respond(Frame::ConnectAck {
flags: 0,
selected_version,
capabilities: 0,
}),
Err(error) => FrameAction::Respond(Frame::ConnectError {
flags: 0,
reason_code: error.reason_code(),
message: error.message().map(str::to_owned),
}),
}
}
fn constant_time_eq(expected: &[u8], candidate: &[u8]) -> bool {
let mut difference = u8::from(expected.len() != candidate.len());
for (left, right) in expected.iter().zip(candidate.iter()) {
difference |= left ^ right;
}
difference == 0
}
fn publish_response(
services: &dyn ConnectionServices,
stream_id: u32,
channel: &str,
envelope: &MessageEnvelope,
idempotency_key: Option<&str>,
) -> FrameAction {
match services.publish(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,
}),
Err(error) => FrameAction::Respond(Frame::PublishError {
flags: 0,
stream_id,
reason_code: 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],
) -> 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 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 install = liminal::channel::InboxInstall {
budget,
depth_cap: limits.max_subscription_inbox_depth,
notifier,
};
let services = runtime.services();
match services.subscribe(channel, accepted_schemas, Some(install)) {
Ok(mut subscription) => {
subscription.set_stream_id(stream_id);
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_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 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) => {
if let Some(waker) = runtime.ready_waker(pid) {
conversation.register_reply_notifier(std::sync::Arc::new(move || {
waker.fire();
}));
}
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 conversation_message(
services: &dyn ConnectionServices,
state: &mut ConnectionProcessState,
flags: u8,
stream_id: u32,
conversation_id: u64,
envelope: &MessageEnvelope,
) -> FrameAction {
if !services.supports_channel_operations() {
return conversation_error(
stream_id,
conversation_id,
&unsupported_by_front_door("conversation messaging"),
);
}
let Some(conversation) = state.conversations.get(&conversation_id) else {
return conversation_error(
stream_id,
conversation_id,
"conversation is not open on this connection",
);
};
if flags & CONVERSATION_REPLY_REQUESTED_FLAG == 0 {
if let Err(error) = services.conversation_message(conversation, envelope) {
return conversation_error(stream_id, conversation_id, &error.to_string());
}
return FrameAction::NoResponse;
}
let op_id = match state
.pending_replies
.admit(conversation_id, stream_id, Instant::now())
{
Ok(op_id) => op_id,
Err(error) => {
return FrameAction::Respond(super::pending_reply::cap_refusal_frame(
stream_id,
conversation_id,
&error,
));
}
};
if let Err(error) = services.conversation_message(conversation, envelope) {
state.pending_replies.cancel(op_id);
return conversation_error(stream_id, conversation_id, &error.to_string());
}
FrameAction::NoResponse
}
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;