docs: document client errors and bounds
This commit is contained in:
1 parent
01c10e2e0d
commit
6f77d38a7f
2 files changed
+131
No files matched your search
@@ -1,11 +1,32 @@
|
|||||||
use std::{net::SocketAddr, time::Duration};
|
use std::{net::SocketAddr, time::Duration};
|
||||||
|
|
||||||
|
/// Protocol compatibility bound for a requested `server_fill` value, in UTF-8
|
||||||
|
/// bytes.
|
||||||
|
///
|
||||||
|
/// This is the maximum encoded server-fill string accepted by this client and
|
||||||
|
/// protocol decoder. [`ClientConfig::server_fill`] enforces the same bound
|
||||||
|
/// before opening a session.
|
||||||
|
pub use irtt_proto::MAX_SERVER_FILL_BYTES;
|
||||||
pub use irtt_proto::MAX_SERVER_FILL_BYTES;
|
pub use irtt_proto::MAX_SERVER_FILL_BYTES;
|
||||||
use irtt_proto::{Clock, ReceivedStats, StampAt};
|
use irtt_proto::{Clock, ReceivedStats, StampAt};
|
||||||
|
|
||||||
pub(crate) const DEFAULT_PORT: u16 = 2112;
|
pub(crate) const DEFAULT_PORT: u16 = 2112;
|
||||||
|
/// Largest valid DSCP codepoint accepted by client configuration.
|
||||||
|
///
|
||||||
|
/// DSCP is a six-bit value in the range `0..=63`. The client maps it into the
|
||||||
|
/// socket traffic-class field when configuring packet priority.
|
||||||
pub const MAX_DSCP_CODEPOINT: u8 = 63;
|
pub const MAX_DSCP_CODEPOINT: u8 = 63;
|
||||||
|
/// Largest IPv4 TTL or IPv6 hop-limit value accepted by client configuration.
|
||||||
|
///
|
||||||
|
/// This is the public user-configuration bound for
|
||||||
|
/// [`SocketConfig::ttl`]. A value of zero is rejected separately because socket
|
||||||
|
/// TTL and hop-limit settings are configured as `1..=255`.
|
||||||
pub const MAX_TTL: u32 = 255;
|
pub const MAX_TTL: u32 = 255;
|
||||||
|
/// Largest UDP payload length accepted by client configuration, in bytes.
|
||||||
|
///
|
||||||
|
/// This is the maximum UDP payload size excluding IP and UDP headers. It caps
|
||||||
|
/// [`ClientConfig::length`] before protocol packets are encoded or sent.
|
||||||
|
pub const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507;
|
||||||
pub const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507;
|
pub const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507;
|
||||||
pub(crate) const DEFAULT_DURATION: Duration = Duration::from_secs(3);
|
pub(crate) const DEFAULT_DURATION: Duration = Duration::from_secs(3);
|
||||||
pub(crate) const DEFAULT_INTERVAL: Duration = Duration::from_secs(1);
|
pub(crate) const DEFAULT_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
|||||||
@@ -2,66 +2,176 @@ use std::{io, time::Duration};
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Failure reported while configuring, opening, running, receiving, closing, or
|
||||||
|
/// joining an IRTT client session.
|
||||||
|
///
|
||||||
|
/// Configuration validation errors usually require changing
|
||||||
|
/// [`ClientConfig`](crate::ClientConfig) or related options before retrying.
|
||||||
|
/// Network, protocol, and runtime errors may be transient, but can also reflect
|
||||||
|
/// peer incompatibility or an invalid session state.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
|
/// The configured server address could not be resolved to a socket address.
|
||||||
|
///
|
||||||
|
/// Check the host, port, address-family restrictions, and name service
|
||||||
|
/// configuration before retrying.
|
||||||
#[error("failed to resolve server address {addr:?}")]
|
#[error("failed to resolve server address {addr:?}")]
|
||||||
Resolve { addr: String },
|
Resolve { addr: String },
|
||||||
|
/// A UDP socket operation failed while binding, connecting, sending, or
|
||||||
|
/// receiving.
|
||||||
|
///
|
||||||
|
/// This can be a transient network or OS error, but may also reflect local
|
||||||
|
/// permissions, routing, or socket configuration.
|
||||||
#[error("socket error: {0}")]
|
#[error("socket error: {0}")]
|
||||||
Socket(#[from] io::Error),
|
Socket(#[from] io::Error),
|
||||||
|
/// Applying or reading a socket option failed.
|
||||||
|
///
|
||||||
|
/// This is usually caused by an unsupported option, an invalid local value,
|
||||||
|
/// or a platform-specific socket limitation. Changing socket configuration
|
||||||
|
/// is more likely to help than retrying unchanged.
|
||||||
#[error("socket option error while trying to {operation} for {remote}: {source}")]
|
#[error("socket option error while trying to {operation} for {remote}: {source}")]
|
||||||
SocketOption {
|
SocketOption {
|
||||||
operation: &'static str,
|
operation: &'static str,
|
||||||
remote: std::net::SocketAddr,
|
remote: std::net::SocketAddr,
|
||||||
source: io::Error,
|
source: io::Error,
|
||||||
},
|
},
|
||||||
|
/// Restoring the configured read timeout after open negotiation failed.
|
||||||
|
///
|
||||||
|
/// The session may have opened successfully, but the socket could not be
|
||||||
|
/// returned to the caller's requested receive-timeout behavior.
|
||||||
#[error("failed to restore configured socket read timeout after open negotiation: {source}")]
|
#[error("failed to restore configured socket read timeout after open negotiation: {source}")]
|
||||||
ReadTimeoutRestore { source: io::Error },
|
ReadTimeoutRestore { source: io::Error },
|
||||||
|
/// A packet failed IRTT protocol parsing or authentication.
|
||||||
|
///
|
||||||
|
/// Likely causes include malformed input, unsupported protocol data, or an
|
||||||
|
/// HMAC mismatch between client and server.
|
||||||
#[error("protocol error: {0}")]
|
#[error("protocol error: {0}")]
|
||||||
Protocol(#[from] irtt_proto::ProtoError),
|
Protocol(#[from] irtt_proto::ProtoError),
|
||||||
|
/// No valid open reply was received before all configured open attempts
|
||||||
|
/// timed out.
|
||||||
|
///
|
||||||
|
/// Retrying can succeed when the server or network is temporarily
|
||||||
|
/// unavailable. Persistent failures usually require changing the address,
|
||||||
|
/// authentication key, server configuration, or timeout settings.
|
||||||
#[error("all open requests timed out")]
|
#[error("all open requests timed out")]
|
||||||
OpenTimeout,
|
OpenTimeout,
|
||||||
|
/// An open-attempt timeout is below the client's supported minimum.
|
||||||
|
///
|
||||||
|
/// Increase the configured timeout before retrying.
|
||||||
#[error("open timeout {timeout:?} is below the minimum {minimum:?}")]
|
#[error("open timeout {timeout:?} is below the minimum {minimum:?}")]
|
||||||
OpenTimeoutTooSmall {
|
OpenTimeoutTooSmall {
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
minimum: Duration,
|
minimum: Duration,
|
||||||
},
|
},
|
||||||
|
/// The configured open-attempt timeout list is empty.
|
||||||
|
///
|
||||||
|
/// Provide at least one timeout before opening the session.
|
||||||
#[error("open_timeouts must not be empty")]
|
#[error("open_timeouts must not be empty")]
|
||||||
NoOpenTimeouts,
|
NoOpenTimeouts,
|
||||||
|
/// The server explicitly rejected the open request.
|
||||||
|
///
|
||||||
|
/// Retrying unchanged is unlikely to help unless the server configuration or
|
||||||
|
/// load has changed.
|
||||||
#[error("server rejected the open request")]
|
#[error("server rejected the open request")]
|
||||||
ServerRejected,
|
ServerRejected,
|
||||||
|
/// A no-test open request received a reply that did not have no-test close
|
||||||
|
/// semantics.
|
||||||
|
///
|
||||||
|
/// This indicates an incompatible or unexpected peer response.
|
||||||
#[error("unexpected no-test open reply")]
|
#[error("unexpected no-test open reply")]
|
||||||
UnexpectedNoTestReply,
|
UnexpectedNoTestReply,
|
||||||
|
/// A no-test open reply included a connection token.
|
||||||
|
///
|
||||||
|
/// No-test negotiation must not establish a probe session, so a non-zero
|
||||||
|
/// token indicates an incompatible or unexpected peer response.
|
||||||
#[error("no-test reply included a non-zero connection token: {token}")]
|
#[error("no-test reply included a non-zero connection token: {token}")]
|
||||||
NonZeroNoTestToken { token: u64 },
|
NonZeroNoTestToken { token: u64 },
|
||||||
|
/// The peer replied with a protocol version different from the requested
|
||||||
|
/// version.
|
||||||
|
///
|
||||||
|
/// This is a protocol compatibility error and requires compatible client
|
||||||
|
/// and server versions.
|
||||||
#[error("protocol version mismatch: requested {requested}, received {received}")]
|
#[error("protocol version mismatch: requested {requested}, received {received}")]
|
||||||
ProtocolVersionMismatch { requested: i64, received: i64 },
|
ProtocolVersionMismatch { requested: i64, received: i64 },
|
||||||
|
/// The server returned a zero connection token for a normal test session.
|
||||||
|
///
|
||||||
|
/// A normal session requires a non-zero token to correlate echo traffic, so
|
||||||
|
/// this indicates an invalid peer response.
|
||||||
#[error("server returned a zero connection token")]
|
#[error("server returned a zero connection token")]
|
||||||
ZeroToken,
|
ZeroToken,
|
||||||
|
/// Negotiated parameters were rejected by the configured negotiation policy.
|
||||||
|
///
|
||||||
|
/// Use a less restrictive configuration or
|
||||||
|
/// [`NegotiationPolicy::Loose`](crate::NegotiationPolicy::Loose) when the
|
||||||
|
/// caller accepts documented server restrictions.
|
||||||
#[error("strict negotiation rejected changed params: {reason}")]
|
#[error("strict negotiation rejected changed params: {reason}")]
|
||||||
NegotiationRejected { reason: String },
|
NegotiationRejected { reason: String },
|
||||||
|
/// The requested operation requires an open session, but the client is only
|
||||||
|
/// connected.
|
||||||
#[error("client is not open")]
|
#[error("client is not open")]
|
||||||
NotOpen,
|
NotOpen,
|
||||||
|
/// The session is already open.
|
||||||
|
///
|
||||||
|
/// Continue running, receiving, or closing the existing session instead of
|
||||||
|
/// opening it again.
|
||||||
#[error("client session is already open")]
|
#[error("client session is already open")]
|
||||||
AlreadyOpen,
|
AlreadyOpen,
|
||||||
|
/// The no-test session has already completed.
|
||||||
|
///
|
||||||
|
/// Create a new client session to perform another negotiation.
|
||||||
#[error("client session is already completed")]
|
#[error("client session is already completed")]
|
||||||
AlreadyCompleted,
|
AlreadyCompleted,
|
||||||
|
/// The session is already closed.
|
||||||
|
///
|
||||||
|
/// Create a new client session before opening or running another test.
|
||||||
#[error("client session is already closed")]
|
#[error("client session is already closed")]
|
||||||
AlreadyClosed,
|
AlreadyClosed,
|
||||||
|
/// A duration could not be represented as nanoseconds for scheduling or
|
||||||
|
/// protocol encoding.
|
||||||
|
///
|
||||||
|
/// Use smaller configured or negotiated durations.
|
||||||
#[error("duration is too large to encode as nanoseconds")]
|
#[error("duration is too large to encode as nanoseconds")]
|
||||||
DurationOverflow,
|
DurationOverflow,
|
||||||
|
/// A client-maintained counter would overflow.
|
||||||
|
///
|
||||||
|
/// This is a runtime limit rather than a network failure. Start a new
|
||||||
|
/// session if the test duration or send volume reaches this limit.
|
||||||
#[error("counter {counter} overflowed")]
|
#[error("counter {counter} overflowed")]
|
||||||
CounterOverflow { counter: &'static str },
|
CounterOverflow { counter: &'static str },
|
||||||
|
/// Sending another probe would exceed the configured pending-probe limit.
|
||||||
|
///
|
||||||
|
/// Increase [`ClientConfig::max_pending_probes`](crate::ClientConfig::max_pending_probes),
|
||||||
|
/// reduce send rate, or receive replies and timeouts more often before
|
||||||
|
/// sending again.
|
||||||
#[error("pending probe limit exceeded ({limit})")]
|
#[error("pending probe limit exceeded ({limit})")]
|
||||||
PendingLimitExceeded { limit: usize },
|
PendingLimitExceeded { limit: usize },
|
||||||
|
/// Client configuration failed validation before the requested operation.
|
||||||
|
///
|
||||||
|
/// The reported reason names a value that must be changed before retrying.
|
||||||
#[error("invalid configuration: {reason}")]
|
#[error("invalid configuration: {reason}")]
|
||||||
InvalidConfig { reason: String },
|
InvalidConfig { reason: String },
|
||||||
|
/// A managed-session worker thread panicked before producing an outcome.
|
||||||
|
///
|
||||||
|
/// This is a runtime failure from joining the managed session, not a
|
||||||
|
/// protocol response from the server.
|
||||||
#[error("managed client worker thread panicked")]
|
#[error("managed client worker thread panicked")]
|
||||||
WorkerPanicked,
|
WorkerPanicked,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Failure reported while setting up or receiving from an event subscription.
|
||||||
|
///
|
||||||
|
/// Zero-capacity subscriber queues are rejected when subscriptions are created.
|
||||||
|
/// Once a subscription exists, this error type reports delivery failures observed
|
||||||
|
/// by [`EventSubscription`](crate::EventSubscription).
|
||||||
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum EventSubscriptionError {
|
pub enum EventSubscriptionError {
|
||||||
|
/// The subscription can no longer receive new events.
|
||||||
|
///
|
||||||
|
/// Already queued events are delivered first. After the queue is drained,
|
||||||
|
/// `recv` and `try_recv` return this error when the hub disconnects the
|
||||||
|
/// subscriber, the hub is shut down, or the subscription is disconnected by
|
||||||
|
/// its overflow policy.
|
||||||
#[error("event subscription is disconnected")]
|
#[error("event subscription is disconnected")]
|
||||||
Disconnected,
|
Disconnected,
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user