irtt_client/config.rs
1use std::{net::SocketAddr, time::Duration};
2
3/// Protocol compatibility bound for a requested `server_fill` value, in UTF-8
4/// bytes.
5///
6/// This is the maximum encoded server-fill string accepted by this client and
7/// protocol decoder. [`ClientConfig::server_fill`] enforces the same bound
8/// before opening a session.
9pub use irtt_proto::MAX_SERVER_FILL_BYTES;
10use irtt_proto::{Clock, ReceivedStats, StampAt};
11
12pub(crate) const DEFAULT_PORT: u16 = 2112;
13/// Largest valid DSCP codepoint accepted by client configuration.
14///
15/// DSCP is a six-bit value in the range `0..=63`. The client maps it into the
16/// socket traffic-class field when configuring packet priority.
17pub const MAX_DSCP_CODEPOINT: u8 = 63;
18/// Largest IPv4 TTL or IPv6 hop-limit value accepted by client configuration.
19///
20/// This is the public user-configuration bound for
21/// [`SocketConfig::ttl`]. A value of zero is rejected separately because socket
22/// TTL and hop-limit settings are configured as `1..=255`.
23pub const MAX_TTL: u32 = 255;
24/// Largest UDP payload length accepted by client configuration, in bytes.
25///
26/// This is the maximum UDP payload size excluding IP and UDP headers. It caps
27/// [`ClientConfig::length`] before protocol packets are encoded or sent.
28pub const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507;
29pub(crate) const DEFAULT_DURATION: Duration = Duration::from_secs(3);
30pub(crate) const DEFAULT_INTERVAL: Duration = Duration::from_secs(1);
31pub(crate) const DEFAULT_OPEN_TIMEOUTS: [Duration; 4] = [
32 Duration::from_secs(1),
33 Duration::from_secs(2),
34 Duration::from_secs(4),
35 Duration::from_secs(8),
36];
37pub(crate) const MIN_OPEN_TIMEOUT: Duration = Duration::from_millis(200);
38pub(crate) const DEFAULT_PROBE_TIMEOUT: Duration = Duration::from_secs(4);
39pub(crate) const DEFAULT_MAX_PENDING: usize = 4096;
40
41/// Configuration for opening and running an IRTT client session.
42///
43/// This type describes both the protocol parameters sent in the IRTT open
44/// request and the local client behavior used to drive the UDP socket. Values
45/// that are negotiated by the server are available after opening the session
46/// through [`NegotiatedParams`](crate::NegotiatedParams).
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ClientConfig {
49 /// Remote server name or address.
50 ///
51 /// If no port is present, the default IRTT port 2112 is used. IPv6
52 /// literals may be supplied either bracketed or unbracketed when the
53 /// default port should be used.
54 pub server_addr: String,
55 /// Requested run duration.
56 ///
57 /// `Some(duration)` requests a finite test and must be greater than zero.
58 /// `None` requests continuous mode and is encoded on the wire as a zero
59 /// duration. Use [`RunMode::NoTest`] when the caller wants negotiation only
60 /// without sending probes.
61 pub duration: Option<Duration>,
62 /// Requested spacing between probe sends.
63 ///
64 /// The interval is encoded as nanoseconds in the open request and must be
65 /// greater than zero. The server may return a different interval depending
66 /// on the negotiated policy and server restrictions.
67 pub interval: Duration,
68 /// Requested echo packet payload length, in bytes.
69 ///
70 /// The value must fit within the UDP payload limit after protocol overhead.
71 /// A server may reduce the requested length during negotiation.
72 pub length: u32,
73 /// Requested server receive-statistics fields in echo replies.
74 pub received_stats: ReceivedStats,
75 /// Requested timestamp placement in echo replies.
76 pub stamp_at: StampAt,
77 /// Requested server clock sources for timestamp fields.
78 pub clock: Clock,
79 /// Requested DSCP codepoint.
80 ///
81 /// This is the six-bit DSCP value, not the full traffic-class byte, and
82 /// must be less than or equal to [`MAX_DSCP_CODEPOINT`].
83 pub dscp: u8,
84 /// Optional HMAC key used to authenticate IRTT packets.
85 ///
86 /// When present, open, echo, and close packets are encoded and decoded with
87 /// HMAC authentication. The peer must be configured with the same key.
88 pub hmac_key: Option<Vec<u8>>,
89 /// Optional server payload fill request.
90 ///
91 /// `None` leaves server fill behavior unspecified. `Some(value)` requests
92 /// a non-empty server fill mode/value and must not exceed
93 /// [`MAX_SERVER_FILL_BYTES`] bytes when UTF-8 encoded.
94 pub server_fill: Option<String>,
95 /// Per-attempt receive timeouts used while opening the session.
96 ///
97 /// The client sends an open request for each entry until a valid open reply
98 /// is received. The list must not be empty, and each timeout must be at
99 /// least 200 ms.
100 pub open_timeouts: Vec<Duration>,
101 /// Whether opening the session should start a probe test or perform a
102 /// negotiation-only no-test exchange.
103 pub run_mode: RunMode,
104 /// Policy for server changes to negotiable protocol parameters.
105 pub negotiation_policy: NegotiationPolicy,
106 /// Local UDP socket configuration.
107 pub socket_config: SocketConfig,
108 /// Time after sending a probe before the client reports it as lost.
109 ///
110 /// This timeout is local client behavior; it is not negotiated with the
111 /// server. It must be greater than zero.
112 pub probe_timeout: Duration,
113 /// Maximum number of probes tracked as pending/timed-out/completed.
114 ///
115 /// This bounds memory used for reply classification and must be greater
116 /// than zero. A very small value can reject sends when replies are still
117 /// outstanding.
118 pub max_pending_probes: usize,
119}
120
121impl Default for ClientConfig {
122 fn default() -> Self {
123 Self {
124 server_addr: format!("127.0.0.1:{DEFAULT_PORT}"),
125 duration: Some(DEFAULT_DURATION),
126 interval: DEFAULT_INTERVAL,
127 length: 0,
128 received_stats: ReceivedStats::Both,
129 stamp_at: StampAt::Both,
130 clock: Clock::Both,
131 dscp: 0,
132 hmac_key: None,
133 server_fill: None,
134 open_timeouts: DEFAULT_OPEN_TIMEOUTS.to_vec(),
135 run_mode: RunMode::Normal,
136 negotiation_policy: NegotiationPolicy::Strict,
137 socket_config: SocketConfig::default(),
138 probe_timeout: DEFAULT_PROBE_TIMEOUT,
139 max_pending_probes: DEFAULT_MAX_PENDING,
140 }
141 }
142}
143
144/// Local UDP socket options used by [`ClientConfig`].
145///
146/// These settings affect how the client binds, resolves, and receives from the
147/// socket. They do not change the IRTT protocol parameters negotiated with the
148/// server.
149#[derive(Debug, Clone, PartialEq, Eq, Default)]
150pub struct SocketConfig {
151 /// Local address to bind before connecting the UDP socket.
152 ///
153 /// `None` binds to an unspecified address with an ephemeral port matching
154 /// the selected remote address family.
155 pub bind_addr: Option<SocketAddr>,
156 /// Optional IPv4 TTL or IPv6 hop limit applied to sent packets.
157 ///
158 /// `None` leaves the platform default unchanged. Values must fit in the
159 /// platform socket option range; [`MAX_TTL`] is the public configuration
160 /// bound used by this crate.
161 pub ttl: Option<u32>,
162 /// Restrict name resolution to IPv4 addresses.
163 pub ipv4_only: bool,
164 /// Restrict name resolution to IPv6 addresses and set IPV6_V6ONLY for IPv6
165 /// sockets.
166 pub ipv6_only: bool,
167 /// Socket read timeout used after the session is open.
168 ///
169 /// `None` leaves reads blocking for APIs that perform a single receive.
170 /// Managed sessions may replace `None` or long timeouts with a short
171 /// timeout so cooperative cancellation can be observed promptly.
172 pub recv_timeout: Option<Duration>,
173}
174
175/// How strictly to handle server-side negotiation restrictions.
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum NegotiationPolicy {
178 /// Reject any negotiated parameter that is more restrictive or different
179 /// than requested.
180 Strict,
181 /// Accept documented server restrictions and report them in
182 /// [`NegotiatedParams`](crate::NegotiatedParams).
183 Loose,
184}
185
186/// Mode requested during the IRTT open exchange.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum RunMode {
189 /// Open a session and send echo probes for the negotiated run duration.
190 Normal,
191 /// Complete negotiation without running the echo probe test.
192 NoTest,
193}
194
195/// Bound for a single receive-drain operation.
196///
197/// This is used by lower-level callers that drive [`Client`](crate::Client)
198/// directly and want to cap how many datagrams are processed before returning
199/// to their own event loop.
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub struct RecvBudget {
202 /// Maximum number of datagrams to process before returning.
203 pub max_packets: usize,
204}
205
206impl Default for RecvBudget {
207 fn default() -> Self {
208 Self { max_packets: 64 }
209 }
210}