1use crate::Method;
14use crate::error::{Error, Result};
15
16#[non_exhaustive]
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub enum SessionState {
21 #[default]
24 Init,
25 Ready,
28 Playing,
30 Recording,
32}
33
34impl SessionState {
35 pub fn name(&self) -> &'static str {
37 match self {
38 SessionState::Init => "Init",
39 SessionState::Ready => "Ready",
40 SessionState::Playing => "Playing",
41 SessionState::Recording => "Recording",
42 }
43 }
44}
45
46impl core::fmt::Display for SessionState {
47 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48 f.write_str(self.name())
49 }
50}
51
52pub fn is_state_neutral(method: &Method) -> bool {
55 matches!(
56 method,
57 Method::Options
58 | Method::Describe
59 | Method::Announce
60 | Method::GetParameter
61 | Method::SetParameter
62 )
63}
64
65pub fn client_next_state(current: SessionState, method: &Method) -> Result<SessionState> {
77 if is_state_neutral(method) {
78 return Ok(current);
79 }
80 let next = match (current, method) {
81 (SessionState::Init, Method::Setup) => SessionState::Ready,
83 (SessionState::Init, Method::Teardown) => SessionState::Init,
84 (SessionState::Ready, Method::Play) => SessionState::Playing,
86 (SessionState::Ready, Method::Record) => SessionState::Recording,
87 (SessionState::Ready, Method::Teardown) => SessionState::Init,
88 (SessionState::Ready, Method::Setup) => SessionState::Ready,
89 (SessionState::Playing, Method::Pause) => SessionState::Ready,
91 (SessionState::Playing, Method::Teardown) => SessionState::Init,
92 (SessionState::Playing, Method::Play) => SessionState::Playing,
93 (SessionState::Playing, Method::Setup) => SessionState::Playing, (SessionState::Recording, Method::Pause) => SessionState::Ready,
96 (SessionState::Recording, Method::Teardown) => SessionState::Init,
97 (SessionState::Recording, Method::Record) => SessionState::Recording,
98 (SessionState::Recording, Method::Setup) => SessionState::Recording, _ => {
100 return Err(Error::MethodNotValidInState {
101 method: method.clone(),
102 state: current,
103 });
104 }
105 };
106 Ok(next)
107}
108
109pub fn server_next_state(current: SessionState, method: &Method) -> Result<SessionState> {
117 if is_state_neutral(method) {
118 return Ok(current);
119 }
120 let next = match (current, method) {
121 (SessionState::Init, Method::Setup) => SessionState::Ready,
123 (SessionState::Init, Method::Teardown) => SessionState::Init,
124 (SessionState::Ready, Method::Play) => SessionState::Playing,
126 (SessionState::Ready, Method::Setup) => SessionState::Ready,
127 (SessionState::Ready, Method::Teardown) => SessionState::Init,
128 (SessionState::Ready, Method::Record) => SessionState::Recording,
129 (SessionState::Playing, Method::Play) => SessionState::Playing,
131 (SessionState::Playing, Method::Pause) => SessionState::Ready,
132 (SessionState::Playing, Method::Teardown) => SessionState::Init,
133 (SessionState::Playing, Method::Setup) => SessionState::Playing,
134 (SessionState::Recording, Method::Record) => SessionState::Recording,
136 (SessionState::Recording, Method::Pause) => SessionState::Ready,
137 (SessionState::Recording, Method::Teardown) => SessionState::Init,
138 (SessionState::Recording, Method::Setup) => SessionState::Recording,
139 _ => {
140 return Err(Error::MethodNotValidInState {
141 method: method.clone(),
142 state: current,
143 });
144 }
145 };
146 Ok(next)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn client_setup_from_init_is_ready() {
155 assert_eq!(
156 client_next_state(SessionState::Init, &Method::Setup).unwrap(),
157 SessionState::Ready
158 );
159 }
160
161 #[test]
162 fn client_play_from_init_bites() {
163 assert!(client_next_state(SessionState::Init, &Method::Play).is_err());
164 }
165
166 #[test]
167 fn client_pause_from_ready_bites() {
168 assert!(client_next_state(SessionState::Ready, &Method::Pause).is_err());
169 }
170
171 #[test]
172 fn client_teardown_any_to_init() {
173 for s in [
174 SessionState::Ready,
175 SessionState::Playing,
176 SessionState::Recording,
177 ] {
178 assert_eq!(
179 client_next_state(s, &Method::Teardown).unwrap(),
180 SessionState::Init
181 );
182 }
183 }
184
185 #[test]
186 fn state_neutral_methods_never_change_state() {
187 for m in [
188 Method::Options,
189 Method::Describe,
190 Method::Announce,
191 Method::GetParameter,
192 Method::SetParameter,
193 ] {
194 for s in [
195 SessionState::Init,
196 SessionState::Ready,
197 SessionState::Playing,
198 ] {
199 assert_eq!(client_next_state(s, &m).unwrap(), s);
200 assert_eq!(server_next_state(s, &m).unwrap(), s);
201 }
202 }
203 }
204
205 #[test]
206 fn server_play_from_init_bites() {
207 assert!(server_next_state(SessionState::Init, &Method::Play).is_err());
208 }
209}