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;