moqtap_client/draft18/session/state.rs
1/// Session lifecycle states.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum SessionState {
4 /// Initial state before the QUIC connection is established.
5 Connecting,
6 /// Exchanging CLIENT_SETUP and SERVER_SETUP messages.
7 SetupExchange,
8 /// Session is fully established and can exchange data.
9 Active,
10 /// GOAWAY received; finishing in-flight requests before closing.
11 Draining,
12 /// Session is terminated.
13 Closed,
14}
15
16/// Errors arising from invalid session state transitions.
17#[derive(Debug, thiserror::Error, PartialEq, Eq)]
18pub enum SessionError {
19 /// Attempted a state transition that is not allowed.
20 #[error("invalid state transition from {from:?} to {to:?}")]
21 InvalidTransition {
22 /// The state the session was in.
23 from: SessionState,
24 /// The state the transition targeted.
25 to: SessionState,
26 },
27}
28
29/// Pure state machine for MoQT session lifecycle.
30/// Transitions: Connecting -> SetupExchange -> Active -> Draining -> Closed.
31pub struct SessionStateMachine {
32 state: SessionState,
33}
34
35impl Default for SessionStateMachine {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl SessionStateMachine {
42 /// Create a new state machine starting in the `Connecting` state.
43 pub fn new() -> Self {
44 Self { state: SessionState::Connecting }
45 }
46
47 /// Returns the current session state.
48 pub fn state(&self) -> SessionState {
49 self.state
50 }
51
52 /// Transition: Connecting -> SetupExchange.
53 pub fn on_connect(&mut self) -> Result<(), SessionError> {
54 if self.state == SessionState::Connecting {
55 self.state = SessionState::SetupExchange;
56 Ok(())
57 } else {
58 Err(SessionError::InvalidTransition {
59 from: self.state,
60 to: SessionState::SetupExchange,
61 })
62 }
63 }
64
65 /// Transition: SetupExchange -> Active.
66 pub fn on_setup_complete(&mut self) -> Result<(), SessionError> {
67 if self.state == SessionState::SetupExchange {
68 self.state = SessionState::Active;
69 Ok(())
70 } else {
71 Err(SessionError::InvalidTransition { from: self.state, to: SessionState::Active })
72 }
73 }
74
75 /// Transition: Active -> Draining (GOAWAY received).
76 pub fn on_goaway(&mut self) -> Result<(), SessionError> {
77 if self.state == SessionState::Active {
78 self.state = SessionState::Draining;
79 Ok(())
80 } else {
81 Err(SessionError::InvalidTransition { from: self.state, to: SessionState::Draining })
82 }
83 }
84
85 /// Transition: SetupExchange|Active|Draining -> Closed.
86 ///
87 /// draft-18 Section 3.5: "The Transport Session can be terminated at any point."
88 /// The same section lists VERSION_NEGOTIATION_FAILED (0x15), "The client didn't
89 /// offer a version supported by the server" — a failure reachable only from
90 /// SetupExchange, before any session is Active. A session that cannot close there
91 /// cannot record the one outcome that code exists for.
92 ///
93 /// Connecting is still refused: no transport session has been established, so there
94 /// is nothing to terminate.
95 pub fn on_close(&mut self) -> Result<(), SessionError> {
96 if matches!(
97 self.state,
98 SessionState::SetupExchange | SessionState::Active | SessionState::Draining
99 ) {
100 self.state = SessionState::Closed;
101 Ok(())
102 } else {
103 Err(SessionError::InvalidTransition { from: self.state, to: SessionState::Closed })
104 }
105 }
106}