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!(matches!(
164 client_next_state(SessionState::Init, &Method::Play),
165 Err(crate::error::Error::MethodNotValidInState { .. })
166 ));
167 }
168
169 #[test]
170 fn client_pause_from_ready_bites() {
171 assert!(matches!(
172 client_next_state(SessionState::Ready, &Method::Pause),
173 Err(crate::error::Error::MethodNotValidInState { .. })
174 ));
175 }
176
177 #[test]
178 fn client_teardown_any_to_init() {
179 for s in [
180 SessionState::Ready,
181 SessionState::Playing,
182 SessionState::Recording,
183 ] {
184 assert_eq!(
185 client_next_state(s, &Method::Teardown).unwrap(),
186 SessionState::Init
187 );
188 }
189 }
190
191 #[test]
192 fn state_neutral_methods_never_change_state() {
193 for m in [
194 Method::Options,
195 Method::Describe,
196 Method::Announce,
197 Method::GetParameter,
198 Method::SetParameter,
199 ] {
200 for s in [
201 SessionState::Init,
202 SessionState::Ready,
203 SessionState::Playing,
204 ] {
205 assert_eq!(client_next_state(s, &m).unwrap(), s);
206 assert_eq!(server_next_state(s, &m).unwrap(), s);
207 }
208 }
209 }
210
211 #[test]
212 fn server_play_from_init_bites() {
213 assert!(matches!(
214 server_next_state(SessionState::Init, &Method::Play),
215 Err(crate::error::Error::MethodNotValidInState { .. })
216 ));
217 }
218}