Skip to main content

Client

Struct Client 

Source
pub struct Client { /* private fields */ }
Expand description

Low-level synchronous IRTT client.

Client exposes the protocol steps directly: connect a UDP socket, open a session, send probes, receive replies, poll timeouts, and close. Callers that do not need to own this loop can use the unified managed API in crate::managed when the tokio feature is enabled.

§Example

Client is the runtime-free adapter. The caller drives receives and timeout polling between sends; see the standalone example for a complete loop. This example type-checks without contacting a server:

use irtt_client::{Client, ClientConfig};

let mut client = Client::connect("127.0.0.1:2112", ClientConfig::default())?;
let outcome = client.open()?;
println!("opened: {outcome:?}");
let sent = client.send_probe()?;
println!("sent: {sent:?}");
let received = client.recv_once()?;
println!("received: {received:?}");
let timed_out = client.poll_timeouts()?;
println!("timeouts: {timed_out:?}");
let closed = client.close()?;
println!("closed: {closed:?}");

Implementations§

Source§

impl Client

Source

pub fn connect( endpoint: impl AsRef<str>, config: ClientConfig, ) -> Result<Self, ClientError>

Resolve the endpoint and create a connected UDP socket.

This validates local configuration and prepares the open request, but it does not contact the server. Call open to perform the IRTT open exchange.

The endpoint accepts a name or address; an omitted port defaults to 2112. IPv6 literals may be bracketed or unbracketed with the default port. The reusable config contains no endpoint identity.

Examples found in repository?
examples/blocking_client.rs (line 41)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn set_recv_timeout( &mut self, timeout: Option<Duration>, ) -> Result<(), ClientError>

Set the blocking receive timeout used outside open attempts.

None (the default) lets recv_once() block indefinitely. A nonzero timeout bounds one logical receive, including retries after EINTR. recv_available() retains its bounded drain behavior. Open attempts use only OpenPolicy::timeouts and restore this setting on success or failure.

A zero duration is rejected by the socket; the previous setting is retained when applying the new timeout fails.

Examples found in repository?
examples/blocking_client.rs (line 49)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn open(&mut self) -> Result<OpenOutcome, ClientError>

Perform the IRTT open exchange.

On success, returns the negotiated open outcome and transitions the client into either an open probe session or completed no-test state. Open attempts use OpenPolicy::timeouts. Malformed or unrelated datagrams are ignored until the current attempt’s absolute deadline, so one attempt may consume several datagrams without retransmitting. Silence or ignored traffic eventually produces ClientError::OpenTimeout. A structurally recognizable malformed or incompatible Open reply from the connected peer is terminal; when HMAC is configured, that recognition additionally requires authentication.

When a trusted reply allocates a token but later negotiation or socket preparation fails, the client sends a best-effort cleanup close and preserves the original failure. Failed opening never leaves the session machine open.

Examples found in repository?
examples/blocking_client.rs (line 54)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>

Send a close request and emit a ClientEvent::SessionClosed event.

The close event means the client has sent its close packet and stopped tracking the session locally; it is not a server acknowledgement. If the send fails, the negotiated DSCP is restored best-effort and the session remains open.

Examples found in repository?
examples/blocking_client.rs (line 120)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn probe_timeout(&self) -> Duration

Return the local timeout used to classify pending probes as lost.

Examples found in repository?
examples/blocking_client.rs (line 77)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn send_probe(&mut self) -> Result<SendReceipt, SendProbeError>

Send one echo probe now. The caller owns pacing and run duration.

The negotiated interval and duration do not gate low-level sends. A receipt means socket acceptance and infallible session commitment have completed. Failures before acceptance return SendProbeError::NotCommitted; later validation or TX processing failures preserve the receipt in SendProbeError::AfterCommit.

Examples found in repository?
examples/blocking_client.rs (line 85)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn recv_once(&mut self) -> Result<Vec<ClientEvent>, ClientError>

Receive and classify at most one datagram from the socket.

Returns an empty event list when the socket read would block or times out. Malformed or unrelated datagrams are reported as warning events. Requires an open session; lifecycle errors are returned before socket I/O.

Examples found in repository?
examples/blocking_client.rs (line 101)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn recv_available( &mut self, budget: RecvBudget, ) -> Result<Vec<ClientEvent>, ClientError>

Receive and classify datagrams until a receive produces no events or the receive budget is exhausted.

Requires an open session; lifecycle errors are returned before socket I/O.

Source

pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>

Polls for probes that have timed out as of the current monotonic time.

Examples found in repository?
examples/blocking_client.rs (line 108)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn poll_timeouts_at( &mut self, now: Instant, ) -> Result<Vec<ClientEvent>, ClientError>

Polls for probes that have timed out as of now.

This is useful for callers that drive Client from their own event loop and want timeout decisions to use the same sampled Instant as their scheduling logic.

now is monotonic time only; wall-clock time is not used for timeout expiry.

Source

pub fn negotiation(&self) -> Option<&NegotiationResult>

Accepted semantics and exact peer parameters of the currently live session.

Returns None before Open, after Close, and after no-test completion.

Source

pub fn next_probe_timeout_deadline(&self) -> Option<Instant>

Earliest timeout deadline among probes still awaiting a reply.

Source

pub fn has_pending_probes(&self) -> bool

Whether any sent probes still await a reply or timeout.

Examples found in repository?
examples/blocking_client.rs (line 81)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}
Source

pub fn is_peer_closed(&self) -> bool

Return whether the session was closed by a peer close-flagged reply.

Direct operations on a closed client still return ClientError::AlreadyClosed. This method lets higher-level run loops avoid treating a successfully observed peer close as a local cleanup failure.

Examples found in repository?
examples/blocking_client.rs (line 79)
25fn main() {
26    let config = ClientConfig {
27        open: OpenPolicy {
28            // A single short attempt keeps this example fast when no server is
29            // reachable; production callers should generally keep the default.
30            timeouts: vec![Duration::from_millis(300)],
31            ..Default::default()
32        },
33        request: SessionRequest {
34            duration: Some(RUN_DURATION),
35            interval: Duration::from_millis(200),
36            ..Default::default()
37        },
38        ..Default::default()
39    };
40
41    let mut client = match Client::connect("127.0.0.1:2112", config) {
42        Ok(client) => client,
43        Err(err) => {
44            eprintln!("failed to prepare client socket: {err}");
45            return;
46        }
47    };
48
49    if let Err(err) = client.set_recv_timeout(Some(Duration::from_millis(100))) {
50        eprintln!("failed to configure receive timeout: {err}");
51        return;
52    }
53
54    let negotiated = match client.open() {
55        Ok(OpenOutcome::Started(started)) => {
56            println!("session opened: {started:?}");
57            started.negotiation
58        }
59        Ok(OpenOutcome::NoTestCompleted(completed)) => {
60            println!("{completed:?}");
61            return;
62        }
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    };
68
69    // This loop owns cadence and duration. The transport sends whenever called.
70    let interval = negotiated.accepted.interval;
71    let start = Instant::now();
72    let end = start
73        + negotiated
74            .accepted
75            .duration
76            .expect("example requests a finite run");
77    let deadline = end + client.probe_timeout();
78    let mut next_send = start;
79    while Instant::now() < deadline && !client.is_peer_closed() {
80        let now = Instant::now();
81        if now >= end && !client.has_pending_probes() {
82            break;
83        }
84        if now < end && now >= next_send {
85            match client.send_probe() {
86                Ok(receipt) => {
87                    print_event(&receipt.into());
88                    while next_send <= Instant::now() {
89                        next_send += interval;
90                    }
91                }
92                Err(err) => {
93                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
94                        print_event(&(*receipt).into());
95                    }
96                    eprintln!("send failed: {err}");
97                    break;
98                }
99            }
100        }
101        match client.recv_once() {
102            Ok(events) => events.iter().for_each(print_event),
103            Err(err) => {
104                eprintln!("receive failed: {err}");
105                break;
106            }
107        }
108        match client.poll_timeouts() {
109            Ok(events) => events.iter().for_each(print_event),
110            Err(err) => {
111                eprintln!("timeout polling failed: {err}");
112                break;
113            }
114        }
115    }
116
117    if client.is_peer_closed() {
118        return;
119    }
120    match client.close() {
121        Ok(events) => events.iter().for_each(print_event),
122        Err(err) => eprintln!("close failed: {err}"),
123    }
124}

Trait Implementations§

Source§

impl Debug for Client

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.