use super::StateMachine;
use crate::{
errors::{Result, SessionError},
state_table::types::{EventType, Role},
types::{CallState, SessionId, SessionInfo},
};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct StateMachineHelpers {
pub state_machine: Arc<StateMachine>,
active_sessions: Arc<RwLock<HashMap<SessionId, SessionInfo>>>,
subscribers: Arc<RwLock<HashMap<SessionId, Vec<Box<dyn Fn(SessionEvent) + Send + Sync>>>>>,
}
#[derive(Debug, Clone)]
pub enum SessionEvent {
StateChanged { from: CallState, to: CallState },
CallEstablished,
CallTerminated { reason: String },
MediaReady,
IncomingCall { from: String },
}
impl StateMachineHelpers {
pub fn new(state_machine: Arc<StateMachine>) -> Self {
Self {
state_machine,
active_sessions: Arc::new(RwLock::new(HashMap::new())),
subscribers: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn create_session(
&self,
session_id: SessionId,
from: String,
to: String,
role: Role,
) -> Result<()> {
let session = self
.state_machine
.store
.create_session(
session_id.clone(),
role,
true, )
.await?;
let mut session = session;
session.local_uri = Some(from.clone());
session.remote_uri = Some(to.clone());
self.state_machine.store.update_session(session).await?;
let info = SessionInfo {
session_id: session_id.clone(),
from,
to,
state: CallState::Idle,
start_time: std::time::SystemTime::now(),
media_active: false,
};
self.active_sessions.write().await.insert(session_id, info);
Ok(())
}
pub async fn make_transfer_leg(
&self,
from: &str,
to: &str,
transferor_session_id: &SessionId,
) -> Result<SessionId> {
self.make_call_inner(
from,
to,
None,
Some(transferor_session_id.clone()),
None,
Vec::new(),
)
.await
}
pub async fn set_transferor_session(
&self,
leg_session_id: &SessionId,
transferor_session_id: &SessionId,
) -> Result<()> {
let mut session = self.state_machine.store.get_session(leg_session_id).await?;
session.transferor_session_id = Some(transferor_session_id.clone());
session.is_transfer_call = true;
self.state_machine.store.update_session(session).await?;
Ok(())
}
async fn make_call_inner(
&self,
from: &str,
to: &str,
credentials: Option<crate::types::Credentials>,
transferor_session_id: Option<SessionId>,
pai_uri: Option<String>,
extra_headers: Vec<rvoip_sip_core::types::TypedHeader>,
) -> Result<SessionId> {
let session_id = SessionId::new();
self.create_session(
session_id.clone(),
from.to_string(),
to.to_string(),
Role::UAC,
)
.await?;
if credentials.is_some()
|| transferor_session_id.is_some()
|| pai_uri.is_some()
|| !extra_headers.is_empty()
{
let mut session = self.state_machine.store.get_session(&session_id).await?;
if let Some(creds) = credentials {
session.credentials = Some(creds);
}
if let Some(referor) = transferor_session_id {
session.transferor_session_id = Some(referor);
session.is_transfer_call = true;
}
if let Some(pai) = pai_uri {
session.pai_uri = Some(pai);
}
if !extra_headers.is_empty() {
session.extra_headers = extra_headers;
}
self.state_machine.store.update_session(session).await?;
}
self.state_machine
.process_event(
&session_id,
EventType::MakeCall {
target: to.to_string(),
},
)
.await?;
Ok(session_id)
}
pub async fn accept_call(&self, session_id: &SessionId) -> Result<()> {
self.state_machine
.process_event(session_id, EventType::AcceptCall)
.await?;
Ok(())
}
pub async fn accept_call_with_sdp(&self, session_id: &SessionId, sdp: String) -> Result<()> {
let mut session = self.state_machine.store.get_session(session_id).await?;
session.local_sdp = Some(sdp);
session.sdp_negotiated = true;
self.state_machine.store.update_session(session).await?;
self.state_machine
.process_event(session_id, EventType::AcceptCall)
.await?;
Ok(())
}
pub async fn send_early_media(
&self,
session_id: &SessionId,
sdp: Option<String>,
) -> Result<()> {
self.state_machine
.process_event(session_id, EventType::SendEarlyMedia { sdp })
.await?;
Ok(())
}
pub async fn reject_call(
&self,
session_id: &SessionId,
status: u16,
reason: &str,
) -> Result<()> {
self.state_machine
.process_event(
session_id,
EventType::RejectCall {
status,
reason: reason.to_string(),
},
)
.await?;
Ok(())
}
pub async fn redirect_call(
&self,
session_id: &SessionId,
status: u16,
contacts: Vec<String>,
) -> Result<()> {
self.state_machine
.process_event(session_id, EventType::RedirectCall { status, contacts })
.await?;
Ok(())
}
pub async fn hangup(&self, session_id: &SessionId) -> Result<()> {
if self
.state_machine
.store
.get_session(session_id)
.await
.is_err()
{
return Err(SessionError::SessionNotFound(session_id.to_string()));
}
self.state_machine
.process_event(session_id, EventType::HangupCall)
.await?;
Ok(())
}
pub async fn create_conference(&self, session_id: &SessionId, name: &str) -> Result<()> {
self.state_machine
.process_event(
session_id,
EventType::CreateConference {
name: name.to_string(),
},
)
.await?;
Ok(())
}
pub async fn add_to_conference(
&self,
host_session_id: &SessionId,
participant_session_id: &SessionId,
) -> Result<()> {
self.state_machine
.process_event(
host_session_id,
EventType::AddParticipant {
session_id: participant_session_id.to_string(),
},
)
.await?;
Ok(())
}
pub async fn get_session_info(&self, session_id: &SessionId) -> Result<SessionInfo> {
self.active_sessions
.read()
.await
.get(session_id)
.cloned()
.ok_or_else(|| SessionError::SessionNotFound(session_id.to_string()))
}
pub async fn list_sessions(&self) -> Vec<SessionInfo> {
let sessions = self.state_machine.store.get_all_sessions().await;
sessions
.into_iter()
.map(|s| SessionInfo {
session_id: s.session_id.clone(),
from: s.local_uri.unwrap_or_default(),
to: s.remote_uri.unwrap_or_default(),
state: s.call_state,
start_time: std::time::SystemTime::now(), media_active: s.media_session_id.is_some(),
})
.collect()
}
#[cfg(feature = "perf-tests")]
pub async fn perf_diagnostic_counts(&self) -> serde_json::Value {
let active_sessions = self.active_sessions.read().await.len();
let subscribers = self.subscribers.read().await.len();
serde_json::json!({
"active_sessions": active_sessions,
"subscriber_sessions": subscribers,
})
}
pub async fn get_state(&self, session_id: &SessionId) -> Result<CallState> {
let session = self.state_machine.store.get_session(session_id).await?;
Ok(session.call_state)
}
pub async fn is_in_conference(&self, session_id: &SessionId) -> Result<bool> {
let _ = session_id;
Ok(false)
}
pub async fn subscribe<F>(&self, session_id: SessionId, callback: F)
where
F: Fn(SessionEvent) + Send + Sync + 'static,
{
self.subscribers
.write()
.await
.entry(session_id)
.or_insert_with(Vec::new)
.push(Box::new(callback));
}
pub async fn unsubscribe(&self, session_id: &SessionId) {
self.subscribers.write().await.remove(session_id);
}
#[allow(dead_code)]
pub(crate) async fn notify_subscribers(&self, session_id: &SessionId, event: SessionEvent) {
if let Some(callbacks) = self.subscribers.read().await.get(session_id) {
for callback in callbacks {
callback(event.clone());
}
}
}
#[allow(dead_code)]
pub(crate) async fn cleanup_session(&self, session_id: &SessionId) {
self.active_sessions.write().await.remove(session_id);
self.subscribers.write().await.remove(session_id);
}
}