use crate::error::{Error, Result};
use crate::Method;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SessionState {
#[default]
Init,
Ready,
Playing,
Recording,
}
impl SessionState {
pub fn name(&self) -> &'static str {
match self {
SessionState::Init => "Init",
SessionState::Ready => "Ready",
SessionState::Playing => "Playing",
SessionState::Recording => "Recording",
}
}
}
impl core::fmt::Display for SessionState {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
pub fn is_state_neutral(method: &Method) -> bool {
matches!(
method,
Method::Options
| Method::Describe
| Method::Announce
| Method::GetParameter
| Method::SetParameter
)
}
pub fn client_next_state(current: SessionState, method: &Method) -> Result<SessionState> {
if is_state_neutral(method) {
return Ok(current);
}
let next = match (current, method) {
(SessionState::Init, Method::Setup) => SessionState::Ready,
(SessionState::Init, Method::Teardown) => SessionState::Init,
(SessionState::Ready, Method::Play) => SessionState::Playing,
(SessionState::Ready, Method::Record) => SessionState::Recording,
(SessionState::Ready, Method::Teardown) => SessionState::Init,
(SessionState::Ready, Method::Setup) => SessionState::Ready,
(SessionState::Playing, Method::Pause) => SessionState::Ready,
(SessionState::Playing, Method::Teardown) => SessionState::Init,
(SessionState::Playing, Method::Play) => SessionState::Playing,
(SessionState::Playing, Method::Setup) => SessionState::Playing, (SessionState::Recording, Method::Pause) => SessionState::Ready,
(SessionState::Recording, Method::Teardown) => SessionState::Init,
(SessionState::Recording, Method::Record) => SessionState::Recording,
(SessionState::Recording, Method::Setup) => SessionState::Recording, _ => {
return Err(Error::MethodNotValidInState {
method: method.clone(),
state: current,
})
}
};
Ok(next)
}
pub fn server_next_state(current: SessionState, method: &Method) -> Result<SessionState> {
if is_state_neutral(method) {
return Ok(current);
}
let next = match (current, method) {
(SessionState::Init, Method::Setup) => SessionState::Ready,
(SessionState::Init, Method::Teardown) => SessionState::Init,
(SessionState::Ready, Method::Play) => SessionState::Playing,
(SessionState::Ready, Method::Setup) => SessionState::Ready,
(SessionState::Ready, Method::Teardown) => SessionState::Init,
(SessionState::Ready, Method::Record) => SessionState::Recording,
(SessionState::Playing, Method::Play) => SessionState::Playing,
(SessionState::Playing, Method::Pause) => SessionState::Ready,
(SessionState::Playing, Method::Teardown) => SessionState::Init,
(SessionState::Playing, Method::Setup) => SessionState::Playing,
(SessionState::Recording, Method::Record) => SessionState::Recording,
(SessionState::Recording, Method::Pause) => SessionState::Ready,
(SessionState::Recording, Method::Teardown) => SessionState::Init,
(SessionState::Recording, Method::Setup) => SessionState::Recording,
_ => {
return Err(Error::MethodNotValidInState {
method: method.clone(),
state: current,
})
}
};
Ok(next)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_setup_from_init_is_ready() {
assert_eq!(
client_next_state(SessionState::Init, &Method::Setup).unwrap(),
SessionState::Ready
);
}
#[test]
fn client_play_from_init_bites() {
assert!(client_next_state(SessionState::Init, &Method::Play).is_err());
}
#[test]
fn client_pause_from_ready_bites() {
assert!(client_next_state(SessionState::Ready, &Method::Pause).is_err());
}
#[test]
fn client_teardown_any_to_init() {
for s in [
SessionState::Ready,
SessionState::Playing,
SessionState::Recording,
] {
assert_eq!(
client_next_state(s, &Method::Teardown).unwrap(),
SessionState::Init
);
}
}
#[test]
fn state_neutral_methods_never_change_state() {
for m in [
Method::Options,
Method::Describe,
Method::Announce,
Method::GetParameter,
Method::SetParameter,
] {
for s in [
SessionState::Init,
SessionState::Ready,
SessionState::Playing,
] {
assert_eq!(client_next_state(s, &m).unwrap(), s);
assert_eq!(server_next_state(s, &m).unwrap(), s);
}
}
}
#[test]
fn server_play_from_init_bites() {
assert!(server_next_state(SessionState::Init, &Method::Play).is_err());
}
}