1use std::fmt;
4use std::io;
5use std::net::SocketAddr;
6use std::path::PathBuf;
7use std::time::Duration;
8
9use crate::addr::AddrError;
10use crate::driver::DriverKind;
11use crate::handoff::HandoffError;
12use crate::inherit::InheritError;
13use crate::proxy::ProxyError;
14use crate::tls::TlsError;
15
16#[non_exhaustive]
34#[derive(Debug)]
35pub enum NetError {
36 Bind {
38 addr: SocketAddr,
40 source: io::Error,
42 },
43 AddrInUse {
45 addr: SocketAddr,
47 },
48 AddrNotAvailable {
50 addr: SocketAddr,
52 },
53 PermissionDenied {
55 what: &'static str,
57 },
58 NoAddresses,
60 ConnectTimeout {
62 addr: SocketAddr,
64 after: Duration,
66 },
67 ConnectionRefused {
69 addr: SocketAddr,
71 },
72 HostUnreachable {
74 addr: SocketAddr,
76 },
77 NetworkUnreachable {
79 addr: SocketAddr,
81 },
82 TimedOut {
84 kind: TimeoutKind,
86 },
87 ConnectionReset,
89 BrokenPipe,
91 Closed,
93 Killed {
95 reason: KillReason,
97 },
98 EgressDenied {
100 code: u16,
102 },
103 Refused {
105 cause: RefusalCause,
107 },
108 Limit {
110 which: LimitKind,
112 },
113 SlowPeer {
115 direction: Direction,
117 },
118 InvalidConfig {
120 field: &'static str,
122 reason: ConfigReason,
124 },
125 Unsupported {
127 feature: &'static str,
129 platform: &'static str,
131 },
132 PathTooLong {
134 max: usize,
136 },
137 StaleSocketInUse {
139 path: PathBuf,
141 },
142 PipeNameInUse {
144 name: String,
146 },
147 NotFound,
149 FileTruncated {
151 sent: u64,
153 },
154 Address(AddrError),
156 Proxy(ProxyError),
158 Tls(TlsError),
160 Inherit(InheritError),
162 Handoff(HandoffError),
164 DriverSetup {
166 driver: DriverKind,
168 source: io::Error,
170 },
171 DoorClosed,
173 ShuttingDown,
175 ForkedChild,
177 Os {
179 op: &'static str,
181 source: io::Error,
183 },
184}
185
186#[non_exhaustive]
195#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
196pub enum ErrorClass {
197 Config,
199 Resource,
201 Peer,
203 Timeout,
205 Policy,
207 Platform,
209 Bug,
211}
212
213impl NetError {
214 #[must_use]
221 pub fn class(&self) -> ErrorClass {
222 match self {
223 NetError::InvalidConfig { .. }
224 | NetError::Address(_)
225 | NetError::AddrInUse { .. }
226 | NetError::AddrNotAvailable { .. }
227 | NetError::PermissionDenied { .. }
228 | NetError::NoAddresses
229 | NetError::PathTooLong { .. }
230 | NetError::StaleSocketInUse { .. }
231 | NetError::PipeNameInUse { .. }
232 | NetError::NotFound
233 | NetError::Bind { .. } => ErrorClass::Config,
234 NetError::Limit { which } => match which {
235 LimitKind::BufferBudget | LimitKind::Descriptors | LimitKind::CompletionQueue => {
236 ErrorClass::Resource
237 }
238 _ => ErrorClass::Policy,
239 },
240 NetError::ConnectTimeout { .. } | NetError::TimedOut { .. } => ErrorClass::Timeout,
241 NetError::ConnectionRefused { .. }
242 | NetError::HostUnreachable { .. }
243 | NetError::NetworkUnreachable { .. }
244 | NetError::ConnectionReset
245 | NetError::BrokenPipe
246 | NetError::Closed
247 | NetError::FileTruncated { .. }
248 | NetError::Proxy(_)
249 | NetError::Tls(_)
250 | NetError::SlowPeer { .. } => ErrorClass::Peer,
251 NetError::Killed { .. }
252 | NetError::EgressDenied { .. }
253 | NetError::Refused { .. }
254 | NetError::DoorClosed
255 | NetError::ShuttingDown => ErrorClass::Policy,
256 NetError::Unsupported { .. } | NetError::DriverSetup { .. } => ErrorClass::Platform,
257 NetError::ForkedChild => ErrorClass::Bug,
258 NetError::Inherit(_) | NetError::Handoff(_) => ErrorClass::Config,
259 NetError::Os { source, .. } => class_of_io(source),
260 }
261 }
262
263 #[must_use]
270 pub fn is_retryable(&self) -> bool {
271 matches!(
272 self,
273 NetError::ConnectTimeout { .. }
274 | NetError::TimedOut {
275 kind: TimeoutKind::Connect
276 }
277 ) || self.class() == ErrorClass::Resource
278 }
279
280 #[must_use]
287 pub fn os_error_code(&self) -> Option<i32> {
288 match self {
289 NetError::Bind { source, .. }
290 | NetError::DriverSetup { source, .. }
291 | NetError::Os { source, .. } => source.raw_os_error(),
292 _ => None,
293 }
294 }
295
296 pub(crate) fn from_os(op: &'static str, source: io::Error) -> NetError {
298 match source.kind() {
299 io::ErrorKind::ConnectionReset | io::ErrorKind::ConnectionAborted => {
300 NetError::ConnectionReset
301 }
302 io::ErrorKind::BrokenPipe => NetError::BrokenPipe,
303 io::ErrorKind::NotConnected => NetError::Closed,
304 _ => NetError::Os { op, source },
305 }
306 }
307}
308
309fn class_of_io(e: &io::Error) -> ErrorClass {
310 match e.kind() {
311 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => ErrorClass::Timeout,
312 io::ErrorKind::OutOfMemory => ErrorClass::Resource,
313 io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => ErrorClass::Config,
314 io::ErrorKind::Unsupported => ErrorClass::Platform,
315 _ => ErrorClass::Peer,
316 }
317}
318
319impl fmt::Display for NetError {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 match self {
322 NetError::Bind { addr, source } => write!(f, "could not bind {addr}: {source}"),
323 NetError::AddrInUse { addr } => write!(
324 f,
325 "another socket is listening on {addr}; stop it or choose another port (on restart, use listener hand-off)"
326 ),
327 NetError::AddrNotAvailable { addr } => write!(
328 f,
329 "{addr} is not an address of this host (or IPv6 is disabled); bind an address this host owns"
330 ),
331 NetError::PermissionDenied { what } => write!(
332 f,
333 "permission denied: {what}; ports below 1024 need privileges: bind before dropping them, or use a high port behind a reverse proxy"
334 ),
335 NetError::NoAddresses => {
336 f.write_str("name resolution returned no address; check the host name")
337 }
338 NetError::ConnectTimeout { addr, after } => write!(
339 f,
340 "no answer from {addr} within {after:?}; check reachability or raise the connect timeout"
341 ),
342 NetError::ConnectionRefused { addr } => {
343 write!(f, "{addr} refused the connection; nothing listens there")
344 }
345 NetError::HostUnreachable { addr } => write!(f, "no route to host {addr}"),
346 NetError::NetworkUnreachable { addr } => write!(f, "no route to the network of {addr}"),
347 NetError::TimedOut { kind } => write!(
348 f,
349 "the {} deadline passed; the peer is too slow, or the limit is too low for this workload",
350 kind.as_str()
351 ),
352 NetError::ConnectionReset => f.write_str("the peer reset the connection"),
353 NetError::BrokenPipe => f.write_str("write after the peer closed the connection"),
354 NetError::Closed => f.write_str("the connection is already closed"),
355 NetError::Killed { reason } => {
356 write!(f, "the connection was killed (code {})", reason.0)
357 }
358 NetError::EgressDenied { code } => {
359 write!(
360 f,
361 "the gate denied a write (code {code}); the connection was reset"
362 )
363 }
364 NetError::Refused { cause } => {
365 write!(f, "the connection was refused: {}", cause.as_str())
366 }
367 NetError::Limit { which } => {
368 write!(
369 f,
370 "the {} limit was reached; raise it or shed load",
371 which.as_str()
372 )
373 }
374 NetError::SlowPeer { direction } => write!(
375 f,
376 "the peer did not reach the minimum transfer rate ({})",
377 direction.as_str()
378 ),
379 NetError::InvalidConfig { field, reason } => {
380 write!(f, "invalid configuration for `{field}`: {reason}")
381 }
382 NetError::Unsupported { feature, platform } => {
383 write!(f, "{feature} is not supported on {platform}")
384 }
385 NetError::PathTooLong { max } => write!(
386 f,
387 "the socket path is longer than the platform allows ({max} bytes including the terminator); use a shorter path"
388 ),
389 NetError::StaleSocketInUse { path } => write!(
390 f,
391 "{} is in use by a live listener or is not a socket; stop that listener or choose another path",
392 path.display()
393 ),
394 NetError::PipeNameInUse { name } => write!(
395 f,
396 "another process created the pipe {name} first (possible squatting); choose another name or find that process"
397 ),
398 NetError::NotFound => {
399 f.write_str("no local endpoint with that name; start the server first")
400 }
401 NetError::FileTruncated { sent } => write!(
402 f,
403 "the file ended after {sent} bytes while it was being sent; the connection was reset"
404 ),
405 NetError::Address(e) => write!(f, "{e}"),
406 NetError::Proxy(e) => write!(f, "PROXY protocol: {e}"),
407 NetError::Tls(e) => write!(f, "TLS: {e}"),
408 NetError::Inherit(e) => write!(f, "inherited listeners: {e}"),
409 NetError::Handoff(e) => write!(f, "listener hand-off: {e}"),
410 NetError::DriverSetup { driver, source } => write!(
411 f,
412 "the {driver:?} driver could not start: {source}; use DriverKind::Auto or the blocking driver"
413 ),
414 NetError::DoorClosed => f.write_str("the door is closed; call open_door first"),
415 NetError::ShuttingDown => f.write_str("the server is draining or stopped"),
416 NetError::ForkedChild => f.write_str(
417 "a server was used in a child process after fork; create servers after forking",
418 ),
419 NetError::Os { op, source } => write!(f, "{op} failed: {source}"),
420 }
421 }
422}
423
424impl std::error::Error for NetError {
425 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
426 match self {
427 NetError::Bind { source, .. }
428 | NetError::DriverSetup { source, .. }
429 | NetError::Os { source, .. } => Some(source),
430 NetError::Address(e) => Some(e),
431 NetError::Proxy(e) => Some(e),
432 NetError::Tls(e) => Some(e),
433 NetError::Inherit(e) => Some(e),
434 NetError::Handoff(e) => Some(e),
435 _ => None,
436 }
437 }
438}
439
440impl From<io::Error> for NetError {
441 fn from(e: io::Error) -> NetError {
442 match e.kind() {
443 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => NetError::TimedOut {
444 kind: TimeoutKind::Read,
445 },
446 _ => NetError::from_os("io", e),
447 }
448 }
449}
450
451impl From<AddrError> for NetError {
452 fn from(e: AddrError) -> NetError {
453 NetError::Address(e)
454 }
455}
456
457impl From<NetError> for io::Error {
458 fn from(e: NetError) -> io::Error {
459 let kind = match &e {
460 NetError::TimedOut { .. }
461 | NetError::ConnectTimeout { .. }
462 | NetError::SlowPeer { .. } => io::ErrorKind::TimedOut,
463 NetError::ConnectionReset | NetError::Killed { .. } | NetError::EgressDenied { .. } => {
464 io::ErrorKind::ConnectionReset
465 }
466 NetError::BrokenPipe => io::ErrorKind::BrokenPipe,
467 NetError::Closed => io::ErrorKind::NotConnected,
468 NetError::ConnectionRefused { .. } => io::ErrorKind::ConnectionRefused,
469 NetError::AddrInUse { .. } => io::ErrorKind::AddrInUse,
470 NetError::AddrNotAvailable { .. } => io::ErrorKind::AddrNotAvailable,
471 NetError::PermissionDenied { .. } => io::ErrorKind::PermissionDenied,
472 NetError::NotFound => io::ErrorKind::NotFound,
473 NetError::Unsupported { .. } => io::ErrorKind::Unsupported,
474 NetError::InvalidConfig { .. } | NetError::Address(_) => io::ErrorKind::InvalidInput,
475 _ => io::ErrorKind::Other,
476 };
477 io::Error::new(kind, e)
478 }
479}
480
481#[non_exhaustive]
493#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
494pub enum CloseReason {
495 PeerClosed,
497 PeerReset,
499 Idle,
501 HandshakeTimeout,
503 ReadTimeout,
505 WriteTimeout,
507 ExchangeDeadline,
509 ParkDeadline,
511 SlowPeer {
513 direction: Direction,
515 },
516 Refused(RefusalCause),
518 ProxyUntrusted,
520 ProxyMissing,
522 ProxyInvalid,
524 TlsFailed,
526 HelloRefused,
528 Killed(KillReason),
530 EgressDenied(u16),
532 Drained,
534 DrainDeadline,
536 Shed,
538 Shutdown,
540 SessionClosed,
542 SessionError,
544 SessionPanicked,
546 Limit(LimitKind),
548 Io(OsErrorClass),
550}
551
552impl CloseReason {
553 #[must_use]
559 pub fn as_str(&self) -> &'static str {
560 match self {
561 CloseReason::PeerClosed => "peer_closed",
562 CloseReason::PeerReset => "peer_reset",
563 CloseReason::Idle => "idle",
564 CloseReason::HandshakeTimeout => "handshake_timeout",
565 CloseReason::ReadTimeout => "read_timeout",
566 CloseReason::WriteTimeout => "write_timeout",
567 CloseReason::ExchangeDeadline => "exchange_deadline",
568 CloseReason::ParkDeadline => "park_deadline",
569 CloseReason::SlowPeer {
570 direction: Direction::In,
571 } => "slow_peer_in",
572 CloseReason::SlowPeer {
573 direction: Direction::Out,
574 } => "slow_peer_out",
575 CloseReason::Refused(_) => "refused",
576 CloseReason::ProxyUntrusted => "proxy_untrusted",
577 CloseReason::ProxyMissing => "proxy_missing",
578 CloseReason::ProxyInvalid => "proxy_invalid",
579 CloseReason::TlsFailed => "tls_failed",
580 CloseReason::HelloRefused => "hello_refused",
581 CloseReason::Killed(_) => "killed",
582 CloseReason::EgressDenied(_) => "egress_denied",
583 CloseReason::Drained => "drained",
584 CloseReason::DrainDeadline => "drain_deadline",
585 CloseReason::Shed => "shed",
586 CloseReason::Shutdown => "shutdown",
587 CloseReason::SessionClosed => "session_closed",
588 CloseReason::SessionError => "session_error",
589 CloseReason::SessionPanicked => "session_panicked",
590 CloseReason::Limit(_) => "limit",
591 CloseReason::Io(_) => "io",
592 }
593 }
594
595 #[must_use]
601 pub fn is_abortive(&self) -> bool {
602 !matches!(
603 self,
604 CloseReason::PeerClosed
605 | CloseReason::Idle
606 | CloseReason::Drained
607 | CloseReason::SessionClosed
608 )
609 }
610
611 pub(crate) fn index(&self) -> usize {
613 match self {
614 CloseReason::PeerClosed => 0,
615 CloseReason::PeerReset => 1,
616 CloseReason::Idle => 2,
617 CloseReason::HandshakeTimeout => 3,
618 CloseReason::ReadTimeout => 4,
619 CloseReason::WriteTimeout => 5,
620 CloseReason::ExchangeDeadline => 6,
621 CloseReason::ParkDeadline => 7,
622 CloseReason::SlowPeer { .. } => 8,
623 CloseReason::Refused(_) => 9,
624 CloseReason::ProxyUntrusted => 10,
625 CloseReason::ProxyMissing => 11,
626 CloseReason::ProxyInvalid => 12,
627 CloseReason::TlsFailed => 13,
628 CloseReason::HelloRefused => 14,
629 CloseReason::Killed(_) => 15,
630 CloseReason::EgressDenied(_) => 16,
631 CloseReason::Drained => 17,
632 CloseReason::DrainDeadline => 18,
633 CloseReason::Shed => 19,
634 CloseReason::Shutdown => 20,
635 CloseReason::SessionClosed => 21,
636 CloseReason::SessionError => 22,
637 CloseReason::SessionPanicked => 23,
638 CloseReason::Limit(_) => 24,
639 CloseReason::Io(_) => 25,
640 }
641 }
642
643 pub(crate) const COUNT: usize = 26;
645
646 pub(crate) fn for_timeout(kind: TimeoutKind) -> CloseReason {
648 match kind {
649 TimeoutKind::Handshake => CloseReason::HandshakeTimeout,
650 TimeoutKind::Idle => CloseReason::Idle,
651 TimeoutKind::Read | TimeoutKind::Connect => CloseReason::ReadTimeout,
652 TimeoutKind::Write => CloseReason::WriteTimeout,
653 TimeoutKind::Exchange => CloseReason::ExchangeDeadline,
654 TimeoutKind::Park => CloseReason::ParkDeadline,
655 TimeoutKind::Drain => CloseReason::DrainDeadline,
656 }
657 }
658}
659
660impl fmt::Display for CloseReason {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 f.write_str(self.as_str())
663 }
664}
665
666#[non_exhaustive]
672#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
673pub enum RefusalCause {
674 GlobalCap,
676 ListenerCap,
678 SourceCap,
680 LaneReserve,
682 Gate(u16),
684 Session,
686 Busy,
688 DoorClosed,
690 Shed,
692 Lockdown,
694}
695
696impl RefusalCause {
697 #[must_use]
699 pub fn as_str(&self) -> &'static str {
700 match self {
701 RefusalCause::GlobalCap => "global_cap",
702 RefusalCause::ListenerCap => "listener_cap",
703 RefusalCause::SourceCap => "source_cap",
704 RefusalCause::LaneReserve => "lane_reserve",
705 RefusalCause::Gate(_) => "gate",
706 RefusalCause::Session => "session",
707 RefusalCause::Busy => "busy",
708 RefusalCause::DoorClosed => "door_closed",
709 RefusalCause::Shed => "shed",
710 RefusalCause::Lockdown => "lockdown",
711 }
712 }
713
714 pub(crate) fn index(&self) -> usize {
715 match self {
716 RefusalCause::GlobalCap => 0,
717 RefusalCause::ListenerCap => 1,
718 RefusalCause::SourceCap => 2,
719 RefusalCause::LaneReserve => 3,
720 RefusalCause::Gate(_) => 4,
721 RefusalCause::Session => 5,
722 RefusalCause::Busy => 6,
723 RefusalCause::DoorClosed => 7,
724 RefusalCause::Shed => 8,
725 RefusalCause::Lockdown => 9,
726 }
727 }
728
729 pub(crate) const COUNT: usize = 10;
730}
731
732#[non_exhaustive]
738#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
739pub enum LimitKind {
740 Connections,
742 PerListener,
744 PerSource,
746 BufferBudget,
748 OutputBudget,
750 CompletionQueue,
752 ProxyHeader,
754 AcceptBatch,
756 ReadBuffer,
758 Amplification,
760 SourceBudget,
762 Descriptors,
764}
765
766impl LimitKind {
767 #[must_use]
769 pub fn as_str(&self) -> &'static str {
770 match self {
771 LimitKind::Connections => "connections",
772 LimitKind::PerListener => "per_listener",
773 LimitKind::PerSource => "per_source",
774 LimitKind::BufferBudget => "buffer_budget",
775 LimitKind::OutputBudget => "output_budget",
776 LimitKind::CompletionQueue => "completion_queue",
777 LimitKind::ProxyHeader => "proxy_header",
778 LimitKind::AcceptBatch => "accept_batch",
779 LimitKind::ReadBuffer => "read_buffer",
780 LimitKind::Amplification => "amplification",
781 LimitKind::SourceBudget => "source_budget",
782 LimitKind::Descriptors => "descriptors",
783 }
784 }
785}
786
787#[non_exhaustive]
793#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
794pub enum TimeoutKind {
795 Handshake,
797 Idle,
799 Read,
801 Write,
803 Exchange,
805 Park,
807 Connect,
809 Drain,
811}
812
813impl TimeoutKind {
814 #[must_use]
816 pub fn as_str(&self) -> &'static str {
817 match self {
818 TimeoutKind::Handshake => "handshake",
819 TimeoutKind::Idle => "idle",
820 TimeoutKind::Read => "read",
821 TimeoutKind::Write => "write",
822 TimeoutKind::Exchange => "exchange",
823 TimeoutKind::Park => "park",
824 TimeoutKind::Connect => "connect",
825 TimeoutKind::Drain => "drain",
826 }
827 }
828}
829
830#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
837pub struct KillReason(pub u16);
838
839#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
845pub enum Direction {
846 In,
848 Out,
850}
851
852impl Direction {
853 #[must_use]
855 pub fn as_str(&self) -> &'static str {
856 match self {
857 Direction::In => "in",
858 Direction::Out => "out",
859 }
860 }
861}
862
863#[non_exhaustive]
871#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
872pub enum OsErrorClass {
873 Reset,
875 Aborted,
877 TimedOut,
879 Refused,
881 Unreachable,
883 FileTruncated,
885 Other(i32),
887}
888
889impl OsErrorClass {
890 #[must_use]
892 pub fn from_io(e: &io::Error) -> OsErrorClass {
893 match e.kind() {
894 io::ErrorKind::ConnectionReset | io::ErrorKind::BrokenPipe => OsErrorClass::Reset,
895 io::ErrorKind::ConnectionAborted => OsErrorClass::Aborted,
896 io::ErrorKind::TimedOut => OsErrorClass::TimedOut,
897 io::ErrorKind::ConnectionRefused => OsErrorClass::Refused,
898 io::ErrorKind::HostUnreachable | io::ErrorKind::NetworkUnreachable => {
899 OsErrorClass::Unreachable
900 }
901 _ => OsErrorClass::Other(e.raw_os_error().unwrap_or(0)),
902 }
903 }
904}
905
906#[non_exhaustive]
913#[derive(Clone, Copy, Debug, PartialEq, Eq)]
914pub enum ConfigReason {
915 Zero,
917 TooLarge {
919 max: u64,
921 },
922 TooSmall {
924 min: u64,
926 },
927 AboveGlobal,
929 BelowTick,
931 WindowBelowCheck,
933 BudgetBelowWorkers {
935 need: u64,
937 },
938 DescriptorLimit {
940 have: u64,
942 need: u64,
944 },
945 Missing,
947 NotListening,
949 Inconsistent,
951}
952
953impl fmt::Display for ConfigReason {
954 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955 match self {
956 ConfigReason::Zero => f.write_str("zero is not allowed here"),
957 ConfigReason::TooLarge { max } => write!(f, "above the maximum of {max}"),
958 ConfigReason::TooSmall { min } => write!(f, "below the minimum of {min}"),
959 ConfigReason::AboveGlobal => f.write_str("above the global connection cap"),
960 ConfigReason::BelowTick => f.write_str("shorter than one timer tick"),
961 ConfigReason::WindowBelowCheck => {
962 f.write_str("the minimum-rate window is shorter than its check interval")
963 }
964 ConfigReason::BudgetBelowWorkers { need } => write!(
965 f,
966 "the buffer budget must be at least {need} bytes (workers x largest class x 2)"
967 ),
968 ConfigReason::DescriptorLimit { have, need } => write!(
969 f,
970 "needs {need} descriptors but the process limit is {have}; raise the limit or lower the caps"
971 ),
972 ConfigReason::Missing => f.write_str("a required part is not set"),
973 ConfigReason::NotListening => {
974 f.write_str("the socket is not a listening stream socket")
975 }
976 ConfigReason::Inconsistent => f.write_str("values contradict each other"),
977 }
978 }
979}
980
981#[cfg(test)]
982mod tests {
983 use super::*;
984
985 #[test]
986 fn test_close_reason_index_covers_every_variant_once() {
987 let all = [
988 CloseReason::PeerClosed,
989 CloseReason::PeerReset,
990 CloseReason::Idle,
991 CloseReason::HandshakeTimeout,
992 CloseReason::ReadTimeout,
993 CloseReason::WriteTimeout,
994 CloseReason::ExchangeDeadline,
995 CloseReason::ParkDeadline,
996 CloseReason::SlowPeer {
997 direction: Direction::Out,
998 },
999 CloseReason::Refused(RefusalCause::Busy),
1000 CloseReason::ProxyUntrusted,
1001 CloseReason::ProxyMissing,
1002 CloseReason::ProxyInvalid,
1003 CloseReason::TlsFailed,
1004 CloseReason::HelloRefused,
1005 CloseReason::Killed(KillReason(1)),
1006 CloseReason::EgressDenied(1),
1007 CloseReason::Drained,
1008 CloseReason::DrainDeadline,
1009 CloseReason::Shed,
1010 CloseReason::Shutdown,
1011 CloseReason::SessionClosed,
1012 CloseReason::SessionError,
1013 CloseReason::SessionPanicked,
1014 CloseReason::Limit(LimitKind::ReadBuffer),
1015 CloseReason::Io(OsErrorClass::Reset),
1016 ];
1017 let mut seen = [false; CloseReason::COUNT];
1018 for r in all {
1019 assert!(!seen[r.index()], "duplicate index for {r:?}");
1020 seen[r.index()] = true;
1021 }
1022 assert!(seen.iter().all(|s| *s));
1023 }
1024
1025 #[test]
1026 fn test_close_reason_size_is_eight_bytes() {
1027 assert!(std::mem::size_of::<CloseReason>() <= 8);
1028 }
1029
1030 #[test]
1031 fn test_close_style_graceful_reasons_are_not_abortive() {
1032 for r in [
1033 CloseReason::PeerClosed,
1034 CloseReason::Idle,
1035 CloseReason::Drained,
1036 CloseReason::SessionClosed,
1037 ] {
1038 assert!(!r.is_abortive());
1039 }
1040 assert!(CloseReason::DrainDeadline.is_abortive());
1041 assert!(CloseReason::Killed(KillReason(0)).is_abortive());
1042 }
1043
1044 #[test]
1045 fn test_from_io_timed_out_maps_to_read_timeout() {
1046 let e: NetError = io::Error::from(io::ErrorKind::TimedOut).into();
1047 assert!(matches!(
1048 e,
1049 NetError::TimedOut {
1050 kind: TimeoutKind::Read
1051 }
1052 ));
1053 }
1054
1055 #[test]
1056 fn test_from_io_reset_maps_to_connection_reset() {
1057 let e: NetError = io::Error::from(io::ErrorKind::ConnectionReset).into();
1058 assert!(matches!(e, NetError::ConnectionReset));
1059 assert_eq!(e.class(), ErrorClass::Peer);
1060 }
1061
1062 #[test]
1063 fn test_into_io_keeps_kind_and_source() {
1064 let io_err: io::Error = NetError::BrokenPipe.into();
1065 assert_eq!(io_err.kind(), io::ErrorKind::BrokenPipe);
1066 assert!(io_err.get_ref().is_some());
1067 }
1068
1069 #[test]
1070 fn test_retryable_connect_timeout_true_reset_false() {
1071 let addr: SocketAddr = "127.0.0.1:1".parse().unwrap();
1072 assert!(
1073 NetError::ConnectTimeout {
1074 addr,
1075 after: Duration::from_secs(1)
1076 }
1077 .is_retryable()
1078 );
1079 assert!(!NetError::ConnectionReset.is_retryable());
1080 assert!(
1081 NetError::Limit {
1082 which: LimitKind::BufferBudget
1083 }
1084 .is_retryable()
1085 );
1086 }
1087
1088 #[test]
1089 fn test_display_says_what_to_do() {
1090 let addr: SocketAddr = "0.0.0.0:443".parse().unwrap();
1091 let text = NetError::AddrInUse { addr }.to_string();
1092 assert!(text.contains("0.0.0.0:443"));
1093 assert!(text.contains("hand-off"));
1094 }
1095
1096 #[test]
1097 fn test_os_error_code_present_only_for_os_errors() {
1098 let e = NetError::Os {
1099 op: "x",
1100 source: io::Error::from_raw_os_error(5),
1101 };
1102 assert_eq!(e.os_error_code(), Some(5));
1103 assert_eq!(NetError::NotFound.os_error_code(), None);
1104 }
1105
1106 #[test]
1107 fn test_timeout_kind_maps_to_close_reason() {
1108 assert_eq!(
1109 CloseReason::for_timeout(TimeoutKind::Idle),
1110 CloseReason::Idle
1111 );
1112 assert_eq!(
1113 CloseReason::for_timeout(TimeoutKind::Write),
1114 CloseReason::WriteTimeout
1115 );
1116 }
1117}