#![allow(clippy::module_name_repetitions)]
use std::{io, sync::Arc};
use thiserror::Error;
use crate::observability::{ObservabilityError, ObservabilitySampleRate};
use crate::proactor::{BufferRingError, ProactorConfig, ProactorError, ProactorSetupFlags};
use crate::tls::TlsError;
use crate::ws::{WsConfig, WsError};
pub const DEFAULT_BUF_RING_SLOT_SIZE: u32 = 4 * 1024;
pub const DEFAULT_BUF_RING_ENTRIES: u16 = 256;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum State {
Init,
Connecting,
TlsHandshake,
WsHandshake,
Open,
Closing,
Closed,
}
#[derive(Debug, Error)]
pub enum ConnectionError {
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("dns resolution returned no addresses for {0}")]
DnsEmpty(String),
#[error("proactor: {0}")]
Proactor(#[from] ProactorError),
#[error("buf ring: {0}")]
BufRing(#[from] BufferRingError),
#[error("tls: {0}")]
Tls(#[from] TlsError),
#[error("ws: {0}")]
Ws(#[from] WsError),
#[error("observability: {0}")]
Observability(#[from] ObservabilityError),
#[error("operation not allowed in state {0:?}")]
InvalidState(State),
#[error("connect failed: {0}")]
ConnectFailed(#[source] io::Error),
#[error("recv failed: {0}")]
RecvFailed(#[source] io::Error),
#[error("send failed: {0}")]
SendFailed(#[source] io::Error),
#[error("peer closed connection")]
PeerClosed,
#[error("CQE returned unknown OpKind: raw user_data = 0x{0:016x}")]
UnknownOpKind(u64),
#[error("pool {0} id space exhausted; restart or implement id reuse")]
IdSpaceExhausted(&'static str),
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct IngressStats {
pub recv_data_cqes: u64,
pub recv_bytes: u64,
pub recv_multishot_rearms: u64,
pub recv_ring_exhaustions: u64,
pub plain_recv_batches: u64,
pub plain_recv_batch_cqes: u64,
pub plain_recv_copied_batches: u64,
pub plain_recv_copied_bytes: u64,
pub plaintext_chunks: u64,
pub plaintext_bytes: u64,
pub ws_data_drains: u64,
pub ws_data_drain_skips: u64,
pub ws_data_events: u64,
pub ws_text_events: u64,
pub ws_binary_events: u64,
}
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub host: String,
pub port: u16,
pub path: String,
pub use_tls: bool,
pub tls_config: Option<Arc<rustls::ClientConfig>>,
pub proactor: ProactorConfig,
pub conn_id: u32,
pub bgid: u16,
pub buf_ring_slot_size: u32,
pub buf_ring_entries: u16,
pub ws_config: Option<WsConfig>,
pub send_buffer_initial_capacity: Option<usize>,
pub tls_pending_out_initial_capacity: Option<usize>,
pub plain_recv_batch_copy_max_bytes: usize,
pub track_ingress_stats: bool,
pub observability_sample_rate: ObservabilitySampleRate,
pub record_observability_histograms: bool,
}
impl ConnectionConfig {
#[must_use]
pub fn new(host: impl Into<String>, port: u16, path: impl Into<String>) -> Self {
Self {
host: host.into(),
port,
path: path.into(),
use_tls: true,
tls_config: None,
proactor: ProactorConfig::default(),
conn_id: 0,
bgid: 0,
buf_ring_slot_size: DEFAULT_BUF_RING_SLOT_SIZE,
buf_ring_entries: DEFAULT_BUF_RING_ENTRIES,
ws_config: None,
send_buffer_initial_capacity: None,
tls_pending_out_initial_capacity: None,
plain_recv_batch_copy_max_bytes: 0,
track_ingress_stats: false,
observability_sample_rate: ObservabilitySampleRate::always(),
record_observability_histograms: false,
}
}
#[must_use]
pub const fn with_tls(mut self, on: bool) -> Self {
self.use_tls = on;
self
}
#[must_use]
pub fn with_tls_config(mut self, config: Arc<rustls::ClientConfig>) -> Self {
self.tls_config = Some(config);
self
}
#[must_use]
pub const fn with_proactor(mut self, proactor: ProactorConfig) -> Self {
self.proactor = proactor;
self
}
#[must_use]
pub const fn with_proactor_entries(mut self, entries: u32) -> Self {
self.proactor.sq_entries = entries;
self
}
#[must_use]
pub const fn with_sq_entries(mut self, entries: u32) -> Self {
self.proactor.sq_entries = entries;
self
}
#[must_use]
pub const fn with_cq_entries(mut self, entries: u32) -> Self {
self.proactor.cq_entries = Some(entries);
self
}
#[must_use]
pub const fn with_proactor_setup_flags(mut self, flags: ProactorSetupFlags) -> Self {
self.proactor.setup_flags = flags;
self
}
#[must_use]
pub const fn with_buf_ring(mut self, slot_size: u32, entries: u16) -> Self {
debug_assert!(slot_size > 0, "slot_size must be > 0");
debug_assert!(
entries > 0 && entries.is_power_of_two(),
"entries must be non-zero power of 2"
);
self.buf_ring_slot_size = slot_size;
self.buf_ring_entries = entries;
self
}
#[must_use]
pub fn with_ws_config(mut self, config: WsConfig) -> Self {
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_ws_limits(mut self, max_message_size: usize, max_frame_payload: u64) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.max_message_size = max_message_size;
config.max_frame_payload = max_frame_payload;
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_ws_recv_buffer_capacity(mut self, bytes: usize) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.initial_recv_buffer_capacity = Some(bytes);
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_ws_message_buffer_capacity(mut self, bytes: usize) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.initial_message_buffer_capacity = Some(bytes);
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_ws_tx_buffer_capacity(mut self, bytes: usize) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.initial_tx_buffer_capacity = Some(bytes);
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_ws_buffer_capacities(
mut self,
recv_bytes: usize,
message_bytes: usize,
tx_bytes: usize,
) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.initial_recv_buffer_capacity = Some(recv_bytes);
config.initial_message_buffer_capacity = Some(message_bytes);
config.initial_tx_buffer_capacity = Some(tx_bytes);
self.ws_config = Some(config);
self
}
#[must_use]
pub fn with_auto_pong(mut self, on: bool) -> Self {
let mut config = self
.ws_config
.take()
.unwrap_or_else(|| WsConfig::new(self.host.clone(), self.path.clone()));
config.auto_pong = on;
self.ws_config = Some(config);
self
}
#[must_use]
pub const fn with_send_buffer_capacity(mut self, bytes: usize) -> Self {
self.send_buffer_initial_capacity = Some(bytes);
self
}
#[must_use]
pub const fn with_tls_pending_out_capacity(mut self, bytes: usize) -> Self {
self.tls_pending_out_initial_capacity = Some(bytes);
self
}
#[must_use]
pub const fn with_connection_buffer_capacities(
mut self,
send_bytes: usize,
tls_pending_out_bytes: usize,
) -> Self {
self.send_buffer_initial_capacity = Some(send_bytes);
self.tls_pending_out_initial_capacity = Some(tls_pending_out_bytes);
self
}
#[must_use]
pub const fn with_plain_recv_batch_copy_max_bytes(mut self, bytes: usize) -> Self {
self.plain_recv_batch_copy_max_bytes = bytes;
self
}
#[must_use]
pub const fn with_ingress_stats(mut self, on: bool) -> Self {
self.track_ingress_stats = on;
self
}
#[must_use]
pub const fn with_observability_sample_rate_bps(mut self, basis_points: u16) -> Self {
self.observability_sample_rate = ObservabilitySampleRate::from_basis_points(basis_points);
self
}
#[must_use]
pub const fn with_observability_histograms(mut self, on: bool) -> Self {
self.record_observability_histograms = on;
self
}
}