Skip to main content

moqtap_client/draft11/
subscription.rs

1/// Subscription lifecycle states.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum SubscriptionState {
4    /// Initial state before any SUBSCRIBE message is sent.
5    Idle,
6    /// SUBSCRIBE has been sent; awaiting OK or ERROR.
7    Subscribing,
8    /// Subscription is accepted and data may be flowing.
9    Active,
10    /// Subscription has ended (error, unsubscribe, or subscribe done).
11    Done,
12}
13
14/// Errors that can occur during subscription state transitions.
15#[derive(Debug, thiserror::Error, PartialEq, Eq)]
16pub enum SubscriptionError {
17    /// An event was received that is not valid for the current state.
18    #[error("invalid transition from {from:?} on event {event}")]
19    InvalidTransition {
20        /// The state the machine was in when the invalid event arrived.
21        from: SubscriptionState,
22        /// The name of the event that was rejected.
23        event: String,
24    },
25}
26
27/// Pure state machine for a MoQT subscription (draft-10).
28/// Transitions: Idle → Subscribing → Active → Done.
29pub struct SubscriptionStateMachine {
30    state: SubscriptionState,
31}
32
33impl Default for SubscriptionStateMachine {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl SubscriptionStateMachine {
40    /// Creates a new state machine in the [`SubscriptionState::Idle`] state.
41    pub fn new() -> Self {
42        Self { state: SubscriptionState::Idle }
43    }
44
45    /// Returns the current state of the subscription.
46    pub fn state(&self) -> SubscriptionState {
47        self.state
48    }
49
50    /// Idle → Subscribing (SUBSCRIBE sent).
51    pub fn on_subscribe_sent(&mut self) -> Result<(), SubscriptionError> {
52        if self.state == SubscriptionState::Idle {
53            self.state = SubscriptionState::Subscribing;
54            Ok(())
55        } else {
56            Err(SubscriptionError::InvalidTransition {
57                from: self.state,
58                event: "on_subscribe_sent".to_string(),
59            })
60        }
61    }
62
63    /// Subscribing → Active (SUBSCRIBE_OK received).
64    pub fn on_subscribe_ok(&mut self) -> Result<(), SubscriptionError> {
65        if self.state == SubscriptionState::Subscribing {
66            self.state = SubscriptionState::Active;
67            Ok(())
68        } else {
69            Err(SubscriptionError::InvalidTransition {
70                from: self.state,
71                event: "on_subscribe_ok".to_string(),
72            })
73        }
74    }
75
76    /// Subscribing → Done (SUBSCRIBE_ERROR received).
77    pub fn on_subscribe_error(&mut self) -> Result<(), SubscriptionError> {
78        if self.state == SubscriptionState::Subscribing {
79            self.state = SubscriptionState::Done;
80            Ok(())
81        } else {
82            Err(SubscriptionError::InvalidTransition {
83                from: self.state,
84                event: "on_subscribe_error".to_string(),
85            })
86        }
87    }
88
89    /// Active → Done (UNSUBSCRIBE sent).
90    pub fn on_unsubscribe(&mut self) -> Result<(), SubscriptionError> {
91        if self.state == SubscriptionState::Active {
92            self.state = SubscriptionState::Done;
93            Ok(())
94        } else {
95            Err(SubscriptionError::InvalidTransition {
96                from: self.state,
97                event: "on_unsubscribe".to_string(),
98            })
99        }
100    }
101
102    /// Active → Active (SUBSCRIBE_UPDATE sent/received — self-transition).
103    pub fn on_subscribe_update(&mut self) -> Result<(), SubscriptionError> {
104        if self.state == SubscriptionState::Active {
105            Ok(())
106        } else {
107            Err(SubscriptionError::InvalidTransition {
108                from: self.state,
109                event: "on_subscribe_update".to_string(),
110            })
111        }
112    }
113
114    /// Active → Done (SUBSCRIBE_DONE received — publisher finished).
115    pub fn on_subscribe_done(&mut self) -> Result<(), SubscriptionError> {
116        if self.state == SubscriptionState::Active {
117            self.state = SubscriptionState::Done;
118            Ok(())
119        } else {
120            Err(SubscriptionError::InvalidTransition {
121                from: self.state,
122                event: "on_subscribe_done".to_string(),
123            })
124        }
125    }
126}