use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum PortBinding {
#[default]
OsAssigned,
Explicit(u16),
Range(u16, u16),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum IpMode {
IPv4Only,
IPv6Only,
#[default]
DualStack,
DualStackSeparate {
ipv4_port: PortBinding,
ipv6_port: PortBinding,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SocketOptions {
pub send_buffer_size: Option<usize>,
pub recv_buffer_size: Option<usize>,
pub reuse_address: bool,
pub reuse_port: bool,
}
impl SocketOptions {
#[must_use]
pub fn with_platform_defaults() -> Self {
Self {
send_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
recv_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
reuse_address: false,
reuse_port: false,
}
}
#[must_use]
pub fn with_pqc_defaults() -> Self {
Self {
send_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
recv_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
reuse_address: false,
reuse_port: false,
}
}
#[must_use]
pub fn with_buffer_sizes(send: usize, recv: usize) -> Self {
Self {
send_buffer_size: Some(send),
recv_buffer_size: Some(recv),
reuse_address: false,
reuse_port: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PortRetryBehavior {
#[default]
FailFast,
FallbackToOsAssigned,
TryNext,
}
#[derive(Debug, Clone)]
pub struct EndpointPortConfig {
pub port: PortBinding,
pub ip_mode: IpMode,
pub socket_options: SocketOptions,
pub retry_behavior: PortRetryBehavior,
}
impl Default for EndpointPortConfig {
fn default() -> Self {
Self {
port: PortBinding::OsAssigned,
ip_mode: IpMode::DualStack,
socket_options: SocketOptions::default(),
retry_behavior: PortRetryBehavior::FailFast,
}
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum EndpointConfigError {
#[error("Port {0} is already in use. Try using PortBinding::OsAssigned to let the OS choose.")]
PortInUse(u16),
#[error("Invalid port number: {0}. Port must be in range 0-65535.")]
InvalidPort(u32),
#[error(
"Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions."
)]
PermissionDenied(u16),
#[error(
"No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned."
)]
NoPortInRange(u16, u16),
#[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
DualStackNotSupported,
#[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
Ipv6NotAvailable,
#[error("Failed to bind socket: {0}")]
BindFailed(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("IO error: {0}")]
IoError(String),
}
impl From<std::io::Error> for EndpointConfigError {
fn from(err: std::io::Error) -> Self {
use std::io::ErrorKind;
match err.kind() {
ErrorKind::AddrInUse => {
Self::BindFailed(err.to_string())
}
ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
_ => Self::IoError(err.to_string()),
}
}
}
pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
pub mod buffer_defaults {
pub const MIN_BUFFER_SIZE: usize = 64 * 1024;
pub const CLASSICAL_BUFFER_SIZE: usize = 256 * 1024;
pub const PQC_BUFFER_SIZE: usize = 4 * 1024 * 1024;
#[cfg(target_os = "windows")]
pub const PLATFORM_DEFAULT: usize = 256 * 1024;
#[cfg(target_os = "linux")]
pub const PLATFORM_DEFAULT: usize = 2 * 1024 * 1024;
#[cfg(target_os = "macos")]
pub const PLATFORM_DEFAULT: usize = 512 * 1024;
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
pub const PLATFORM_DEFAULT: usize = 256 * 1024;
#[must_use]
pub fn recommended_buffer_size(pqc_enabled: bool) -> usize {
if pqc_enabled {
PQC_BUFFER_SIZE
} else {
PLATFORM_DEFAULT.max(CLASSICAL_BUFFER_SIZE)
}
}
}
#[derive(Debug, Clone)]
pub struct BoundSocket {
pub addrs: Vec<SocketAddr>,
pub config: EndpointPortConfig,
}
impl BoundSocket {
pub fn primary_addr(&self) -> Option<SocketAddr> {
self.addrs.first().copied()
}
pub fn all_addrs(&self) -> &[SocketAddr] {
&self.addrs
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_port_binding_default() {
let port = PortBinding::default();
assert_eq!(port, PortBinding::OsAssigned);
}
#[test]
fn test_port_binding_explicit() {
let port = PortBinding::Explicit(9000);
match port {
PortBinding::Explicit(9000) => {}
_ => panic!("Expected Explicit(9000)"),
}
}
#[test]
fn test_port_binding_range() {
let port = PortBinding::Range(9000, 9010);
match port {
PortBinding::Range(9000, 9010) => {}
_ => panic!("Expected Range(9000, 9010)"),
}
}
#[test]
fn test_ip_mode_default() {
let mode = IpMode::default();
assert_eq!(mode, IpMode::DualStack); }
#[test]
fn test_ip_mode_ipv4_only() {
let mode = IpMode::IPv4Only;
match mode {
IpMode::IPv4Only => {}
_ => panic!("Expected IPv4Only"),
}
}
#[test]
fn test_ip_mode_dual_stack_separate() {
let mode = IpMode::DualStackSeparate {
ipv4_port: PortBinding::Explicit(9000),
ipv6_port: PortBinding::Explicit(9001),
};
match mode {
IpMode::DualStackSeparate {
ipv4_port,
ipv6_port,
} => {
assert_eq!(ipv4_port, PortBinding::Explicit(9000));
assert_eq!(ipv6_port, PortBinding::Explicit(9001));
}
_ => panic!("Expected DualStackSeparate"),
}
}
#[test]
fn test_socket_options_default() {
let opts = SocketOptions::default();
assert_eq!(opts.send_buffer_size, None);
assert_eq!(opts.recv_buffer_size, None);
assert!(!opts.reuse_address);
assert!(!opts.reuse_port);
}
#[test]
fn test_retry_behavior_default() {
let behavior = PortRetryBehavior::default();
assert_eq!(behavior, PortRetryBehavior::FailFast);
}
#[test]
fn test_endpoint_port_config_default() {
let config = EndpointPortConfig::default();
assert_eq!(config.port, PortBinding::OsAssigned);
assert_eq!(config.ip_mode, IpMode::DualStack); assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
}
#[test]
fn test_endpoint_config_error_display() {
let err = EndpointConfigError::PortInUse(9000);
assert!(err.to_string().contains("Port 9000 is already in use"));
let err = EndpointConfigError::InvalidPort(70000);
assert!(err.to_string().contains("Invalid port number"));
let err = EndpointConfigError::PermissionDenied(80);
assert!(err.to_string().contains("privileged port"));
}
#[test]
fn test_bound_socket() {
let config = EndpointPortConfig::default();
let addrs = vec![
"127.0.0.1:9000".parse().expect("valid address"),
"127.0.0.1:9001".parse().expect("valid address"),
];
let bound = BoundSocket {
addrs: addrs.clone(),
config,
};
assert_eq!(bound.primary_addr(), Some(addrs[0]));
assert_eq!(bound.all_addrs(), &addrs[..]);
}
}