Skip to main content

moqtap_client/draft12/
publish.rs

1/// Where a subscription a PUBLISH opened has got to, whichever end sent it.
2///
3/// Section 4.1: "A subscription can be initiated by either a publisher or a
4/// subscriber. A publisher initiates a subscription to a track by sending the
5/// PUBLISH message. The subscriber either accepts or rejects the subscription
6/// using PUBLISH_OK or PUBLISH_ERROR." Both sides of that sentence are here.
7/// The transitions are named for who acted rather than for the state they
8/// reach, so the two directions have two names for each step and a refused
9/// transition names the event the caller actually attempted.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PublishState {
12    /// No PUBLISH has been sent or received yet.
13    Idle,
14    /// A PUBLISH is outstanding: sent and not answered, or arrived and not
15    /// answered.
16    Publishing,
17    /// A PUBLISH_OK has been sent or received: the subscription is live.
18    Active,
19    /// The subscription is over, by refusal or by either end ending it.
20    Done,
21}
22
23/// Errors that can occur during publish state transitions.
24#[derive(Debug, thiserror::Error, PartialEq, Eq)]
25pub enum PublishError {
26    /// An event was received that is not valid for the current state.
27    #[error("invalid transition from {from:?} on event {event}")]
28    InvalidTransition {
29        /// The state the machine was in when the invalid event arrived.
30        from: PublishState,
31        /// The name of the event that was rejected.
32        event: String,
33    },
34}
35
36/// Pure state machine for a subscription a PUBLISH opened.
37/// Transitions: Idle -> Publishing -> Active -> Done.
38///
39/// The two ends of the flow are the two halves of Section 4.1's sentence: "A
40/// subscriber MUST send exactly one PUBLISH_OK or PUBLISH_ERROR in response to
41/// a PUBLISH", and "the subscription can be ... terminated by the subscriber
42/// using UNSUBSCRIBE, or terminated by the publisher using SUBSCRIBE_DONE".
43/// Each event has a transition of its own even where two of them land in the
44/// same state, so that a refusal names the event that was refused, and so that
45/// each direction of the flow names its own half of it.
46pub struct PublishStateMachine {
47    state: PublishState,
48}
49
50impl Default for PublishStateMachine {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl PublishStateMachine {
57    /// Creates a new state machine in the [`PublishState::Idle`] state.
58    pub fn new() -> Self {
59        Self { state: PublishState::Idle }
60    }
61
62    /// Returns the current state of the subscription.
63    pub fn state(&self) -> PublishState {
64        self.state
65    }
66
67    fn step(
68        &mut self,
69        from: PublishState,
70        to: PublishState,
71        event: &str,
72    ) -> Result<(), PublishError> {
73        if self.state == from {
74            self.state = to;
75            Ok(())
76        } else {
77            Err(PublishError::InvalidTransition { from: self.state, event: event.to_string() })
78        }
79    }
80
81    /// Idle -> Publishing (PUBLISH received from the peer).
82    pub fn on_publish_received(&mut self) -> Result<(), PublishError> {
83        self.step(PublishState::Idle, PublishState::Publishing, "on_publish_received")
84    }
85
86    /// Publishing -> Active (this endpoint answered PUBLISH_OK).
87    pub fn on_publish_ok_sent(&mut self) -> Result<(), PublishError> {
88        self.step(PublishState::Publishing, PublishState::Active, "on_publish_ok_sent")
89    }
90
91    /// Publishing -> Done (this endpoint answered PUBLISH_ERROR).
92    pub fn on_publish_error_sent(&mut self) -> Result<(), PublishError> {
93        self.step(PublishState::Publishing, PublishState::Done, "on_publish_error_sent")
94    }
95
96    /// Active -> Done (this endpoint sent UNSUBSCRIBE).
97    ///
98    /// Only from Active: the draft gives the subscriber UNSUBSCRIBE for a
99    /// subscription that is established, and a PUBLISH it has not answered yet
100    /// is refused with PUBLISH_ERROR instead.
101    pub fn on_unsubscribe_sent(&mut self) -> Result<(), PublishError> {
102        self.step(PublishState::Active, PublishState::Done, "on_unsubscribe_sent")
103    }
104
105    /// Active -> Done (the publishing peer sent SUBSCRIBE_DONE).
106    pub fn on_subscribe_done_received(&mut self) -> Result<(), PublishError> {
107        self.step(PublishState::Active, PublishState::Done, "on_subscribe_done_received")
108    }
109
110    /// Idle -> Publishing (PUBLISH sent to the peer).
111    ///
112    /// The mirror of [`Self::on_publish_received`], and the same step: Section
113    /// 4.1 opens with "A subscription can be initiated by either a publisher
114    /// or a subscriber", so an offer looks the same from both ends and only
115    /// the event name says which end made it.
116    pub fn on_publish_sent(&mut self) -> Result<(), PublishError> {
117        self.step(PublishState::Idle, PublishState::Publishing, "on_publish_sent")
118    }
119
120    /// Publishing -> Active (the subscribing peer answered PUBLISH_OK).
121    pub fn on_publish_ok(&mut self) -> Result<(), PublishError> {
122        self.step(PublishState::Publishing, PublishState::Active, "on_publish_ok")
123    }
124
125    /// Publishing -> Done (the subscribing peer answered PUBLISH_ERROR).
126    ///
127    /// The offer is over rather than pending. Section 4.1: "Objects MUST NOT
128    /// be sent for requests that end with an error."
129    pub fn on_publish_error(&mut self) -> Result<(), PublishError> {
130        self.step(PublishState::Publishing, PublishState::Done, "on_publish_error")
131    }
132
133    /// Active -> Done (this endpoint, as the publisher, sent SUBSCRIBE_DONE).
134    pub fn on_subscribe_done_sent(&mut self) -> Result<(), PublishError> {
135        self.step(PublishState::Active, PublishState::Done, "on_subscribe_done_sent")
136    }
137
138    /// Active -> Done (the subscribing peer sent UNSUBSCRIBE).
139    ///
140    /// The mirror of [`Self::on_unsubscribe_sent`] and the same step, on a
141    /// subscription this endpoint opened rather than one it took. Section 8.11
142    /// gives the message to the subscriber alone, so which end sends it
143    /// follows from which end made the offer.
144    pub fn on_unsubscribe_received(&mut self) -> Result<(), PublishError> {
145        self.step(PublishState::Active, PublishState::Done, "on_unsubscribe_received")
146    }
147}