Skip to main content

irtt_client/
error.rs

1use std::{collections::TryReserveError, io, time::Duration};
2
3use thiserror::Error;
4
5/// Failure reported while configuring, opening, running, receiving, closing, or
6/// joining an IRTT client session.
7///
8/// Configuration validation errors usually require changing
9/// [`ClientConfig`](crate::ClientConfig) or related options before retrying.
10/// Network, protocol, and runtime errors may be transient, but can also reflect
11/// peer incompatibility or an invalid session state.
12#[derive(Debug, Error)]
13#[non_exhaustive]
14pub enum ClientError {
15    /// A Tokio-backed client operation was polled without a current runtime.
16    #[cfg(feature = "tokio")]
17    #[error("Tokio AsyncClient requires a current Tokio runtime")]
18    NoTokioRuntime,
19    /// The configured server address could not be resolved to a socket address.
20    ///
21    /// Check the host, port, address-family restrictions, and name service
22    /// configuration before retrying.
23    #[error("failed to resolve server address {addr:?}")]
24    Resolve { addr: String },
25    /// A UDP socket operation failed while binding, connecting, sending, or
26    /// receiving.
27    ///
28    /// This can be a transient network or OS error, but may also reflect local
29    /// permissions, routing, or socket configuration.
30    #[error("socket error: {0}")]
31    Socket(#[from] io::Error),
32    /// Applying or reading a socket option failed.
33    ///
34    /// This is usually caused by an unsupported option, an invalid local value,
35    /// or a platform-specific socket limitation. Changing socket configuration
36    /// is more likely to help than retrying unchanged.
37    #[error("socket option error while trying to {operation} for {remote}: {source}")]
38    SocketOption {
39        operation: &'static str,
40        remote: std::net::SocketAddr,
41        source: io::Error,
42    },
43    /// Restoring the configured read timeout after open negotiation failed.
44    ///
45    /// The prepared session was not committed, and the client remains
46    /// connected rather than partially open.
47    #[error("failed to restore configured socket read timeout after open negotiation: {source}")]
48    ReadTimeoutRestore { source: io::Error },
49    /// A packet failed IRTT protocol parsing or authentication.
50    ///
51    /// Likely causes include malformed input, unsupported protocol data, or an
52    /// HMAC mismatch between client and server.
53    #[error("protocol error: {0}")]
54    Protocol(#[from] irtt_proto::ProtoError),
55    /// No valid open reply was received before all configured open attempts
56    /// timed out.
57    ///
58    /// Retrying can succeed when the server or network is temporarily
59    /// unavailable. Persistent failures usually require changing the address,
60    /// authentication key, server configuration, or timeout settings.
61    #[error("all open requests timed out")]
62    OpenTimeout,
63    /// An open-attempt timeout is below the client's supported minimum.
64    ///
65    /// Increase the configured timeout before retrying.
66    #[error("open timeout {timeout:?} is below the minimum {minimum:?}")]
67    OpenTimeoutTooSmall {
68        timeout: Duration,
69        minimum: Duration,
70    },
71    /// The configured open-attempt timeout list is empty.
72    ///
73    /// Provide at least one timeout before opening the session.
74    #[error("open_timeouts must not be empty")]
75    NoOpenTimeouts,
76    /// The server explicitly rejected the open request.
77    ///
78    /// Retrying unchanged is unlikely to help unless the server configuration or
79    /// load has changed.
80    #[error("server rejected the open request")]
81    ServerRejected,
82    /// A no-test open request received a reply that did not have no-test close
83    /// semantics.
84    ///
85    /// This indicates an incompatible or unexpected peer response.
86    #[error("unexpected no-test open reply")]
87    UnexpectedNoTestReply,
88    /// A no-test open reply included a connection token.
89    ///
90    /// No-test negotiation must not establish a probe session, so a non-zero
91    /// token indicates an incompatible or unexpected peer response.
92    #[error("no-test reply included a non-zero connection token: {token}")]
93    NonZeroNoTestToken { token: u64 },
94    /// The peer replied with a protocol version different from the requested
95    /// version.
96    ///
97    /// This is a protocol compatibility error and requires compatible client
98    /// and server versions.
99    #[error("protocol version mismatch: requested {requested}, received {received}")]
100    ProtocolVersionMismatch { requested: i64, received: i64 },
101    /// The server returned a zero connection token for a normal test session.
102    ///
103    /// A normal session requires a non-zero token to correlate echo traffic, so
104    /// this indicates an invalid peer response.
105    #[error("server returned a zero connection token")]
106    ZeroToken,
107    /// Negotiated parameters were rejected by the configured negotiation policy.
108    ///
109    /// Use a less restrictive configuration or
110    /// [`NegotiationPolicy::Loose`](crate::NegotiationPolicy::Loose) when the
111    /// caller accepts documented server restrictions.
112    #[error("strict negotiation rejected changed params: {reason}")]
113    NegotiationRejected { reason: String },
114    /// The requested operation requires an open session, but the client is only
115    /// connected.
116    #[error("client is not open")]
117    NotOpen,
118    /// The session is already open.
119    ///
120    /// Continue running, receiving, or closing the existing session instead of
121    /// opening it again.
122    #[error("client session is already open")]
123    AlreadyOpen,
124    /// The no-test session has already completed.
125    ///
126    /// Create a new client session to perform another negotiation.
127    #[error("client session is already completed")]
128    AlreadyCompleted,
129    /// The session is already closed.
130    ///
131    /// Create a new client session before opening or running another test.
132    #[error("client session is already closed")]
133    AlreadyClosed,
134    /// A duration could not be represented as nanoseconds for scheduling or
135    /// protocol encoding.
136    ///
137    /// Use smaller configured or negotiated durations.
138    #[error("duration is too large to encode as nanoseconds")]
139    DurationOverflow,
140    /// A client-maintained counter would overflow.
141    ///
142    /// This is a runtime limit rather than a network failure. Start a new
143    /// session if the test duration or send volume reaches this limit.
144    #[error("counter {counter} overflowed")]
145    CounterOverflow { counter: &'static str },
146    /// Sending another probe would exceed the configured pending-probe limit.
147    ///
148    /// Increase [`ClientConfig::max_pending_probes`](crate::ClientConfig::max_pending_probes),
149    /// reduce send rate, or receive replies and timeouts more often before
150    /// sending again.
151    #[error("pending probe limit exceeded ({limit})")]
152    PendingLimitExceeded { limit: usize },
153    /// A wrapping wire sequence is still represented by an in-flight probe.
154    ///
155    /// The existing probe must be replied to or timed out before that sequence
156    /// can be reused.
157    #[error("wire sequence {seq} is still pending")]
158    PendingSequenceCollision { seq: u32 },
159    /// A prepared probe no longer matches the session's next wire sequence.
160    ///
161    /// Prepare the probe again after any intervening successful send.
162    #[error(
163        "prepared probe sequence {prepared_seq} is stale; next wire sequence is {next_wire_seq}"
164    )]
165    StalePreparedProbe {
166        prepared_seq: u32,
167        next_wire_seq: u32,
168    },
169    /// Reserving memory required by an operation failed before any datagram was
170    /// sent or logical protocol state was changed.
171    #[error("failed to reserve memory for {operation}: {source}")]
172    AllocationFailed {
173        operation: &'static str,
174        #[source]
175        source: TryReserveError,
176    },
177    /// UDP reported successful acceptance of a datagram with an unexpected
178    /// byte count.
179    ///
180    /// The send is committed locally before this transport invariant failure
181    /// is returned because the kernel reported success.
182    #[error("UDP accepted {actual} bytes for a {expected}-byte datagram")]
183    DatagramLengthMismatch { expected: usize, actual: usize },
184    /// Client configuration failed validation before the requested operation.
185    ///
186    /// The reported reason names a value that must be changed before retrying.
187    #[error("invalid configuration: {reason}")]
188    InvalidConfig { reason: String },
189}