Skip to main content

AsyncClient

Struct AsyncClient 

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

Low-level Tokio IRTT client for one connected UDP target.

AsyncClient does not construct, own, or store a Tokio runtime. Its async methods are polled by the caller and require a current Tokio runtime with I/O and time enabled.

§Example

Use this low-level adapter when the application already owns Tokio and wants to drive the session lifecycle itself. This example type-checks without contacting a server.

use irtt_client::{AsyncClient, ClientConfig};

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

Implementations§

Source§

impl AsyncClient

Source

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

Resolve the endpoint and construct one connected Tokio UDP socket.

Polling this future without a current Tokio runtime returns ClientError::NoTokioRuntime. A runtime without enabled I/O or time drivers is outside this type’s runtime contract.

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/tokio_client.rs (line 60)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Perform the IRTT open exchange.

Each configured attempt uses one absolute deadline for one successful request submission and all replies inspected during that attempt. Malformed, unrelated, wrong-direction, and unauthenticated datagrams are ignored without resending the request.

Dropping this future before trusted acceptance leaves the local machine connected with no negotiated adapter state. If it is dropped during best-effort post-token cleanup, cleanup may remain incomplete because this low-level client never detaches cleanup work.

Examples found in repository?
examples/tokio_client.rs (line 68)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Send one echo probe now, committing protocol state only after acceptance. The caller owns pacing and run duration.

A prepared packet is retained across readiness false positives, cancellation, and socket errors so a later call retries the same logical probe without advancing state before kernel acceptance. Cancellation or WouldBlock before submission is uncommitted. After acceptance there is no suspension before infallible commitment, and the prepared probe is cleared even if post-send processing returns SendProbeError::AfterCommit. That failure retains the authoritative receipt; earlier failures return SendProbeError::NotCommitted.

Examples found in repository?
examples/tokio_client.rs (line 123)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Await and classify one complete UDP datagram.

State is validated before readiness is awaited. Readiness false positives retry without changing protocol state. Authenticated peer-close events remain authoritative even if best-effort DSCP cleanup fails.

Examples found in repository?
examples/tokio_client.rs (line 109)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Poll protocol timeouts using a newly captured monotonic timestamp.

Examples found in repository?
examples/tokio_client.rs (line 139)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Poll protocol timeouts using the caller’s monotonic timestamp.

Source

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

Send the retained close packet and commit local close exactly once.

DSCP is cleared only after write readiness and immediately before the nonblocking send attempt. A WouldBlock result restores it before the next await, so dropping this future at a suspension point cannot leave an otherwise-open session with cleared DSCP.

Examples found in repository?
examples/tokio_client.rs (line 151)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

pub fn probe_timeout(&self) -> Duration

Return the configured local probe timeout.

Examples found in repository?
examples/tokio_client.rs (line 91)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
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 has_pending_probes(&self) -> bool

Whether any sent probes still await a reply or timeout.

Examples found in repository?
examples/tokio_client.rs (line 94)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

pub fn is_peer_closed(&self) -> bool

Return whether an authenticated peer close ended the session.

Examples found in repository?
examples/tokio_client.rs (line 93)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}
Source

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

Earliest timeout deadline among probes still awaiting a reply.

Examples found in repository?
examples/tokio_client.rs (line 103)
44async fn run() {
45    let config = ClientConfig {
46        open: OpenPolicy {
47            // A single short attempt keeps this example fast when no server is
48            // reachable; production callers should generally keep the default.
49            timeouts: vec![Duration::from_millis(300)],
50            ..Default::default()
51        },
52        request: SessionRequest {
53            duration: Some(Duration::from_secs(2)),
54            interval: Duration::from_millis(200),
55            ..Default::default()
56        },
57        ..Default::default()
58    };
59
60    let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61        Ok(client) => client,
62        Err(err) => {
63            eprintln!("failed to prepare client socket: {err}");
64            return;
65        }
66    };
67
68    let negotiated = match client.open().await {
69        Ok(OpenOutcome::Started(started)) => {
70            println!("session opened: {started:?}");
71            started.negotiation
72        }
73        Ok(OpenOutcome::NoTestCompleted(completed)) => {
74            println!("{completed:?}");
75            return;
76        }
77        Err(err) => {
78            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79            return;
80        }
81    };
82
83    // This loop owns cadence and duration. The transport sends whenever called.
84    let interval = negotiated.accepted.interval;
85    let start = Instant::now();
86    let end = start
87        + negotiated
88            .accepted
89            .duration
90            .expect("example requests a finite run");
91    let deadline = end + client.probe_timeout();
92    let mut next_send = start;
93    while Instant::now() < deadline && !client.is_peer_closed() {
94        if Instant::now() >= end && !client.has_pending_probes() {
95            break;
96        }
97        // Bound the receive wait by the next send/timeout deadline, rather
98        // than awaiting recv() unconditionally: a lost or rate-limited reply
99        // must not stall pacing or timeout classification indefinitely.
100        let wake_at = (next_send < end)
101            .then_some(next_send)
102            .into_iter()
103            .chain(client.next_probe_timeout_deadline())
104            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105            .min()
106            .unwrap();
107
108        let recv_result = tokio::select! {
109            events = client.recv() => Some(events),
110            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111        };
112        if let Some(result) = recv_result {
113            match result {
114                Ok(events) => events.iter().for_each(print_event),
115                Err(err) => {
116                    eprintln!("receive failed: {err}");
117                    break;
118                }
119            }
120        }
121
122        if Instant::now() < end && Instant::now() >= next_send {
123            match client.send_probe().await {
124                Ok(receipt) => {
125                    print_event(&receipt.into());
126                    while next_send <= Instant::now() {
127                        next_send += interval;
128                    }
129                }
130                Err(err) => {
131                    if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132                        print_event(&(*receipt).into());
133                    }
134                    eprintln!("send failed: {err}");
135                    break;
136                }
137            }
138        }
139        match client.poll_timeouts() {
140            Ok(events) => events.iter().for_each(print_event),
141            Err(err) => {
142                eprintln!("timeout polling failed: {err}");
143                break;
144            }
145        }
146    }
147
148    if client.is_peer_closed() {
149        return;
150    }
151    match client.close().await {
152        Ok(events) => events.iter().for_each(print_event),
153        Err(err) => eprintln!("close failed: {err}"),
154    }
155}

Trait Implementations§

Source§

impl Debug for AsyncClient

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.