Skip to main content

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}