1use std::net::SocketAddr;
14use thiserror::Error;
15
16#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub enum PortBinding {
19 #[default]
31 OsAssigned,
32
33 Explicit(u16),
42
43 Range(u16, u16),
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Default)]
56pub enum IpMode {
57 #[default]
64 IPv4Only,
65
66 IPv6Only,
68
69 DualStack,
74
75 DualStackSeparate {
79 ipv4_port: PortBinding,
81 ipv6_port: PortBinding,
83 },
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct SocketOptions {
89 pub send_buffer_size: Option<usize>,
91 pub recv_buffer_size: Option<usize>,
93 pub reuse_address: bool,
95 pub reuse_port: bool,
97}
98
99impl SocketOptions {
100 #[must_use]
106 pub fn with_platform_defaults() -> Self {
107 Self {
108 send_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
109 recv_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
110 reuse_address: false,
111 reuse_port: false,
112 }
113 }
114
115 #[must_use]
120 pub fn with_pqc_defaults() -> Self {
121 Self {
122 send_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
123 recv_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
124 reuse_address: false,
125 reuse_port: false,
126 }
127 }
128
129 #[must_use]
131 pub fn with_buffer_sizes(send: usize, recv: usize) -> Self {
132 Self {
133 send_buffer_size: Some(send),
134 recv_buffer_size: Some(recv),
135 reuse_address: false,
136 reuse_port: false,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
143pub enum PortRetryBehavior {
144 #[default]
146 FailFast,
147 FallbackToOsAssigned,
149 TryNext,
151}
152
153#[derive(Debug, Clone)]
190pub struct EndpointPortConfig {
191 pub port: PortBinding,
193 pub ip_mode: IpMode,
195 pub socket_options: SocketOptions,
197 pub retry_behavior: PortRetryBehavior,
199}
200
201impl Default for EndpointPortConfig {
202 fn default() -> Self {
203 Self {
204 port: PortBinding::OsAssigned,
206 ip_mode: IpMode::IPv4Only,
208 socket_options: SocketOptions::default(),
209 retry_behavior: PortRetryBehavior::FailFast,
210 }
211 }
212}
213
214#[derive(Debug, Error, Clone, PartialEq, Eq)]
216pub enum EndpointConfigError {
217 #[error("Port {0} is already in use. Try using PortBinding::OsAssigned to let the OS choose.")]
219 PortInUse(u16),
220
221 #[error("Invalid port number: {0}. Port must be in range 0-65535.")]
223 InvalidPort(u32),
224
225 #[error(
227 "Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions."
228 )]
229 PermissionDenied(u16),
230
231 #[error(
233 "No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned."
234 )]
235 NoPortInRange(u16, u16),
236
237 #[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
239 DualStackNotSupported,
240
241 #[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
243 Ipv6NotAvailable,
244
245 #[error("Failed to bind socket: {0}")]
247 BindFailed(String),
248
249 #[error("Invalid configuration: {0}")]
251 InvalidConfig(String),
252
253 #[error("IO error: {0}")]
255 IoError(String),
256}
257
258impl From<std::io::Error> for EndpointConfigError {
259 fn from(err: std::io::Error) -> Self {
260 use std::io::ErrorKind;
261 match err.kind() {
262 ErrorKind::AddrInUse => {
263 Self::BindFailed(err.to_string())
265 }
266 ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
267 ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
268 _ => Self::IoError(err.to_string()),
269 }
270 }
271}
272
273pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
275
276pub mod buffer_defaults {
281 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")]
293 pub const PLATFORM_DEFAULT: usize = 256 * 1024; #[cfg(target_os = "linux")]
297 pub const PLATFORM_DEFAULT: usize = 2 * 1024 * 1024; #[cfg(target_os = "macos")]
301 pub const PLATFORM_DEFAULT: usize = 512 * 1024; #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
305 pub const PLATFORM_DEFAULT: usize = 256 * 1024; #[must_use]
312 pub fn recommended_buffer_size(pqc_enabled: bool) -> usize {
313 if pqc_enabled {
314 PQC_BUFFER_SIZE
315 } else {
316 PLATFORM_DEFAULT.max(CLASSICAL_BUFFER_SIZE)
317 }
318 }
319}
320
321#[derive(Debug, Clone)]
323pub struct BoundSocket {
324 pub addrs: Vec<SocketAddr>,
326 pub config: EndpointPortConfig,
328}
329
330impl BoundSocket {
331 pub fn primary_addr(&self) -> Option<SocketAddr> {
333 self.addrs.first().copied()
334 }
335
336 pub fn all_addrs(&self) -> &[SocketAddr] {
338 &self.addrs
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_port_binding_default() {
348 let port = PortBinding::default();
349 assert_eq!(port, PortBinding::OsAssigned);
350 }
351
352 #[test]
353 fn test_port_binding_explicit() {
354 let port = PortBinding::Explicit(9000);
355 match port {
356 PortBinding::Explicit(9000) => {}
357 _ => panic!("Expected Explicit(9000)"),
358 }
359 }
360
361 #[test]
362 fn test_port_binding_range() {
363 let port = PortBinding::Range(9000, 9010);
364 match port {
365 PortBinding::Range(9000, 9010) => {}
366 _ => panic!("Expected Range(9000, 9010)"),
367 }
368 }
369
370 #[test]
371 fn test_ip_mode_default() {
372 let mode = IpMode::default();
373 assert_eq!(mode, IpMode::IPv4Only);
374 }
375
376 #[test]
377 fn test_ip_mode_ipv4_only() {
378 let mode = IpMode::IPv4Only;
379 match mode {
380 IpMode::IPv4Only => {}
381 _ => panic!("Expected IPv4Only"),
382 }
383 }
384
385 #[test]
386 fn test_ip_mode_dual_stack_separate() {
387 let mode = IpMode::DualStackSeparate {
388 ipv4_port: PortBinding::Explicit(9000),
389 ipv6_port: PortBinding::Explicit(9001),
390 };
391 match mode {
392 IpMode::DualStackSeparate {
393 ipv4_port,
394 ipv6_port,
395 } => {
396 assert_eq!(ipv4_port, PortBinding::Explicit(9000));
397 assert_eq!(ipv6_port, PortBinding::Explicit(9001));
398 }
399 _ => panic!("Expected DualStackSeparate"),
400 }
401 }
402
403 #[test]
404 fn test_socket_options_default() {
405 let opts = SocketOptions::default();
406 assert_eq!(opts.send_buffer_size, None);
407 assert_eq!(opts.recv_buffer_size, None);
408 assert!(!opts.reuse_address);
409 assert!(!opts.reuse_port);
410 }
411
412 #[test]
413 fn test_retry_behavior_default() {
414 let behavior = PortRetryBehavior::default();
415 assert_eq!(behavior, PortRetryBehavior::FailFast);
416 }
417
418 #[test]
419 fn test_endpoint_port_config_default() {
420 let config = EndpointPortConfig::default();
421 assert_eq!(config.port, PortBinding::OsAssigned);
422 assert_eq!(config.ip_mode, IpMode::IPv4Only);
423 assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
424 }
425
426 #[test]
427 fn test_endpoint_config_error_display() {
428 let err = EndpointConfigError::PortInUse(9000);
429 assert!(err.to_string().contains("Port 9000 is already in use"));
430
431 let err = EndpointConfigError::InvalidPort(70000);
432 assert!(err.to_string().contains("Invalid port number"));
433
434 let err = EndpointConfigError::PermissionDenied(80);
435 assert!(err.to_string().contains("privileged port"));
436 }
437
438 #[test]
439 fn test_bound_socket() {
440 let config = EndpointPortConfig::default();
441 let addrs = vec![
442 "127.0.0.1:9000".parse().expect("valid address"),
443 "127.0.0.1:9001".parse().expect("valid address"),
444 ];
445 let bound = BoundSocket {
446 addrs: addrs.clone(),
447 config,
448 };
449
450 assert_eq!(bound.primary_addr(), Some(addrs[0]));
451 assert_eq!(bound.all_addrs(), &addrs[..]);
452 }
453}