moqtap_client/draft20/track_status.rs
1/// TrackStatus lifecycle states.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum TrackStatusState {
4 /// Initial state before any TRACK_STATUS message is sent.
5 Idle,
6 /// TRACK_STATUS has been sent; awaiting OK or ERROR.
7 Pending,
8 /// Track status request has been answered or cancelled.
9 Done,
10}
11
12/// Errors that can occur during track status state transitions.
13#[derive(Debug, thiserror::Error, PartialEq, Eq)]
14pub enum TrackStatusError {
15 /// An event was received that is not valid for the current state.
16 #[error("invalid transition from {from:?} on event {event}")]
17 InvalidTransition {
18 /// The state the machine was in when the invalid event arrived.
19 from: TrackStatusState,
20 /// The name of the event that was rejected.
21 event: String,
22 },
23}
24
25/// Pure state machine for a MoQT track status request.
26/// Transitions: Idle -> Pending -> Done.
27pub struct TrackStatusStateMachine {
28 state: TrackStatusState,
29}
30
31impl Default for TrackStatusStateMachine {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl TrackStatusStateMachine {
38 /// Creates a new state machine in the [`TrackStatusState::Idle`] state.
39 pub fn new() -> Self {
40 Self { state: TrackStatusState::Idle }
41 }
42
43 /// Returns the current state of the track status request.
44 pub fn state(&self) -> TrackStatusState {
45 self.state
46 }
47
48 /// Idle -> Pending (TRACK_STATUS sent).
49 pub fn on_track_status_sent(&mut self) -> Result<(), TrackStatusError> {
50 if self.state == TrackStatusState::Idle {
51 self.state = TrackStatusState::Pending;
52 Ok(())
53 } else {
54 Err(TrackStatusError::InvalidTransition {
55 from: self.state,
56 event: "on_track_status_sent".to_string(),
57 })
58 }
59 }
60
61 /// Pending -> Done (REQUEST_OK received).
62 pub fn on_track_status_ok(&mut self) -> Result<(), TrackStatusError> {
63 if self.state == TrackStatusState::Pending {
64 self.state = TrackStatusState::Done;
65 Ok(())
66 } else {
67 Err(TrackStatusError::InvalidTransition {
68 from: self.state,
69 event: "on_track_status_ok".to_string(),
70 })
71 }
72 }
73
74 /// Pending -> Done (REQUEST_ERROR received).
75 pub fn on_track_status_error(&mut self) -> Result<(), TrackStatusError> {
76 if self.state == TrackStatusState::Pending {
77 self.state = TrackStatusState::Done;
78 Ok(())
79 } else {
80 Err(TrackStatusError::InvalidTransition {
81 from: self.state,
82 event: "on_track_status_error".to_string(),
83 })
84 }
85 }
86
87 /// Pending -> Done, Done -> Done (this request's stream was cancelled).
88 ///
89 /// A track status request owns a stream like any other — "A potential
90 /// subscriber sends TRACK_STATUS as the first and only message on a new
91 /// bidi stream" — and it is the one request kind no draft ever gave a
92 /// withdrawal message of its own. Section 3.3.3: "Once a request
93 /// stream has been opened, the request MAY be cancelled by either endpoint."
94 ///
95 /// `Idle` is refused, on the other half of the same sentence: nothing has
96 /// been written, so there is no stream to terminate. `Done` stays `Done` —
97 /// nothing finishes a request stream's send half on the ordinary path, so a
98 /// caller that walks away from a request that has already ended still
99 /// resets the stream, and that reset is an ordinary end rather than a
100 /// fault.
101 pub fn on_request_cancelled(&mut self) -> Result<(), TrackStatusError> {
102 match self.state {
103 TrackStatusState::Pending => {
104 self.state = TrackStatusState::Done;
105 Ok(())
106 }
107 TrackStatusState::Done => Ok(()),
108 TrackStatusState::Idle => Err(TrackStatusError::InvalidTransition {
109 from: self.state,
110 event: "on_request_cancelled".to_string(),
111 }),
112 }
113 }
114}