Skip to main content

moqtap_client/draft23/
publish.rs

1use moqtap_codec::kvp::{KeyValuePair, KvpValue};
2use moqtap_codec::types::Forward;
3use moqtap_codec::varint::VarInt;
4
5/// Publish lifecycle states (publisher side).
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum PublishState {
8    /// Initial state before any PUBLISH message is sent.
9    Idle,
10    /// PUBLISH has been sent; awaiting OK or ERROR.
11    Publishing,
12    /// PUBLISH_OK received; the track is being published.
13    Active,
14    /// Publish has ended (error, cancellation, or PUBLISH_DONE sent).
15    Done,
16}
17
18/// Errors that can occur during publish state transitions.
19#[derive(Debug, thiserror::Error, PartialEq, Eq)]
20pub enum PublishError {
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: PublishState,
26        /// The name of the event that was rejected.
27        event: String,
28    },
29}
30
31/// Pure state machine for a MoQT publish request (publisher side).
32/// Transitions: Idle -> Publishing -> Active -> Done.
33pub struct PublishStateMachine {
34    state: PublishState,
35}
36
37impl Default for PublishStateMachine {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl PublishStateMachine {
44    /// Creates a new state machine in the [`PublishState::Idle`] state.
45    pub fn new() -> Self {
46        Self { state: PublishState::Idle }
47    }
48
49    /// Returns the current state of the publish request.
50    pub fn state(&self) -> PublishState {
51        self.state
52    }
53
54    /// Idle -> Publishing (PUBLISH sent).
55    pub fn on_publish_sent(&mut self) -> Result<(), PublishError> {
56        if self.state == PublishState::Idle {
57            self.state = PublishState::Publishing;
58            Ok(())
59        } else {
60            Err(PublishError::InvalidTransition {
61                from: self.state,
62                event: "on_publish_sent".to_string(),
63            })
64        }
65    }
66
67    /// Publishing -> Active (PUBLISH_OK received).
68    pub fn on_publish_ok(&mut self) -> Result<(), PublishError> {
69        if self.state == PublishState::Publishing {
70            self.state = PublishState::Active;
71            Ok(())
72        } else {
73            Err(PublishError::InvalidTransition {
74                from: self.state,
75                event: "on_publish_ok".to_string(),
76            })
77        }
78    }
79
80    /// Publishing -> Done (REQUEST_ERROR received).
81    pub fn on_publish_error(&mut self) -> Result<(), PublishError> {
82        if self.state == PublishState::Publishing {
83            self.state = PublishState::Done;
84            Ok(())
85        } else {
86            Err(PublishError::InvalidTransition {
87                from: self.state,
88                event: "on_publish_error".to_string(),
89            })
90        }
91    }
92
93    /// Active -> Done (PUBLISH_DONE sent by publisher).
94    pub fn on_publish_done_sent(&mut self) -> Result<(), PublishError> {
95        if self.state == PublishState::Active {
96            self.state = PublishState::Done;
97            Ok(())
98        } else {
99            Err(PublishError::InvalidTransition {
100                from: self.state,
101                event: "on_publish_done_sent".to_string(),
102            })
103        }
104    }
105
106    /// Publishing | Active -> Done, Done -> Done (this request's stream was
107    /// cancelled).
108    ///
109    /// PUBLISH_DONE ends a publication the receiver accepted. This is the
110    /// request itself being withdrawn, which this draft puts at the stream —
111    /// Section 6.4.2.3: "Once a request
112    /// stream has been opened, the request MAY be cancelled by either endpoint."
113    ///
114    /// `Idle` is refused, on the other half of the same sentence: nothing has
115    /// been written, so there is no stream to terminate. `Done` stays `Done` —
116    /// nothing finishes a request stream's send half on the ordinary path, so a
117    /// caller that walks away from a request that has already ended still
118    /// resets the stream, and that reset is an ordinary end rather than a
119    /// fault.
120    pub fn on_request_cancelled(&mut self) -> Result<(), PublishError> {
121        match self.state {
122            PublishState::Publishing | PublishState::Active => {
123                self.state = PublishState::Done;
124                Ok(())
125            }
126            PublishState::Done => Ok(()),
127            PublishState::Idle => Err(PublishError::InvalidTransition {
128                from: self.state,
129                event: "on_request_cancelled".to_string(),
130            }),
131        }
132    }
133}
134
135/// The `PAUSED` parameter (0x11) that says what `forward` says.
136///
137/// Draft-23 Section 9.19.18: "The allowed values are 0 (unpaused) or 1
138/// (paused)." So [`Forward::DontForward`] is PAUSED = 1 and
139/// [`Forward::Forward`] PAUSED = 0, written out rather than left to the
140/// default.
141pub fn paused_parameter(forward: Forward) -> Vec<KeyValuePair> {
142    let paused = match forward {
143        Forward::Forward => 0,
144        Forward::DontForward => 1,
145    };
146    vec![KeyValuePair {
147        key: VarInt::from_u64(PAUSED).expect("0x11 fits in VarInt"),
148        value: KvpValue::Varint(VarInt::from_u64(paused).expect("0 or 1 fits in VarInt")),
149    }]
150}
151
152/// The `PAUSED` parameter type, draft-23 Section 9.19.18.
153pub const PAUSED: u64 = 0x11;