1use std::net::SocketAddr;
14use thiserror::Error;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PortBinding {
19 OsAssigned,
31
32 Explicit(u16),
41
42 Range(u16, u16),
51}
52
53impl Default for PortBinding {
54 fn default() -> Self {
55 Self::OsAssigned
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum IpMode {
62 IPv4Only,
69
70 IPv6Only,
72
73 DualStack,
78
79 DualStackSeparate {
83 ipv4_port: PortBinding,
85 ipv6_port: PortBinding,
87 },
88}
89
90impl Default for IpMode {
91 fn default() -> Self {
92 Self::IPv4Only
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Default)]
98pub struct SocketOptions {
99 pub send_buffer_size: Option<usize>,
101 pub recv_buffer_size: Option<usize>,
103 pub reuse_address: bool,
105 pub reuse_port: bool,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum PortRetryBehavior {
112 FailFast,
114 FallbackToOsAssigned,
116 TryNext,
118}
119
120impl Default for PortRetryBehavior {
121 fn default() -> Self {
122 Self::FailFast
123 }
124}
125
126#[derive(Debug, Clone)]
163pub struct EndpointPortConfig {
164 pub port: PortBinding,
166 pub ip_mode: IpMode,
168 pub socket_options: SocketOptions,
170 pub retry_behavior: PortRetryBehavior,
172}
173
174impl Default for EndpointPortConfig {
175 fn default() -> Self {
176 Self {
177 port: PortBinding::OsAssigned,
179 ip_mode: IpMode::IPv4Only,
181 socket_options: SocketOptions::default(),
182 retry_behavior: PortRetryBehavior::FailFast,
183 }
184 }
185}
186
187#[derive(Debug, Error, Clone, PartialEq, Eq)]
189pub enum EndpointConfigError {
190 #[error("Port {0} is already in use. Try using PortBinding::OsAssigned to let the OS choose.")]
192 PortInUse(u16),
193
194 #[error("Invalid port number: {0}. Port must be in range 0-65535.")]
196 InvalidPort(u32),
197
198 #[error("Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions.")]
200 PermissionDenied(u16),
201
202 #[error("No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned.")]
204 NoPortInRange(u16, u16),
205
206 #[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
208 DualStackNotSupported,
209
210 #[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
212 Ipv6NotAvailable,
213
214 #[error("Failed to bind socket: {0}")]
216 BindFailed(String),
217
218 #[error("Invalid configuration: {0}")]
220 InvalidConfig(String),
221
222 #[error("IO error: {0}")]
224 IoError(String),
225}
226
227impl From<std::io::Error> for EndpointConfigError {
228 fn from(err: std::io::Error) -> Self {
229 use std::io::ErrorKind;
230 match err.kind() {
231 ErrorKind::AddrInUse => {
232 Self::BindFailed(err.to_string())
234 }
235 ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
236 ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
237 _ => Self::IoError(err.to_string()),
238 }
239 }
240}
241
242pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
244
245#[derive(Debug, Clone)]
247pub struct BoundSocket {
248 pub addrs: Vec<SocketAddr>,
250 pub config: EndpointPortConfig,
252}
253
254impl BoundSocket {
255 pub fn primary_addr(&self) -> Option<SocketAddr> {
257 self.addrs.first().copied()
258 }
259
260 pub fn all_addrs(&self) -> &[SocketAddr] {
262 &self.addrs
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_port_binding_default() {
272 let port = PortBinding::default();
273 assert_eq!(port, PortBinding::OsAssigned);
274 }
275
276 #[test]
277 fn test_port_binding_explicit() {
278 let port = PortBinding::Explicit(9000);
279 match port {
280 PortBinding::Explicit(9000) => {}
281 _ => panic!("Expected Explicit(9000)"),
282 }
283 }
284
285 #[test]
286 fn test_port_binding_range() {
287 let port = PortBinding::Range(9000, 9010);
288 match port {
289 PortBinding::Range(9000, 9010) => {}
290 _ => panic!("Expected Range(9000, 9010)"),
291 }
292 }
293
294 #[test]
295 fn test_ip_mode_default() {
296 let mode = IpMode::default();
297 assert_eq!(mode, IpMode::IPv4Only);
298 }
299
300 #[test]
301 fn test_ip_mode_ipv4_only() {
302 let mode = IpMode::IPv4Only;
303 match mode {
304 IpMode::IPv4Only => {}
305 _ => panic!("Expected IPv4Only"),
306 }
307 }
308
309 #[test]
310 fn test_ip_mode_dual_stack_separate() {
311 let mode = IpMode::DualStackSeparate {
312 ipv4_port: PortBinding::Explicit(9000),
313 ipv6_port: PortBinding::Explicit(9001),
314 };
315 match mode {
316 IpMode::DualStackSeparate { ipv4_port, ipv6_port } => {
317 assert_eq!(ipv4_port, PortBinding::Explicit(9000));
318 assert_eq!(ipv6_port, PortBinding::Explicit(9001));
319 }
320 _ => panic!("Expected DualStackSeparate"),
321 }
322 }
323
324 #[test]
325 fn test_socket_options_default() {
326 let opts = SocketOptions::default();
327 assert_eq!(opts.send_buffer_size, None);
328 assert_eq!(opts.recv_buffer_size, None);
329 assert!(!opts.reuse_address);
330 assert!(!opts.reuse_port);
331 }
332
333 #[test]
334 fn test_retry_behavior_default() {
335 let behavior = PortRetryBehavior::default();
336 assert_eq!(behavior, PortRetryBehavior::FailFast);
337 }
338
339 #[test]
340 fn test_endpoint_port_config_default() {
341 let config = EndpointPortConfig::default();
342 assert_eq!(config.port, PortBinding::OsAssigned);
343 assert_eq!(config.ip_mode, IpMode::IPv4Only);
344 assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
345 }
346
347 #[test]
348 fn test_endpoint_config_error_display() {
349 let err = EndpointConfigError::PortInUse(9000);
350 assert!(err
351 .to_string()
352 .contains("Port 9000 is already in use"));
353
354 let err = EndpointConfigError::InvalidPort(70000);
355 assert!(err.to_string().contains("Invalid port number"));
356
357 let err = EndpointConfigError::PermissionDenied(80);
358 assert!(err.to_string().contains("privileged port"));
359 }
360
361 #[test]
362 fn test_bound_socket() {
363 let config = EndpointPortConfig::default();
364 let addrs = vec![
365 "127.0.0.1:9000".parse().expect("valid address"),
366 "127.0.0.1:9001".parse().expect("valid address"),
367 ];
368 let bound = BoundSocket {
369 addrs: addrs.clone(),
370 config,
371 };
372
373 assert_eq!(bound.primary_addr(), Some(addrs[0]));
374 assert_eq!(bound.all_addrs(), &addrs[..]);
375 }
376}