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}