moqtap_client/draft13/track_status.rs
1/// TrackStatus lifecycle states.
2///
3/// This draft renamed the request to TRACK_STATUS and gave it two answers of
4/// its own. Section 8.21: "The publisher sends a TRACK_STATUS_OK control
5/// message in response to a successful TRACK_STATUS message." A machine with
6/// one arrival event for both cannot say which of them ended the request, and
7/// nothing downstream of it can either.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TrackStatusState {
10 /// Initial state before any TRACK_STATUS message is sent.
11 Idle,
12 /// TRACK_STATUS has been sent; awaiting OK or ERROR.
13 Pending,
14 /// Track status request has completed.
15 Done,
16}
17
18/// Errors that can occur during track status state transitions.
19#[derive(Debug, thiserror::Error, PartialEq, Eq)]
20pub enum TrackStatusError {
21 /// An event was received that is not valid for the current state.
22 #[error("invalid transition from {from:?} on event {event}")]
23 InvalidTransition {
24 /// The state the machine was in when the invalid event arrived.
25 from: TrackStatusState,
26 /// The name of the event that was rejected.
27 event: String,
28 },
29}
30
31/// Pure state machine for a MoQT track status request (draft-12).
32/// Transitions: Idle → Pending → Done.
33pub struct TrackStatusStateMachine {
34 state: TrackStatusState,
35}
36
37impl Default for TrackStatusStateMachine {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl TrackStatusStateMachine {
44 /// Creates a new state machine in the [`TrackStatusState::Idle`] state.
45 pub fn new() -> Self {
46 Self { state: TrackStatusState::Idle }
47 }
48
49 /// Returns the current state of the track status request.
50 pub fn state(&self) -> TrackStatusState {
51 self.state
52 }
53
54 /// Idle → Pending (TRACK_STATUS sent).
55 pub fn on_track_status_sent(&mut self) -> Result<(), TrackStatusError> {
56 if self.state == TrackStatusState::Idle {
57 self.state = TrackStatusState::Pending;
58 Ok(())
59 } else {
60 Err(TrackStatusError::InvalidTransition {
61 from: self.state,
62 event: "on_track_status_sent".to_string(),
63 })
64 }
65 }
66
67 /// Pending → Done (TRACK_STATUS_OK received).
68 pub fn on_track_status_ok(&mut self) -> Result<(), TrackStatusError> {
69 if self.state == TrackStatusState::Pending {
70 self.state = TrackStatusState::Done;
71 Ok(())
72 } else {
73 Err(TrackStatusError::InvalidTransition {
74 from: self.state,
75 event: "on_track_status_ok".to_string(),
76 })
77 }
78 }
79
80 /// Pending → Done (TRACK_STATUS_ERROR received).
81 pub fn on_track_status_error(&mut self) -> Result<(), TrackStatusError> {
82 if self.state == TrackStatusState::Pending {
83 self.state = TrackStatusState::Done;
84 Ok(())
85 } else {
86 Err(TrackStatusError::InvalidTransition {
87 from: self.state,
88 event: "on_track_status_error".to_string(),
89 })
90 }
91 }
92}
93
94/// The same transitions, named for the end the request arrives at.
95///
96/// A track status the peer asks for passes through the states in the same
97/// order as one this endpoint asks for, with every message going the other
98/// way: the request arrives instead of leaving and the answer leaves instead
99/// of arriving. Sharing the transitions and not the names is what lets a
100/// refusal say which event was refused rather than the mirror image of it.
101///
102/// Section 8.20 says what the arriving request is: the receiver "treats it
103/// identically as if it had received a SUBSCRIBE message, except it does not
104/// create downstream subscription state or send any Objects". Identical
105/// treatment and no subscription state is why the request gets a machine of
106/// this kind rather than a subscription's, and why what it opens is a record
107/// of its own rather than an entry among the subscriptions the peer holds.
108impl TrackStatusStateMachine {
109 /// Idle → Pending (TRACK_STATUS received).
110 pub fn on_track_status_received(&mut self) -> Result<(), TrackStatusError> {
111 self.on_track_status_sent().map_err(|_| TrackStatusError::InvalidTransition {
112 from: self.state(),
113 event: "on_track_status_received".to_string(),
114 })
115 }
116
117 /// Pending → Done (TRACK_STATUS_OK sent).
118 pub fn on_track_status_ok_sent(&mut self) -> Result<(), TrackStatusError> {
119 self.on_track_status_ok().map_err(|_| TrackStatusError::InvalidTransition {
120 from: self.state(),
121 event: "on_track_status_ok_sent".to_string(),
122 })
123 }
124
125 /// Pending → Done (TRACK_STATUS_ERROR sent).
126 pub fn on_track_status_error_sent(&mut self) -> Result<(), TrackStatusError> {
127 self.on_track_status_error().map_err(|_| TrackStatusError::InvalidTransition {
128 from: self.state(),
129 event: "on_track_status_error_sent".to_string(),
130 })
131 }
132}