use crate::error::{Error, Result};
use crate::peer_connection::sdp::sdp_type::RTCSdpType;
use std::fmt;
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum StateChangeOp {
SetLocal,
SetRemote,
}
impl Default for StateChangeOp {
fn default() -> Self {
StateChangeOp::SetLocal
}
}
impl fmt::Display for StateChangeOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
StateChangeOp::SetLocal => write!(f, "SetLocal"),
StateChangeOp::SetRemote => write!(f, "SetRemote"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RTCSignalingState {
Unspecified = 0,
Stable,
HaveLocalOffer,
HaveRemoteOffer,
HaveLocalPranswer,
HaveRemotePranswer,
Closed,
}
impl Default for RTCSignalingState {
fn default() -> Self {
RTCSignalingState::Unspecified
}
}
const SIGNALING_STATE_STABLE_STR: &str = "stable";
const SIGNALING_STATE_HAVE_LOCAL_OFFER_STR: &str = "have-local-offer";
const SIGNALING_STATE_HAVE_REMOTE_OFFER_STR: &str = "have-remote-offer";
const SIGNALING_STATE_HAVE_LOCAL_PRANSWER_STR: &str = "have-local-pranswer";
const SIGNALING_STATE_HAVE_REMOTE_PRANSWER_STR: &str = "have-remote-pranswer";
const SIGNALING_STATE_CLOSED_STR: &str = "closed";
impl From<&str> for RTCSignalingState {
fn from(raw: &str) -> Self {
match raw {
SIGNALING_STATE_STABLE_STR => RTCSignalingState::Stable,
SIGNALING_STATE_HAVE_LOCAL_OFFER_STR => RTCSignalingState::HaveLocalOffer,
SIGNALING_STATE_HAVE_REMOTE_OFFER_STR => RTCSignalingState::HaveRemoteOffer,
SIGNALING_STATE_HAVE_LOCAL_PRANSWER_STR => RTCSignalingState::HaveLocalPranswer,
SIGNALING_STATE_HAVE_REMOTE_PRANSWER_STR => RTCSignalingState::HaveRemotePranswer,
SIGNALING_STATE_CLOSED_STR => RTCSignalingState::Closed,
_ => RTCSignalingState::Unspecified,
}
}
}
impl fmt::Display for RTCSignalingState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RTCSignalingState::Stable => write!(f, "{}", SIGNALING_STATE_STABLE_STR),
RTCSignalingState::HaveLocalOffer => {
write!(f, "{}", SIGNALING_STATE_HAVE_LOCAL_OFFER_STR)
}
RTCSignalingState::HaveRemoteOffer => {
write!(f, "{}", SIGNALING_STATE_HAVE_REMOTE_OFFER_STR)
}
RTCSignalingState::HaveLocalPranswer => {
write!(f, "{}", SIGNALING_STATE_HAVE_LOCAL_PRANSWER_STR)
}
RTCSignalingState::HaveRemotePranswer => {
write!(f, "{}", SIGNALING_STATE_HAVE_REMOTE_PRANSWER_STR)
}
RTCSignalingState::Closed => write!(f, "{}", SIGNALING_STATE_CLOSED_STR),
_ => write!(f, "{}", crate::UNSPECIFIED_STR),
}
}
}
impl From<u8> for RTCSignalingState {
fn from(v: u8) -> Self {
match v {
1 => RTCSignalingState::Stable,
2 => RTCSignalingState::HaveLocalOffer,
3 => RTCSignalingState::HaveRemoteOffer,
4 => RTCSignalingState::HaveLocalPranswer,
5 => RTCSignalingState::HaveRemotePranswer,
6 => RTCSignalingState::Closed,
_ => RTCSignalingState::Unspecified,
}
}
}
pub(crate) fn check_next_signaling_state(
cur: RTCSignalingState,
next: RTCSignalingState,
op: StateChangeOp,
sdp_type: RTCSdpType,
) -> Result<RTCSignalingState> {
if sdp_type == RTCSdpType::Rollback && cur == RTCSignalingState::Stable {
return Err(Error::ErrSignalingStateCannotRollback);
}
match cur {
RTCSignalingState::Stable => {
match op {
StateChangeOp::SetLocal => {
if sdp_type == RTCSdpType::Offer && next == RTCSignalingState::HaveLocalOffer {
return Ok(next);
}
}
StateChangeOp::SetRemote => {
if sdp_type == RTCSdpType::Offer && next == RTCSignalingState::HaveRemoteOffer {
return Ok(next);
}
}
}
}
RTCSignalingState::HaveLocalOffer => {
if op == StateChangeOp::SetRemote {
match sdp_type {
RTCSdpType::Answer => {
if next == RTCSignalingState::Stable {
return Ok(next);
}
}
RTCSdpType::Pranswer => {
if next == RTCSignalingState::HaveRemotePranswer {
return Ok(next);
}
}
_ => {}
}
}
}
RTCSignalingState::HaveRemotePranswer => {
if op == StateChangeOp::SetRemote && sdp_type == RTCSdpType::Answer {
if next == RTCSignalingState::Stable {
return Ok(next);
}
}
}
RTCSignalingState::HaveRemoteOffer => {
if op == StateChangeOp::SetLocal {
match sdp_type {
RTCSdpType::Answer => {
if next == RTCSignalingState::Stable {
return Ok(next);
}
}
RTCSdpType::Pranswer => {
if next == RTCSignalingState::HaveLocalPranswer {
return Ok(next);
}
}
_ => {}
}
}
}
RTCSignalingState::HaveLocalPranswer => {
if op == StateChangeOp::SetLocal && sdp_type == RTCSdpType::Answer {
if next == RTCSignalingState::Stable {
return Ok(next);
}
}
}
_ => {
return Err(Error::ErrSignalingStateProposedTransitionInvalid {
from: cur,
applying: sdp_type,
is_local: op == StateChangeOp::SetLocal,
});
}
};
Err(Error::ErrSignalingStateProposedTransitionInvalid {
from: cur,
is_local: op == StateChangeOp::SetLocal,
applying: sdp_type,
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_new_signaling_state() {
let tests = vec![
("Unspecified", RTCSignalingState::Unspecified),
("stable", RTCSignalingState::Stable),
("have-local-offer", RTCSignalingState::HaveLocalOffer),
("have-remote-offer", RTCSignalingState::HaveRemoteOffer),
("have-local-pranswer", RTCSignalingState::HaveLocalPranswer),
(
"have-remote-pranswer",
RTCSignalingState::HaveRemotePranswer,
),
("closed", RTCSignalingState::Closed),
];
for (state_string, expected_state) in tests {
assert_eq!(expected_state, RTCSignalingState::from(state_string));
}
}
#[test]
fn test_signaling_state_string() {
let tests = vec![
(RTCSignalingState::Unspecified, "Unspecified"),
(RTCSignalingState::Stable, "stable"),
(RTCSignalingState::HaveLocalOffer, "have-local-offer"),
(RTCSignalingState::HaveRemoteOffer, "have-remote-offer"),
(RTCSignalingState::HaveLocalPranswer, "have-local-pranswer"),
(
RTCSignalingState::HaveRemotePranswer,
"have-remote-pranswer",
),
(RTCSignalingState::Closed, "closed"),
];
for (state, expected_string) in tests {
assert_eq!(expected_string, state.to_string());
}
}
#[test]
fn test_signaling_state_transitions() {
let tests = vec![
(
"stable->SetLocal(offer)->have-local-offer",
RTCSignalingState::Stable,
RTCSignalingState::HaveLocalOffer,
StateChangeOp::SetLocal,
RTCSdpType::Offer,
None,
),
(
"stable->SetRemote(offer)->have-remote-offer",
RTCSignalingState::Stable,
RTCSignalingState::HaveRemoteOffer,
StateChangeOp::SetRemote,
RTCSdpType::Offer,
None,
),
(
"have-local-offer->SetRemote(answer)->stable",
RTCSignalingState::HaveLocalOffer,
RTCSignalingState::Stable,
StateChangeOp::SetRemote,
RTCSdpType::Answer,
None,
),
(
"have-local-offer->SetRemote(pranswer)->have-remote-pranswer",
RTCSignalingState::HaveLocalOffer,
RTCSignalingState::HaveRemotePranswer,
StateChangeOp::SetRemote,
RTCSdpType::Pranswer,
None,
),
(
"have-remote-pranswer->SetRemote(answer)->stable",
RTCSignalingState::HaveRemotePranswer,
RTCSignalingState::Stable,
StateChangeOp::SetRemote,
RTCSdpType::Answer,
None,
),
(
"have-remote-offer->SetLocal(answer)->stable",
RTCSignalingState::HaveRemoteOffer,
RTCSignalingState::Stable,
StateChangeOp::SetLocal,
RTCSdpType::Answer,
None,
),
(
"have-remote-offer->SetLocal(pranswer)->have-local-pranswer",
RTCSignalingState::HaveRemoteOffer,
RTCSignalingState::HaveLocalPranswer,
StateChangeOp::SetLocal,
RTCSdpType::Pranswer,
None,
),
(
"have-local-pranswer->SetLocal(answer)->stable",
RTCSignalingState::HaveLocalPranswer,
RTCSignalingState::Stable,
StateChangeOp::SetLocal,
RTCSdpType::Answer,
None,
),
(
"(invalid) stable->SetRemote(pranswer)->have-remote-pranswer",
RTCSignalingState::Stable,
RTCSignalingState::HaveRemotePranswer,
StateChangeOp::SetRemote,
RTCSdpType::Pranswer,
Some(Error::ErrSignalingStateProposedTransitionInvalid {
from: RTCSignalingState::Stable,
is_local: false,
applying: RTCSdpType::Pranswer,
}),
),
(
"(invalid) stable->SetRemote(rollback)->have-local-offer",
RTCSignalingState::Stable,
RTCSignalingState::HaveLocalOffer,
StateChangeOp::SetRemote,
RTCSdpType::Rollback,
Some(Error::ErrSignalingStateCannotRollback),
),
];
for (desc, cur, next, op, sdp_type, expected_err) in tests {
let result = check_next_signaling_state(cur, next, op, sdp_type);
match (&result, &expected_err) {
(Ok(got), None) => {
assert_eq!(*got, next, "{} state mismatch", desc);
}
(Err(got), Some(err)) => {
assert_eq!(err.to_string(), got.to_string(), "{} error mismatch", desc);
}
_ => {
assert!(
false,
"{}: expected {:?}, but got {:?}",
desc, expected_err, result
);
}
};
}
}
}