Skip to main content

moqtap_client/draft09/
namespace.rs

1/// SUBSCRIBE_ANNOUNCES lifecycle states (draft-09).
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum SubscribeAnnouncesState {
4    /// Initial state before any message is sent.
5    Idle,
6    /// SUBSCRIBE_ANNOUNCES has been sent; awaiting OK or ERROR.
7    Pending,
8    /// Namespace subscription is accepted and active.
9    Active,
10    /// Namespace subscription has ended.
11    Done,
12}
13
14/// ANNOUNCE lifecycle states (draft-09).
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum AnnounceState {
17    /// Initial state before any message is sent.
18    Idle,
19    /// ANNOUNCE has been sent; awaiting ANNOUNCE_OK or ANNOUNCE_ERROR.
20    Pending,
21    /// Namespace publication is accepted and active.
22    Active,
23    /// Namespace publication has ended (UNANNOUNCE or cancel).
24    Done,
25}
26
27/// Errors that can occur during namespace state transitions.
28#[derive(Debug, thiserror::Error, PartialEq, Eq)]
29pub enum NamespaceError {
30    /// An event was received that is not valid for the current state.
31    #[error("invalid transition from {from} on event {event}")]
32    InvalidTransition {
33        /// The state the machine was in when the invalid event arrived.
34        from: String,
35        /// The name of the event that was rejected.
36        event: String,
37    },
38}
39
40/// State machine for the SUBSCRIBE_ANNOUNCES flow (draft-09).
41/// Idle → Pending → Active → Done.
42pub struct SubscribeAnnouncesStateMachine {
43    state: SubscribeAnnouncesState,
44}
45
46impl Default for SubscribeAnnouncesStateMachine {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52impl SubscribeAnnouncesStateMachine {
53    /// Creates a new machine in [`SubscribeAnnouncesState::Idle`].
54    pub fn new() -> Self {
55        Self { state: SubscribeAnnouncesState::Idle }
56    }
57
58    /// Returns the current state.
59    pub fn state(&self) -> SubscribeAnnouncesState {
60        self.state
61    }
62
63    /// Idle → Pending.
64    pub fn on_subscribe_announces_sent(&mut self) -> Result<(), NamespaceError> {
65        if self.state == SubscribeAnnouncesState::Idle {
66            self.state = SubscribeAnnouncesState::Pending;
67            Ok(())
68        } else {
69            Err(NamespaceError::InvalidTransition {
70                from: format!("{:?}", self.state),
71                event: "on_subscribe_announces_sent".to_string(),
72            })
73        }
74    }
75
76    /// Pending → Active.
77    pub fn on_subscribe_announces_ok(&mut self) -> Result<(), NamespaceError> {
78        if self.state == SubscribeAnnouncesState::Pending {
79            self.state = SubscribeAnnouncesState::Active;
80            Ok(())
81        } else {
82            Err(NamespaceError::InvalidTransition {
83                from: format!("{:?}", self.state),
84                event: "on_subscribe_announces_ok".to_string(),
85            })
86        }
87    }
88
89    /// Pending → Done.
90    pub fn on_subscribe_announces_error(&mut self) -> Result<(), NamespaceError> {
91        if self.state == SubscribeAnnouncesState::Pending {
92            self.state = SubscribeAnnouncesState::Done;
93            Ok(())
94        } else {
95            Err(NamespaceError::InvalidTransition {
96                from: format!("{:?}", self.state),
97                event: "on_subscribe_announces_error".to_string(),
98            })
99        }
100    }
101
102    /// Active → Done (UNSUBSCRIBE_ANNOUNCES sent).
103    pub fn on_unsubscribe_announces(&mut self) -> Result<(), NamespaceError> {
104        if self.state == SubscribeAnnouncesState::Active {
105            self.state = SubscribeAnnouncesState::Done;
106            Ok(())
107        } else {
108            Err(NamespaceError::InvalidTransition {
109                from: format!("{:?}", self.state),
110                event: "on_unsubscribe_announces".to_string(),
111            })
112        }
113    }
114
115    /// Idle → Pending (a SUBSCRIBE_ANNOUNCES arrived from the peer).
116    ///
117    /// The mirror of
118    /// [`on_subscribe_announces_sent`](Self::on_subscribe_announces_sent),
119    /// named for the direction it runs in rather than shared with it: the two
120    /// state edges coincide, so a message dispatched to the wrong one of them
121    /// would move the record silently instead of naming the event it was not.
122    pub fn on_subscribe_announces_received(&mut self) -> Result<(), NamespaceError> {
123        self.on_subscribe_announces_sent().map_err(|_| NamespaceError::InvalidTransition {
124            from: format!("{:?}", self.state()),
125            event: "on_subscribe_announces_received".to_string(),
126        })
127    }
128
129    /// Pending → Active (this endpoint accepted the peer's request with a
130    /// SUBSCRIBE_ANNOUNCES_OK).
131    ///
132    /// Section 4.1: "A publisher MUST send exactly one SUBSCRIBE_ANNOUNCES_OK
133    /// or SUBSCRIBE_ANNOUNCES_ERROR in response to a SUBSCRIBE_ANNOUNCES."
134    ///
135    /// One answer and no second one: the record leaves Pending on the first,
136    /// and a second call finds it somewhere else.
137    pub fn on_subscribe_announces_ok_sent(&mut self) -> Result<(), NamespaceError> {
138        self.on_subscribe_announces_ok().map_err(|_| NamespaceError::InvalidTransition {
139            from: format!("{:?}", self.state()),
140            event: "on_subscribe_announces_ok_sent".to_string(),
141        })
142    }
143
144    /// Pending → Done (this endpoint refused the peer's request with a
145    /// SUBSCRIBE_ANNOUNCES_ERROR).
146    ///
147    /// The other half of the same sentence: one message back, and this is the
148    /// other one it can be.
149    pub fn on_subscribe_announces_error_sent(&mut self) -> Result<(), NamespaceError> {
150        self.on_subscribe_announces_error().map_err(|_| NamespaceError::InvalidTransition {
151            from: format!("{:?}", self.state()),
152            event: "on_subscribe_announces_error_sent".to_string(),
153        })
154    }
155
156    /// Active → Done (the peer withdrew the namespace subscription with an
157    /// UNSUBSCRIBE_ANNOUNCES).
158    ///
159    /// Section 4.1: "An UNSUBSCRIBE_ANNOUNCES withdraws a previous
160    /// SUBSCRIBE_ANNOUNCES."
161    ///
162    /// Active is the acceptance, which is the state a namespace subscription
163    /// reaches by being answered SUBSCRIBE_ANNOUNCES_OK and no other way, so
164    /// a withdrawal of one never answered is refused here.
165    pub fn on_unsubscribe_announces_received(&mut self) -> Result<(), NamespaceError> {
166        self.on_unsubscribe_announces().map_err(|_| NamespaceError::InvalidTransition {
167            from: format!("{:?}", self.state()),
168            event: "on_unsubscribe_announces_received".to_string(),
169        })
170    }
171}
172
173/// State machine for the ANNOUNCE flow (draft-09).
174/// Idle → Pending → Active → Done.
175pub struct AnnounceStateMachine {
176    state: AnnounceState,
177}
178
179impl Default for AnnounceStateMachine {
180    fn default() -> Self {
181        Self::new()
182    }
183}
184
185impl AnnounceStateMachine {
186    /// Creates a new machine in [`AnnounceState::Idle`].
187    pub fn new() -> Self {
188        Self { state: AnnounceState::Idle }
189    }
190
191    /// Returns the current state.
192    pub fn state(&self) -> AnnounceState {
193        self.state
194    }
195
196    /// Idle → Pending (ANNOUNCE sent).
197    pub fn on_announce_sent(&mut self) -> Result<(), NamespaceError> {
198        if self.state == AnnounceState::Idle {
199            self.state = AnnounceState::Pending;
200            Ok(())
201        } else {
202            Err(NamespaceError::InvalidTransition {
203                from: format!("{:?}", self.state),
204                event: "on_announce_sent".to_string(),
205            })
206        }
207    }
208
209    /// Pending → Active (ANNOUNCE_OK received).
210    pub fn on_announce_ok(&mut self) -> Result<(), NamespaceError> {
211        if self.state == AnnounceState::Pending {
212            self.state = AnnounceState::Active;
213            Ok(())
214        } else {
215            Err(NamespaceError::InvalidTransition {
216                from: format!("{:?}", self.state),
217                event: "on_announce_ok".to_string(),
218            })
219        }
220    }
221
222    /// Pending → Done (ANNOUNCE_ERROR received).
223    pub fn on_announce_error(&mut self) -> Result<(), NamespaceError> {
224        if self.state == AnnounceState::Pending {
225            self.state = AnnounceState::Done;
226            Ok(())
227        } else {
228            Err(NamespaceError::InvalidTransition {
229                from: format!("{:?}", self.state),
230                event: "on_announce_error".to_string(),
231            })
232        }
233    }
234
235    /// Active → Done (UNANNOUNCE sent — publisher withdrawing).
236    pub fn on_unannounce(&mut self) -> Result<(), NamespaceError> {
237        if self.state == AnnounceState::Active {
238            self.state = AnnounceState::Done;
239            Ok(())
240        } else {
241            Err(NamespaceError::InvalidTransition {
242                from: format!("{:?}", self.state),
243                event: "on_unannounce".to_string(),
244            })
245        }
246    }
247
248    /// Active → Done (ANNOUNCE_CANCEL received — subscriber cancelling).
249    pub fn on_announce_cancel(&mut self) -> Result<(), NamespaceError> {
250        if self.state == AnnounceState::Active {
251            self.state = AnnounceState::Done;
252            Ok(())
253        } else {
254            Err(NamespaceError::InvalidTransition {
255                from: format!("{:?}", self.state),
256                event: "on_announce_cancel".to_string(),
257            })
258        }
259    }
260}
261
262/// The same transitions, named for the end the advertisement arrives at.
263///
264/// An announcement this endpoint accepts passes through the states in the same
265/// order as one it makes, with every message going the other way: the ANNOUNCE
266/// arrives instead of leaving, the answer leaves instead of arriving, the
267/// withdrawal arrives and the cancellation leaves. Sharing the transitions and
268/// not the names is what lets a refusal say which event was refused, rather
269/// than naming the mirror image of it.
270impl AnnounceStateMachine {
271    /// Idle -> Pending (ANNOUNCE received from the peer).
272    pub fn on_announce_received(&mut self) -> Result<(), NamespaceError> {
273        self.on_announce_sent().map_err(|_| NamespaceError::InvalidTransition {
274            from: format!("{:?}", self.state()),
275            event: "on_announce_received".to_string(),
276        })
277    }
278
279    /// Pending -> Active (ANNOUNCE_OK sent, accepting the announcement).
280    pub fn on_announce_ok_sent(&mut self) -> Result<(), NamespaceError> {
281        self.on_announce_ok().map_err(|_| NamespaceError::InvalidTransition {
282            from: format!("{:?}", self.state()),
283            event: "on_announce_ok_sent".to_string(),
284        })
285    }
286
287    /// Pending -> Done (ANNOUNCE_ERROR sent, refusing the announcement).
288    pub fn on_announce_error_sent(&mut self) -> Result<(), NamespaceError> {
289        self.on_announce_error().map_err(|_| NamespaceError::InvalidTransition {
290            from: format!("{:?}", self.state()),
291            event: "on_announce_error_sent".to_string(),
292        })
293    }
294
295    /// Active -> Done (UNANNOUNCE received, the peer withdrawing).
296    pub fn on_unannounce_received(&mut self) -> Result<(), NamespaceError> {
297        self.on_unannounce().map_err(|_| NamespaceError::InvalidTransition {
298            from: format!("{:?}", self.state()),
299            event: "on_unannounce_received".to_string(),
300        })
301    }
302
303    /// Active -> Done (ANNOUNCE_CANCEL sent, revoking an acceptance).
304    ///
305    /// Active is the acceptance: it is the state an announcement reaches by
306    /// being answered ANNOUNCE_OK and no other way, which is why a cancellation
307    /// of one never answered is refused here rather than sent.
308    pub fn on_announce_cancel_sent(&mut self) -> Result<(), NamespaceError> {
309        self.on_announce_cancel().map_err(|_| NamespaceError::InvalidTransition {
310            from: format!("{:?}", self.state()),
311            event: "on_announce_cancel_sent".to_string(),
312        })
313    }
314}