Skip to main content

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}