#![allow(clippy::expect_used, clippy::unwrap_used, dead_code)]
pub mod embedded;
pub mod raw;
use std::error::Error;
use std::fs;
use std::io::Write as _;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
use frame_conv::{BusAttachment, ResumeStore, StoreError};
use liminal_server::config::types::ParticipantConfig;
use liminal_server::config::{ChannelDef, LimitsConfig, ServerConfig, ServicesConfig};
use liminal_server::server::connection::ConnectionSupervisor;
use liminal_server::server::listener::ServerListener;
pub const QUANTUM: Duration = Duration::from_secs(5);
#[must_use]
pub const fn participant_config() -> ParticipantConfig {
ParticipantConfig {
wire_frame_limit: 65_536,
attach_receipt_ttl_ms: 60_000,
receipt_provenance_ttl_ms: 600_000,
max_live_attach_receipts_server: 1_024,
max_live_attach_receipts_per_participant: 8,
max_receipt_provenance_server: 4_096,
max_receipt_provenance_per_conversation: 256,
max_receipt_provenance_per_participant: 64,
max_retired_identity_slots_server: 1_024,
identity_slots: 4,
observer_recovery_max_entries: 64,
max_semantic_conversations_per_connection: 32,
max_ordinary_record_entries: 1,
max_ordinary_record_bytes: 131_072,
max_generated_marker_entries: 1,
max_generated_marker_bytes: 4_096,
mandatory_transaction_bound_entries: 4,
mandatory_transaction_bound_bytes: 16_384,
full_recovery_claim_entries: 4,
full_recovery_claim_bytes: 16_384,
retained_capacity_entries: 2_048,
retained_capacity_bytes: 16_777_216,
max_retained_record_rows: 1_024,
closure_episode_churn_limit: 1_024,
}
}
fn reserve_loopback_port() -> Result<SocketAddr, Box<dyn Error>> {
let listener = std::net::TcpListener::bind("127.0.0.1:0")?;
let address = listener.local_addr()?;
drop(listener);
Ok(address)
}
fn scratch_dir(label: &str) -> Result<PathBuf, Box<dyn Error>> {
let dir = std::env::temp_dir().join(format!(
"frame-conv-{label}-{}",
uuid::Uuid::new_v4().simple()
));
fs::create_dir_all(&dir)?;
Ok(dir)
}
pub struct RunningServer {
listener: Option<ServerListener>,
address: SocketAddr,
scratch: PathBuf,
}
impl RunningServer {
pub fn start() -> Result<Self, Box<dyn Error>> {
let scratch = scratch_dir("server")?;
std::fs::create_dir_all(scratch.join("durability"))?;
let config = ServerConfig {
listen_address: "127.0.0.1:0".parse()?,
health_listen_address: reserve_loopback_port()?,
channels: vec![ChannelDef {
name: "events".to_owned(),
schema_ref: None,
durable: false,
loaded_schema: None,
}],
routing_rules: Vec::new(),
persistence_path: Some(scratch.join("durability")),
cluster: None,
auth: None,
drain_timeout_ms: 30_000,
services: ServicesConfig::default(),
limits: LimitsConfig::default(),
participant: Some(participant_config()),
websocket: None,
};
let supervisor = ConnectionSupervisor::from_config(&config)?;
let listener = ServerListener::bind(&config, supervisor)?;
let address = listener.local_addr();
Ok(Self {
listener: Some(listener),
address,
scratch,
})
}
#[must_use]
pub fn endpoint(&self) -> String {
self.address.to_string()
}
pub fn shutdown(mut self) -> Result<(), Box<dyn Error>> {
if let Some(listener) = self.listener.take() {
listener.shutdown()?;
}
fs::remove_dir_all(&self.scratch)?;
Ok(())
}
}
#[must_use]
pub fn attachment(endpoint: String) -> BusAttachment {
BusAttachment {
endpoint,
session_capacity: 1,
operation_window_millis: 10,
inbound_buffer: 16,
answer_window: Duration::from_secs(30),
}
}
pub struct FileStore {
path: PathBuf,
}
impl FileStore {
#[must_use]
pub const fn new(path: PathBuf) -> Self {
Self { path }
}
}
impl ResumeStore for FileStore {
fn persist(&mut self, canonical_state: &[u8]) -> Result<(), StoreError> {
let mut file = fs::File::create(&self.path)
.map_err(|error| StoreError::new(format!("create {}: {error}", self.path.display())))?;
file.write_all(canonical_state)
.and_then(|()| file.sync_all())
.map_err(|error| StoreError::new(format!("write {}: {error}", self.path.display())))
}
}
pub fn store_dir(label: &str) -> Result<PathBuf, Box<dyn Error>> {
scratch_dir(label)
}