irtt_client/send.rs
1use std::{error::Error, fmt, net::SocketAddr, time::Duration};
2
3use crate::{ClientError, ClientEvent, ClientTimestamp};
4
5/// An Echo datagram accepted by the UDP socket and committed in session state.
6///
7/// The wire sequence and sent count have advanced, and the probe is pending.
8/// This receipt remains authoritative even if subsequent validation or kernel
9/// TX timestamp processing fails. It carries transport facts only; managed
10/// scheduling annotations belong on the corresponding [`ClientEvent::EchoSent`].
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct SendReceipt {
13 /// Committed wire sequence number.
14 pub seq: u32,
15 /// Resolved remote socket address.
16 pub remote: SocketAddr,
17 /// Public measurement timestamp sampled immediately after socket acceptance.
18 /// The private pre-send anchor still determines the timeout deadline.
19 pub sent_at: ClientTimestamp,
20 /// Number of bytes the socket reported accepting.
21 pub bytes: usize,
22 /// Time spent in the successful socket send call.
23 pub send_call: Duration,
24}
25
26impl From<SendReceipt> for ClientEvent {
27 /// Construct the committed send event without scheduling annotations.
28 fn from(receipt: SendReceipt) -> Self {
29 Self::EchoSent {
30 seq: receipt.seq,
31 remote: receipt.remote,
32 scheduled_at: None,
33 sent_at: receipt.sent_at,
34 bytes: receipt.bytes,
35 send_call: receipt.send_call,
36 timer_error: None,
37 }
38 }
39}
40
41/// Probe-send failure with an explicit socket-acceptance commitment boundary.
42///
43/// There is deliberately no blanket conversion from [`ClientError`]: each send
44/// failure must be classified at the point where its commitment is known.
45#[derive(Debug)]
46pub enum SendProbeError {
47 /// No datagram was accepted and no probe state or sequence was committed.
48 NotCommitted(ClientError),
49 /// The probe was committed, then post-send processing failed.
50 /// The pending probe and advanced sequence/count remain committed.
51 AfterCommit {
52 /// Authoritative receipt for the accepted send.
53 receipt: SendReceipt,
54 /// Operational cause of the post-send failure.
55 source: Box<ClientError>,
56 },
57}
58
59impl fmt::Display for SendProbeError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::NotCommitted(source) => write!(f, "probe send was not committed: {source}"),
63 Self::AfterCommit { source, .. } => {
64 write!(
65 f,
66 "probe send was committed, but post-send processing failed: {source}"
67 )
68 }
69 }
70 }
71}
72
73impl Error for SendProbeError {
74 fn source(&self) -> Option<&(dyn Error + 'static)> {
75 Some(match self {
76 Self::NotCommitted(source) => source,
77 Self::AfterCommit { source, .. } => source.as_ref(),
78 })
79 }
80}