moqtap_client/draft12/track_status.rs
1/// TrackStatus lifecycle states (draft-12).
2///
3/// TRACK_STATUS is a single request/response pair:
4/// the requester sends TRACK_STATUS_REQUEST, and the publisher
5/// replies with TRACK_STATUS. There are no OK / ERROR variants.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum TrackStatusState {
8 /// Initial state before any TRACK_STATUS_REQUEST is sent.
9 Idle,
10 /// TRACK_STATUS_REQUEST has been sent; awaiting TRACK_STATUS reply.
11 Pending,
12 /// Track status reply received.
13 Done,
14}
15
16/// Errors that can occur during track status state transitions.
17#[derive(Debug, thiserror::Error, PartialEq, Eq)]
18pub enum TrackStatusError {
19 /// An event was received that is not valid for the current state.
20 #[error("invalid transition from {from:?} on event {event}")]
21 InvalidTransition {
22 /// The state the machine was in when the invalid event arrived.
23 from: TrackStatusState,
24 /// The name of the event that was rejected.
25 event: String,
26 },
27}
28
29/// Pure state machine for a MoQT track status request (draft-12).
30/// Transitions: Idle → Pending → Done.
31pub struct TrackStatusStateMachine {
32 state: TrackStatusState,
33}
34
35impl Default for TrackStatusStateMachine {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl TrackStatusStateMachine {
42 /// Creates a new state machine in the [`TrackStatusState::Idle`] state.
43 pub fn new() -> Self {
44 Self { state: TrackStatusState::Idle }
45 }
46
47 /// Returns the current state of the track status request.
48 pub fn state(&self) -> TrackStatusState {
49 self.state
50 }
51
52 /// Idle → Pending (TRACK_STATUS_REQUEST sent).
53 pub fn on_track_status_request_sent(&mut self) -> Result<(), TrackStatusError> {
54 if self.state == TrackStatusState::Idle {
55 self.state = TrackStatusState::Pending;
56 Ok(())
57 } else {
58 Err(TrackStatusError::InvalidTransition {
59 from: self.state,
60 event: "on_track_status_request_sent".to_string(),
61 })
62 }
63 }
64
65 /// Pending → Done (TRACK_STATUS received).
66 pub fn on_track_status(&mut self) -> Result<(), TrackStatusError> {
67 if self.state == TrackStatusState::Pending {
68 self.state = TrackStatusState::Done;
69 Ok(())
70 } else {
71 Err(TrackStatusError::InvalidTransition {
72 from: self.state,
73 event: "on_track_status".to_string(),
74 })
75 }
76 }
77}
78
79/// The same transitions, named for the end the request arrives at.
80///
81/// A track status the peer asks for passes through the states in the same
82/// order as one this endpoint asks for, with both messages going the other
83/// way: the request arrives instead of leaving, and the answer leaves instead
84/// of arriving. Sharing the transitions and not the names is what lets a
85/// refusal say which event was refused rather than the mirror image of it.
86///
87/// Section 8.20 leaves the answering end no discretion about whether to
88/// answer: "A TRACK_STATUS message MUST be sent in response to each
89/// TRACK_STATUS_REQUEST." What it bounds is how many, and that is what `Done`
90/// is for: a second answer finds a request that has already left `Pending`.
91impl TrackStatusStateMachine {
92 /// Idle → Pending (TRACK_STATUS_REQUEST received).
93 pub fn on_track_status_request_received(&mut self) -> Result<(), TrackStatusError> {
94 self.on_track_status_request_sent().map_err(|_| TrackStatusError::InvalidTransition {
95 from: self.state(),
96 event: "on_track_status_request_received".to_string(),
97 })
98 }
99
100 /// Pending → Done (TRACK_STATUS sent).
101 pub fn on_track_status_sent(&mut self) -> Result<(), TrackStatusError> {
102 self.on_track_status().map_err(|_| TrackStatusError::InvalidTransition {
103 from: self.state(),
104 event: "on_track_status_sent".to_string(),
105 })
106 }
107}