umsh_node/
ticket.rs

1use alloc::rc::Rc;
2use core::cell::RefCell;
3
4use umsh_mac::{LocalIdentityId, SendReceipt};
5
6/// Identity-scoped send token.
7///
8/// [`SendReceipt`] is only unique within a [`LocalIdentityId`] slot (it's allocated
9/// from a per-identity `next_receipt` counter). `SendToken` combines the two into a
10/// single value that is unique across all identity slots, suitable for use as a
11/// dispatcher key, ticket identifier, or cancellation handle.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct SendToken {
14    pub identity_id: LocalIdentityId,
15    pub receipt: SendReceipt,
16}
17
18impl SendToken {
19    /// Create a new send token from an identity slot and receipt.
20    pub fn new(identity_id: LocalIdentityId, receipt: SendReceipt) -> Self {
21        Self {
22            identity_id,
23            receipt,
24        }
25    }
26}
27
28/// Internal shared state updated by the dispatcher.
29#[derive(Clone, Debug, Default)]
30pub(crate) struct TicketState {
31    /// Frame was handed to the radio at least once.
32    pub transmitted: bool,
33    /// A repeater was overheard forwarding this frame.
34    pub repeated: bool,
35    /// Transport ACK received from the destination.
36    pub acked: bool,
37    /// ACK timeout — all retransmits exhausted without ACK.
38    pub failed: bool,
39    /// MAC is completely done with this send (no more events will fire).
40    pub finished: bool,
41    /// True for sends that don't request ACK (broadcast/multicast).
42    /// The dispatcher marks these finished as soon as Transmitted fires.
43    pub non_ack: bool,
44}
45
46/// Lightweight handle for observing the progress of an in-flight send.
47///
48/// The dispatcher updates the internal `TicketState` synchronously from the MAC
49/// event callback. The application queries progress at its own pace via polling
50/// methods (`was_transmitted`, `was_acked`, `is_finished`, etc.).
51///
52/// Dropping the ticket unregisters it from the dispatcher (via `Weak` reference
53/// invalidation). The MAC continues the in-flight send — dropping only stops
54/// *observation*, not the send itself.
55pub struct SendProgressTicket {
56    token: Option<SendToken>,
57    state: Rc<RefCell<TicketState>>,
58}
59
60impl SendProgressTicket {
61    /// Create a new ticket registered with the dispatcher for ACK-tracked sends.
62    pub(crate) fn new(token: SendToken, state: Rc<RefCell<TicketState>>) -> Self {
63        Self {
64            token: Some(token),
65            state,
66        }
67    }
68
69    /// Create a ticket for a send that has no receipt (e.g. non-ACK unicast).
70    ///
71    /// Without a receipt, the MAC's `Transmitted` event cannot be correlated
72    /// back to this ticket. The ticket is immediately marked as transmitted
73    /// and finished since there is nothing to track.
74    pub(crate) fn fire_and_forget() -> Self {
75        let state = Rc::new(RefCell::new(TicketState {
76            transmitted: true,
77            finished: true,
78            non_ack: true,
79            ..TicketState::default()
80        }));
81        Self { token: None, state }
82    }
83
84    /// The identity-scoped send token, if this is an ACK-tracked send.
85    pub fn token(&self) -> Option<SendToken> {
86        self.token
87    }
88
89    /// The underlying receipt, if this is an ACK-tracked send.
90    pub fn receipt(&self) -> Option<SendReceipt> {
91        self.token.map(|t| t.receipt)
92    }
93
94    /// True after the frame was handed to the radio at least once.
95    ///
96    /// For all send types, this reflects actual radio transmission: it
97    /// becomes `true` when the MAC fires the `Transmitted` event for this
98    /// ticket's receipt.
99    pub fn was_transmitted(&self) -> bool {
100        self.state.borrow().transmitted
101    }
102
103    /// True after a repeater was overheard forwarding this frame.
104    pub fn was_repeated(&self) -> bool {
105        self.state.borrow().repeated
106    }
107
108    /// True after a transport ACK was received from the destination.
109    pub fn was_acked(&self) -> bool {
110        self.state.borrow().acked
111    }
112
113    /// True when the ACK timed out — all retransmits exhausted without ACK.
114    pub fn has_failed(&self) -> bool {
115        self.state.borrow().failed
116    }
117
118    /// True when the MAC is completely done — no more retransmissions,
119    /// no more events will fire for this ticket.
120    ///
121    /// For non-ACK sends (broadcast/multicast), this becomes `true`
122    /// immediately after the first radio transmission. For ACK-tracked
123    /// sends, this becomes `true` after an ACK is received or all
124    /// retransmits are exhausted.
125    pub fn is_finished(&self) -> bool {
126        self.state.borrow().finished
127    }
128}