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}