#![deny(missing_docs)]
use async_trait::async_trait;
use std::collections::{HashMap, HashSet, VecDeque};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use crate::api::dialog_package::{DialogInfo, DialogInfoDocument};
use crate::api::events::{
Event, MediaSecurityState, SipTrace, SubscriptionState, TransferTargetEvidence,
};
use crate::api::handle::{CallId, SessionHandle};
use crate::api::incoming::{IncomingCall, IncomingCallGuard};
use crate::api::performance::PerformanceConfig;
use crate::api::unified::{Config, MediaMode, RegistrationHandle, UnifiedCoordinator};
use crate::cleanup_diag::{self, CleanupStage};
use crate::errors::{Result, SessionError};
#[derive(Clone)]
pub struct ShutdownHandle {
tx: tokio::sync::watch::Sender<bool>,
}
impl ShutdownHandle {
pub fn shutdown(&self) {
let _ = self.tx.send(true);
}
pub(crate) fn from_sender(tx: tokio::sync::watch::Sender<bool>) -> Self {
Self { tx }
}
}
pub enum CallHandlerDecision {
Accept,
AcceptWithSdp(String),
Reject {
status: u16,
reason: String,
},
Redirect(String),
Defer(IncomingCallGuard),
}
#[derive(Debug, Clone)]
pub enum EndReason {
Normal,
Rejected,
Timeout,
NetworkError,
Other(String),
}
impl From<String> for EndReason {
fn from(s: String) -> Self {
match s.to_lowercase().as_str() {
r if r.contains("timeout") => EndReason::Timeout,
r if r.contains("reject") || r.contains("decline") || r.contains("busy") => {
EndReason::Rejected
}
r if r.contains("network") || r.contains("transport") => EndReason::NetworkError,
_ => EndReason::Other(s),
}
}
}
type CallbackFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
type EventHook = Arc<dyn Fn(Event) -> CallbackFuture<Result<()>> + Send + Sync>;
type IncomingHook = Arc<dyn Fn(IncomingCall) -> CallbackFuture<CallHandlerDecision> + Send + Sync>;
type EstablishedHook = Arc<dyn Fn(SessionHandle) -> CallbackFuture<Result<()>> + Send + Sync>;
type ProgressHook = Arc<
dyn Fn(SessionHandle, u16, String, Option<String>) -> CallbackFuture<Result<()>> + Send + Sync,
>;
type DtmfHook = Arc<dyn Fn(SessionHandle, char) -> CallbackFuture<Result<()>> + Send + Sync>;
type EndedHook = Arc<dyn Fn(CallId, EndReason) -> CallbackFuture<Result<()>> + Send + Sync>;
type FailedHook = Arc<dyn Fn(CallId, u16, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type CancelledHook = Arc<dyn Fn(CallId) -> CallbackFuture<Result<()>> + Send + Sync>;
type MediaSecurityHook =
Arc<dyn Fn(SessionHandle, MediaSecurityState) -> CallbackFuture<Result<()>> + Send + Sync>;
type HoldHook = Arc<dyn Fn(SessionHandle) -> CallbackFuture<Result<()>> + Send + Sync>;
type ReferReceivedHook = Arc<
dyn Fn(SessionHandle, crate::api::incoming::IncomingRequest) -> CallbackFuture<Result<bool>>
+ Send
+ Sync,
>;
type TransferAcceptedHook =
Arc<dyn Fn(SessionHandle, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type ReferProgressHook =
Arc<dyn Fn(SessionHandle, u16, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type ReferCompletedHook =
Arc<dyn Fn(SessionHandle, String, u16, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type TransferFailedHook =
Arc<dyn Fn(SessionHandle, u16, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type RegistrationSuccessHook =
Arc<dyn Fn(String, u32, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type RegistrationFailedHook =
Arc<dyn Fn(String, u16, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type UnregistrationSuccessHook = Arc<dyn Fn(String) -> CallbackFuture<Result<()>> + Send + Sync>;
type UnregistrationFailedHook =
Arc<dyn Fn(String, String) -> CallbackFuture<Result<()>> + Send + Sync>;
type SipTraceHook = Arc<dyn Fn(SipTrace) -> CallbackFuture<Result<()>> + Send + Sync>;
pub struct CallbackPeerBuilder {
config: Config,
event: Option<EventHook>,
incoming: Option<IncomingHook>,
established: Option<EstablishedHook>,
progress: Option<ProgressHook>,
dtmf: Option<DtmfHook>,
ended: Option<EndedHook>,
failed: Option<FailedHook>,
cancelled: Option<CancelledHook>,
media_security: Option<MediaSecurityHook>,
local_hold: Option<HoldHook>,
local_resume: Option<HoldHook>,
remote_hold: Option<HoldHook>,
remote_resume: Option<HoldHook>,
refer_received: Option<ReferReceivedHook>,
transfer_accepted: Option<TransferAcceptedHook>,
refer_progress: Option<ReferProgressHook>,
refer_completed: Option<ReferCompletedHook>,
transfer_failed: Option<TransferFailedHook>,
registration_success: Option<RegistrationSuccessHook>,
registration_failed: Option<RegistrationFailedHook>,
unregistration_success: Option<UnregistrationSuccessHook>,
unregistration_failed: Option<UnregistrationFailedHook>,
sip_trace: Option<SipTraceHook>,
}
impl CallbackPeerBuilder {
pub fn new(config: Config) -> Self {
Self {
config,
event: None,
incoming: None,
established: None,
progress: None,
dtmf: None,
ended: None,
failed: None,
cancelled: None,
media_security: None,
local_hold: None,
local_resume: None,
remote_hold: None,
remote_resume: None,
refer_received: None,
transfer_accepted: None,
refer_progress: None,
refer_completed: None,
transfer_failed: None,
registration_success: None,
registration_failed: None,
unregistration_success: None,
unregistration_failed: None,
sip_trace: None,
}
}
pub fn on_event<F, Fut>(mut self, f: F) -> Self
where
F: Fn(Event) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.event = Some(Arc::new(move |event| Box::pin(f(event))));
self
}
pub fn auto_180_ringing(mut self, enabled: bool) -> Self {
self.config = self.config.with_auto_180_ringing(enabled);
self
}
pub fn auto_100_trying(mut self, enabled: bool) -> Self {
self.config = self.config.with_auto_100_trying(enabled);
self
}
pub fn fast_auto_accept_incoming_calls(mut self, enabled: bool) -> Self {
self.config = self.config.with_fast_auto_accept_incoming_calls(enabled);
self
}
pub fn media_enabled(mut self, enabled: bool) -> Self {
self.config = self.config.with_media_enabled(enabled);
self
}
pub fn signaling_only_media(mut self, sdp_rtp_port: u16) -> Self {
self.config = self
.config
.with_media_mode(MediaMode::SignalingOnly { sdp_rtp_port });
self
}
pub fn media_port_capacity(mut self, start: u16, capacity: usize) -> Self {
self.config = self.config.with_media_port_capacity(start, capacity);
self
}
pub fn media_session_capacity(mut self, capacity: usize) -> Self {
self.config = self.config.with_media_session_capacity(capacity);
self
}
pub fn high_cps_udp_auto_answer(mut self, capacity: usize) -> Self {
self.config = self.config.with_high_cps_udp_auto_answer(capacity);
self
}
pub fn performance_config(mut self, performance: PerformanceConfig) -> Result<Self> {
self.config = self.config.try_with_performance_config(performance)?;
Ok(self)
}
pub fn pbx_media_server_performance(mut self, capacity: usize) -> Self {
self.config = self.config.with_pbx_media_server_performance(capacity);
self
}
pub fn signaling_only_server_high_performance(
mut self,
capacity: usize,
sdp_rtp_port: u16,
) -> Self {
self.config = self
.config
.with_signaling_only_server_high_performance(capacity, sdp_rtp_port);
self
}
pub fn app_event_channel_capacity(mut self, capacity: usize) -> Self {
self.config = self.config.with_app_event_channel_capacity(capacity);
self
}
pub fn sip_udp_parse_workers(mut self, workers: usize) -> Self {
self.config = self.config.with_sip_udp_parse_workers(workers);
self
}
pub fn sip_udp_parse_queue_capacity(mut self, capacity: usize) -> Self {
self.config = self.config.with_sip_udp_parse_queue_capacity(capacity);
self
}
pub fn sip_transaction_command_channel_capacity(mut self, capacity: usize) -> Self {
self.config = self
.config
.with_sip_transaction_command_channel_capacity(capacity);
self
}
pub fn server_call_admission_limit(mut self, limit: usize) -> Self {
self.config = self.config.with_server_call_admission_limit(limit);
self
}
pub fn server_call_admission_soft_limit(mut self, limit: usize) -> Self {
self.config = self.config.with_server_call_admission_soft_limit(limit);
self
}
pub fn server_call_admission_pacing_delay_ms(mut self, delay_ms: u64) -> Self {
self.config = self
.config
.with_server_call_admission_pacing_delay_ms(delay_ms);
self
}
pub fn server_overload_retry_after_secs(mut self, seconds: u32) -> Self {
self.config = self.config.with_server_overload_retry_after_secs(seconds);
self
}
pub fn sip_udp_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_sip_udp_diagnostics(enabled);
self
}
pub fn media_setup_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_media_setup_diagnostics(enabled);
self
}
pub fn cleanup_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_cleanup_diagnostics(enabled);
self
}
pub fn cleanup_diagnostic_events(mut self, enabled: bool) -> Self {
self.config = self.config.with_cleanup_diagnostic_events(enabled);
self
}
#[cfg(feature = "perf-tests")]
pub fn perf_max_rss_growth_mb_per_hr(mut self, limit: f64) -> Self {
self.config = self.config.with_perf_max_rss_growth_mb_per_hr(limit);
self
}
pub fn srtp_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_srtp_diagnostics(enabled);
self
}
pub fn rtp_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_rtp_diagnostics(enabled);
self
}
pub fn media_sdp_diagnostics(mut self, enabled: bool) -> Self {
self.config = self.config.with_media_sdp_diagnostics(enabled);
self
}
pub fn on_incoming<F, Fut>(mut self, f: F) -> Self
where
F: Fn(IncomingCall) -> Fut + Send + Sync + 'static,
Fut: Future<Output = CallHandlerDecision> + Send + 'static,
{
self.incoming = Some(Arc::new(move |call| Box::pin(f(call))));
self
}
pub fn on_established<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.established = Some(Arc::new(move |handle| Box::pin(f(handle))));
self
}
pub fn on_progress<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, u16, String, Option<String>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.progress = Some(Arc::new(move |handle, status, reason, sdp| {
Box::pin(f(handle, status, reason, sdp))
}));
self
}
pub fn on_dtmf<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, char) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.dtmf = Some(Arc::new(move |handle, digit| Box::pin(f(handle, digit))));
self
}
pub fn on_failed<F, Fut>(mut self, f: F) -> Self
where
F: Fn(CallId, u16, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.failed = Some(Arc::new(move |call_id, status, reason| {
Box::pin(f(call_id, status, reason))
}));
self
}
pub fn on_cancelled<F, Fut>(mut self, f: F) -> Self
where
F: Fn(CallId) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.cancelled = Some(Arc::new(move |call_id| Box::pin(f(call_id))));
self
}
pub fn on_media_security<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, MediaSecurityState) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.media_security = Some(Arc::new(move |handle, state| Box::pin(f(handle, state))));
self
}
pub fn on_local_hold<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.local_hold = Some(Arc::new(move |handle| Box::pin(f(handle))));
self
}
pub fn on_local_resume<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.local_resume = Some(Arc::new(move |handle| Box::pin(f(handle))));
self
}
pub fn on_remote_hold<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.remote_hold = Some(Arc::new(move |handle| Box::pin(f(handle))));
self
}
pub fn on_remote_resume<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.remote_resume = Some(Arc::new(move |handle| Box::pin(f(handle))));
self
}
pub fn on_ended<F, Fut>(mut self, f: F) -> Self
where
F: Fn(CallId, EndReason) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.ended = Some(Arc::new(move |call_id, reason| {
Box::pin(f(call_id, reason))
}));
self
}
pub fn on_refer_received<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, crate::api::incoming::IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<bool>> + Send + 'static,
{
self.refer_received = Some(Arc::new(move |handle, req| Box::pin(f(handle, req))));
self
}
pub fn on_transfer_accepted<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.transfer_accepted = Some(Arc::new(move |handle, refer_to| {
Box::pin(f(handle, refer_to))
}));
self
}
pub fn on_refer_progress<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, u16, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.refer_progress = Some(Arc::new(move |handle, status, reason| {
Box::pin(f(handle, status, reason))
}));
self
}
pub fn on_refer_completed<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, String, u16, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.refer_completed = Some(Arc::new(move |handle, target, status, reason| {
Box::pin(f(handle, target, status, reason))
}));
self
}
pub fn on_transfer_failed<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SessionHandle, u16, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.transfer_failed = Some(Arc::new(move |handle, status, reason| {
Box::pin(f(handle, status, reason))
}));
self
}
pub fn on_registration_success<F, Fut>(mut self, f: F) -> Self
where
F: Fn(String, u32, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.registration_success = Some(Arc::new(move |registrar, expires, contact| {
Box::pin(f(registrar, expires, contact))
}));
self
}
pub fn on_registration_failed<F, Fut>(mut self, f: F) -> Self
where
F: Fn(String, u16, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.registration_failed = Some(Arc::new(move |registrar, status, reason| {
Box::pin(f(registrar, status, reason))
}));
self
}
pub fn on_unregistration_success<F, Fut>(mut self, f: F) -> Self
where
F: Fn(String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.unregistration_success = Some(Arc::new(move |registrar| Box::pin(f(registrar))));
self
}
pub fn on_unregistration_failed<F, Fut>(mut self, f: F) -> Self
where
F: Fn(String, String) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.unregistration_failed = Some(Arc::new(move |registrar, reason| {
Box::pin(f(registrar, reason))
}));
self
}
pub fn on_sip_trace<F, Fut>(mut self, f: F) -> Self
where
F: Fn(SipTrace) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
self.sip_trace = Some(Arc::new(move |trace| Box::pin(f(trace))));
self
}
pub async fn build(self) -> Result<CallbackPeer<CallbackBuilderHandler>> {
let incoming = self.incoming.ok_or_else(|| {
SessionError::ConfigError(
"CallbackPeer::builder requires an on_incoming hook".to_string(),
)
})?;
CallbackPeer::new(
CallbackBuilderHandler {
event: self.event,
incoming,
established: self.established,
progress: self.progress,
dtmf: self.dtmf,
ended: self.ended,
failed: self.failed,
cancelled: self.cancelled,
media_security: self.media_security,
local_hold: self.local_hold,
local_resume: self.local_resume,
remote_hold: self.remote_hold,
remote_resume: self.remote_resume,
refer_received: self.refer_received,
transfer_accepted: self.transfer_accepted,
refer_progress: self.refer_progress,
refer_completed: self.refer_completed,
transfer_failed: self.transfer_failed,
registration_success: self.registration_success,
registration_failed: self.registration_failed,
unregistration_success: self.unregistration_success,
unregistration_failed: self.unregistration_failed,
sip_trace: self.sip_trace,
},
self.config,
)
.await
}
}
#[doc(hidden)]
pub struct CallbackBuilderHandler {
event: Option<EventHook>,
incoming: IncomingHook,
established: Option<EstablishedHook>,
progress: Option<ProgressHook>,
dtmf: Option<DtmfHook>,
ended: Option<EndedHook>,
failed: Option<FailedHook>,
cancelled: Option<CancelledHook>,
media_security: Option<MediaSecurityHook>,
local_hold: Option<HoldHook>,
local_resume: Option<HoldHook>,
remote_hold: Option<HoldHook>,
remote_resume: Option<HoldHook>,
refer_received: Option<ReferReceivedHook>,
transfer_accepted: Option<TransferAcceptedHook>,
refer_progress: Option<ReferProgressHook>,
refer_completed: Option<ReferCompletedHook>,
transfer_failed: Option<TransferFailedHook>,
registration_success: Option<RegistrationSuccessHook>,
registration_failed: Option<RegistrationFailedHook>,
unregistration_success: Option<UnregistrationSuccessHook>,
unregistration_failed: Option<UnregistrationFailedHook>,
sip_trace: Option<SipTraceHook>,
}
#[async_trait]
impl CallHandler for CallbackBuilderHandler {
async fn on_event(&self, event: Event) {
if let Some(hook) = &self.event {
if let Err(err) = hook(event).await {
tracing::warn!("[CallbackPeerBuilder] on_event failed: {}", err);
}
}
}
async fn on_incoming_call(&self, call: IncomingCall) -> CallHandlerDecision {
(self.incoming)(call).await
}
async fn on_call_established(&self, handle: SessionHandle) {
if let Some(hook) = &self.established {
if let Err(err) = hook(handle).await {
tracing::warn!("[CallbackPeerBuilder] on_established failed: {}", err);
}
}
}
async fn on_call_progress(
&self,
handle: SessionHandle,
status_code: u16,
reason: String,
sdp: Option<String>,
) {
if let Some(hook) = &self.progress {
if let Err(err) = hook(handle, status_code, reason, sdp).await {
tracing::warn!("[CallbackPeerBuilder] on_progress failed: {}", err);
}
}
}
async fn on_call_ended(&self, call_id: CallId, reason: EndReason) {
if let Some(hook) = &self.ended {
if let Err(err) = hook(call_id, reason).await {
tracing::warn!("[CallbackPeerBuilder] on_ended failed: {}", err);
}
}
}
async fn on_call_failed(&self, call_id: CallId, status_code: u16, reason: String) {
if let Some(hook) = &self.failed {
if let Err(err) = hook(call_id, status_code, reason).await {
tracing::warn!("[CallbackPeerBuilder] on_failed failed: {}", err);
}
}
}
async fn on_call_cancelled(&self, call_id: CallId) {
if let Some(hook) = &self.cancelled {
if let Err(err) = hook(call_id).await {
tracing::warn!("[CallbackPeerBuilder] on_cancelled failed: {}", err);
}
}
}
async fn on_dtmf(&self, handle: SessionHandle, digit: char) {
if let Some(hook) = &self.dtmf {
if let Err(err) = hook(handle, digit).await {
tracing::warn!("[CallbackPeerBuilder] on_dtmf failed: {}", err);
}
}
}
async fn on_media_security_negotiated(&self, handle: SessionHandle, state: MediaSecurityState) {
if let Some(hook) = &self.media_security {
if let Err(err) = hook(handle, state).await {
tracing::warn!("[CallbackPeerBuilder] on_media_security failed: {}", err);
}
}
}
async fn on_call_on_hold(&self, handle: SessionHandle) {
if let Some(hook) = &self.local_hold {
if let Err(err) = hook(handle).await {
tracing::warn!("[CallbackPeerBuilder] on_local_hold failed: {}", err);
}
}
}
async fn on_call_resumed(&self, handle: SessionHandle) {
if let Some(hook) = &self.local_resume {
if let Err(err) = hook(handle).await {
tracing::warn!("[CallbackPeerBuilder] on_local_resume failed: {}", err);
}
}
}
async fn on_remote_call_on_hold(&self, handle: SessionHandle) {
if let Some(hook) = &self.remote_hold {
if let Err(err) = hook(handle).await {
tracing::warn!("[CallbackPeerBuilder] on_remote_hold failed: {}", err);
}
}
}
async fn on_remote_call_resumed(&self, handle: SessionHandle) {
if let Some(hook) = &self.remote_resume {
if let Err(err) = hook(handle).await {
tracing::warn!("[CallbackPeerBuilder] on_remote_resume failed: {}", err);
}
}
}
async fn on_refer_received(&self, request: crate::api::incoming::IncomingRequest) {
let Some(hook) = &self.refer_received else {
return;
};
let Some(coord) = request.coordinator.clone() else {
tracing::warn!(
"[CallbackPeerBuilder] on_refer_received fired without a coordinator hook; \
dropping REFER for call {}",
request.call_id
);
return;
};
let handle = SessionHandle::new(request.call_id.clone(), coord);
let accepted = match hook(handle.clone(), request).await {
Ok(b) => b,
Err(err) => {
tracing::warn!(
"[CallbackPeerBuilder] on_refer_received failed; rejecting REFER: {}",
err
);
false
}
};
let result = if accepted {
handle.accept_refer().await
} else {
handle.reject_refer(603, "Decline").await
};
if let Err(err) = result {
tracing::warn!(
"[CallbackPeerBuilder] applying REFER decision failed: {}",
err
);
}
}
async fn on_transfer_accepted(&self, handle: SessionHandle, refer_to: String) {
if let Some(hook) = &self.transfer_accepted {
if let Err(err) = hook(handle, refer_to).await {
tracing::warn!("[CallbackPeerBuilder] on_transfer_accepted failed: {}", err);
}
}
}
async fn on_refer_progress(&self, handle: SessionHandle, status_code: u16, reason: String) {
if let Some(hook) = &self.refer_progress {
if let Err(err) = hook(handle, status_code, reason).await {
tracing::warn!("[CallbackPeerBuilder] on_refer_progress failed: {}", err);
}
}
}
async fn on_refer_completed(
&self,
handle: SessionHandle,
target: String,
status_code: u16,
reason: String,
) {
if let Some(hook) = &self.refer_completed {
if let Err(err) = hook(handle, target, status_code, reason).await {
tracing::warn!("[CallbackPeerBuilder] on_refer_completed failed: {}", err);
}
}
}
async fn on_transfer_failed(&self, handle: SessionHandle, status_code: u16, reason: String) {
if let Some(hook) = &self.transfer_failed {
if let Err(err) = hook(handle, status_code, reason).await {
tracing::warn!("[CallbackPeerBuilder] on_transfer_failed failed: {}", err);
}
}
}
async fn on_registration_success(&self, registrar: String, expires: u32, contact: String) {
if let Some(hook) = &self.registration_success {
if let Err(err) = hook(registrar, expires, contact).await {
tracing::warn!(
"[CallbackPeerBuilder] on_registration_success failed: {}",
err
);
}
}
}
async fn on_registration_failed(&self, registrar: String, status_code: u16, reason: String) {
if let Some(hook) = &self.registration_failed {
if let Err(err) = hook(registrar, status_code, reason).await {
tracing::warn!(
"[CallbackPeerBuilder] on_registration_failed failed: {}",
err
);
}
}
}
async fn on_unregistration_success(&self, registrar: String) {
if let Some(hook) = &self.unregistration_success {
if let Err(err) = hook(registrar).await {
tracing::warn!(
"[CallbackPeerBuilder] on_unregistration_success failed: {}",
err
);
}
}
}
async fn on_unregistration_failed(&self, registrar: String, reason: String) {
if let Some(hook) = &self.unregistration_failed {
if let Err(err) = hook(registrar, reason).await {
tracing::warn!(
"[CallbackPeerBuilder] on_unregistration_failed failed: {}",
err
);
}
}
}
async fn on_sip_trace(&self, trace: SipTrace) {
if let Some(hook) = &self.sip_trace {
if let Err(err) = hook(trace).await {
tracing::warn!("[CallbackPeerBuilder] on_sip_trace failed: {}", err);
}
}
}
}
#[async_trait]
pub trait CallHandler: Send + Sync + 'static {
#[allow(unused_variables)]
async fn on_event(&self, event: Event) {}
async fn on_incoming_call(&self, call: IncomingCall) -> CallHandlerDecision;
#[allow(unused_variables)]
async fn on_call_established(&self, handle: SessionHandle) {}
#[allow(unused_variables)]
async fn on_call_progress(
&self,
handle: SessionHandle,
status_code: u16,
reason: String,
sdp: Option<String>,
) {
}
#[allow(unused_variables)]
async fn on_call_ended(&self, call_id: CallId, reason: EndReason) {}
#[allow(unused_variables)]
async fn on_call_failed(&self, call_id: CallId, status_code: u16, reason: String) {}
#[allow(unused_variables)]
async fn on_call_cancelled(&self, call_id: CallId) {}
#[allow(unused_variables)]
async fn on_dtmf(&self, handle: SessionHandle, digit: char) {}
#[allow(unused_variables)]
async fn on_media_security_negotiated(&self, handle: SessionHandle, state: MediaSecurityState) {
}
#[allow(unused_variables)]
async fn on_call_on_hold(&self, handle: SessionHandle) {}
#[allow(unused_variables)]
async fn on_call_resumed(&self, handle: SessionHandle) {}
#[allow(unused_variables)]
async fn on_remote_call_on_hold(&self, handle: SessionHandle) {}
#[allow(unused_variables)]
async fn on_remote_call_resumed(&self, handle: SessionHandle) {}
#[allow(unused_variables)]
async fn on_transfer_accepted(&self, handle: SessionHandle, refer_to: String) {}
#[allow(unused_variables)]
async fn on_refer_notify(
&self,
handle: SessionHandle,
status_code: u16,
reason: String,
subscription_state: Option<SubscriptionState>,
body: Option<String>,
) {
}
#[allow(unused_variables)]
async fn on_refer_progress(&self, handle: SessionHandle, status_code: u16, reason: String) {}
#[allow(unused_variables)]
async fn on_refer_completed(
&self,
handle: SessionHandle,
target: String,
status_code: u16,
reason: String,
) {
}
#[allow(unused_variables)]
async fn on_transfer_failed(&self, handle: SessionHandle, status_code: u16, reason: String) {}
#[allow(unused_variables)]
async fn on_transfer_target_answered(
&self,
handle: SessionHandle,
target_uri: String,
evidence: TransferTargetEvidence,
) {
}
#[allow(unused_variables)]
async fn on_transfer_replacement_dialog_observed(
&self,
handle: SessionHandle,
dialog: DialogInfo,
) {
}
#[allow(unused_variables)]
async fn on_transfer_replacement_dialog_terminated(
&self,
handle: SessionHandle,
dialog: DialogInfo,
reason: Option<String>,
) {
}
#[allow(unused_variables)]
async fn on_dialog_package_notify(
&self,
subscription_id: CallId,
entity: Option<String>,
version: Option<u32>,
dialogs: Vec<DialogInfo>,
document: DialogInfoDocument,
) {
}
#[allow(unused_variables)]
async fn on_dialog_state_changed(&self, subscription_id: CallId, dialog: DialogInfo) {}
#[allow(unused_variables)]
async fn on_notify_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_refer_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_info_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_message_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_options_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_update_received(&self, request: crate::api::incoming::IncomingRequest) {}
#[allow(unused_variables)]
async fn on_register_received(&self, register: crate::api::incoming::IncomingRegister) {}
#[allow(unused_variables)]
async fn on_registration_success(&self, registrar: String, expires: u32, contact: String) {}
#[allow(unused_variables)]
async fn on_registration_failed(&self, registrar: String, status_code: u16, reason: String) {}
#[allow(unused_variables)]
async fn on_unregistration_success(&self, registrar: String) {}
#[allow(unused_variables)]
async fn on_unregistration_failed(&self, registrar: String, reason: String) {}
#[allow(unused_variables)]
async fn on_sip_trace(&self, trace: SipTrace) {}
#[allow(unused_variables)]
async fn on_auth_retrying(&self, call_id: CallId, status_code: u16, realm: String) {}
}
#[derive(Clone)]
pub struct CallbackPeerControl {
coordinator: Arc<UnifiedCoordinator>,
local_uri: String,
shutdown_tx: tokio::sync::watch::Sender<bool>,
}
impl CallbackPeerControl {
pub fn register(
&self,
registrar: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
) -> crate::api::send::RegisterBuilder {
self.coordinator.register(registrar, username, password)
}
pub async fn is_registered(&self, handle: &RegistrationHandle) -> Result<bool> {
self.coordinator.is_registered(handle).await
}
pub async fn unregister(&self, handle: &RegistrationHandle) -> Result<()> {
self.coordinator.unregister(handle).await
}
pub async fn hangup(&self, handle: &SessionHandle) -> Result<()> {
self.coordinator.hangup(handle.id()).await
}
pub fn shutdown(&self) {
let _ = self.shutdown_tx.send(true);
}
pub fn coordinator(&self) -> &Arc<UnifiedCoordinator> {
&self.coordinator
}
pub fn invite(&self, target: impl Into<String>) -> crate::api::send::OutboundCallBuilder {
self.coordinator
.invite(Some(self.local_uri.clone()), target)
}
}
pub struct CallbackPeer<H: CallHandler> {
handler: Arc<H>,
coordinator: Arc<UnifiedCoordinator>,
local_uri: String,
shutdown_tx: tokio::sync::watch::Sender<bool>,
shutdown_rx: tokio::sync::watch::Receiver<bool>,
established_callbacks: Arc<tokio::sync::Mutex<HashSet<CallId>>>,
terminal_callbacks: Arc<tokio::sync::Mutex<BoundedCallDedupe>>,
deferred_calls: Arc<tokio::sync::Mutex<HashMap<CallId, IncomingCallGuard>>>,
}
const TERMINAL_CALLBACK_DEDUPE_CAPACITY: usize = 8192;
struct BoundedCallDedupe {
set: HashSet<CallId>,
order: VecDeque<CallId>,
capacity: usize,
}
impl BoundedCallDedupe {
fn with_capacity(capacity: usize) -> Self {
Self {
set: HashSet::with_capacity(capacity),
order: VecDeque::with_capacity(capacity),
capacity: capacity.max(1),
}
}
fn insert(&mut self, call_id: CallId) -> bool {
if self.set.contains(&call_id) {
return false;
}
self.set.insert(call_id.clone());
self.order.push_back(call_id);
while self.order.len() > self.capacity {
if let Some(oldest) = self.order.pop_front() {
self.set.remove(&oldest);
}
}
true
}
#[cfg(test)]
fn len(&self) -> usize {
self.set.len()
}
}
impl CallbackPeer<CallbackBuilderHandler> {
pub fn builder(config: Config) -> CallbackPeerBuilder {
CallbackPeerBuilder::new(config)
}
}
impl<H: CallHandler> CallbackPeer<H> {
pub async fn new(handler: H, config: Config) -> Result<Self> {
let local_uri = config.local_uri.clone();
let coordinator = UnifiedCoordinator::new(config).await?;
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
Ok(Self {
handler: Arc::new(handler),
coordinator,
local_uri,
shutdown_tx,
shutdown_rx,
established_callbacks: Arc::new(tokio::sync::Mutex::new(HashSet::new())),
terminal_callbacks: Arc::new(tokio::sync::Mutex::new(
BoundedCallDedupe::with_capacity(TERMINAL_CALLBACK_DEDUPE_CAPACITY),
)),
deferred_calls: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
})
}
pub fn coordinator(&self) -> &Arc<UnifiedCoordinator> {
&self.coordinator
}
pub fn invite(&self, target: impl Into<String>) -> crate::api::send::OutboundCallBuilder {
self.coordinator
.invite(Some(self.local_uri.clone()), target)
}
pub fn control(&self) -> CallbackPeerControl {
CallbackPeerControl {
coordinator: self.coordinator.clone(),
local_uri: self.local_uri.clone(),
shutdown_tx: self.shutdown_tx.clone(),
}
}
pub fn register(
&self,
registrar: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
) -> crate::api::send::RegisterBuilder {
self.coordinator.register(registrar, username, password)
}
pub async fn is_registered(
&self,
handle: &crate::api::unified::RegistrationHandle,
) -> Result<bool> {
self.coordinator.is_registered(handle).await
}
pub async fn unregister(&self, handle: &crate::api::unified::RegistrationHandle) -> Result<()> {
self.coordinator.unregister(handle).await
}
pub fn shutdown(&self) {
let _ = self.shutdown_tx.send(true);
}
pub fn shutdown_handle(&self) -> ShutdownHandle {
ShutdownHandle {
tx: self.shutdown_tx.clone(),
}
}
pub async fn run(self) -> Result<()> {
let mut event_rx = self.coordinator.subscribe_events().await?;
let mut shutdown_rx = self.shutdown_rx.clone();
let mut handlers: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();
loop {
reap_ready_handlers(&mut handlers, "completed");
tokio::select! {
_ = shutdown_rx.changed() => {
if *shutdown_rx.borrow() {
tracing::info!("[CallbackPeer] Shutdown signal received");
break;
}
}
Some(join_result) = handlers.join_next(), if !handlers.is_empty() => {
if let Err(e) = join_result {
if !e.is_cancelled() {
tracing::warn!("[CallbackPeer] Handler task panicked or errored: {}", e);
}
}
}
raw = event_rx.recv() => {
let Some(raw_event) = raw else {
tracing::info!("[CallbackPeer] Event channel closed, stopping");
break;
};
let Some(session_event) = raw_event
.as_any()
.downcast_ref::<crate::adapters::SessionApiCrossCrateEvent>()
else {
continue;
};
let event = session_event.event.clone();
self.dispatch(event, &mut handlers).await;
reap_ready_handlers(&mut handlers, "post-dispatch");
}
}
}
while let Some(join_result) = handlers.join_next().await {
if let Err(e) = join_result {
if !e.is_cancelled() {
tracing::warn!(
"[CallbackPeer] Handler task panicked or errored on drain: {}",
e
);
}
}
}
{
let mut deferred = self.deferred_calls.lock().await;
for guard in deferred.values() {
guard.resolve_without_response();
}
deferred.clear();
}
if let Err(e) = self
.coordinator
.shutdown_gracefully(Some(Duration::ZERO))
.await
{
tracing::warn!("[CallbackPeer] Coordinator shutdown failed: {}", e);
}
Ok(())
}
async fn dispatch(&self, event: Event, handlers: &mut tokio::task::JoinSet<()>) {
let handler = self.handler.clone();
let coordinator = self.coordinator.clone();
let established_callbacks = self.established_callbacks.clone();
let terminal_callbacks = self.terminal_callbacks.clone();
let deferred_calls = self.deferred_calls.clone();
let fast_auto_accept_incoming_calls = coordinator.fast_auto_accept_incoming_calls();
handlers.spawn(async move {
let dispatch_guard = cleanup_diag::stage_guard(
callback_stage_for_event(&event),
callback_label_for_event(&event),
);
handler.on_event(event.clone()).await;
match event {
Event::IncomingCall {
call_id,
from,
to,
sdp,
} => {
let parsed = coordinator
.session_registry
.peek_pending_incoming_request()
.await;
let incoming = match parsed {
Some(req) => IncomingCall::with_request(
call_id.clone(),
from,
to,
sdp,
coordinator.clone(),
req,
),
None => IncomingCall::new(
call_id.clone(),
from,
to,
sdp,
coordinator.clone(),
),
};
let decision = handler.on_incoming_call(incoming).await;
match decision {
CallHandlerDecision::Accept => {
if fast_auto_accept_incoming_calls {
tracing::debug!(
"Callback accept decision for {} already handled by fast auto-accept",
call_id
);
return;
}
let accept_guard = cleanup_diag::stage_guard(
CleanupStage::CallbackAcceptCall,
call_id.to_string(),
);
match coordinator.accept_call(&call_id).await {
Ok(()) => {
accept_guard.finish_success();
let should_notify = {
let mut callbacks = established_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
let handle =
SessionHandle::new(call_id.clone(), coordinator.clone());
handler.on_call_established(handle).await;
}
}
Err(e) => {
tracing::debug!(
"Callback accept decision for {} was not applied: {}",
call_id,
e
);
accept_guard.finish_failure();
}
}
}
CallHandlerDecision::AcceptWithSdp(sdp) => {
if fast_auto_accept_incoming_calls {
tracing::debug!(
"Callback accept-with-SDP decision for {} already handled by fast auto-accept",
call_id
);
return;
}
let accept_guard = cleanup_diag::stage_guard(
CleanupStage::CallbackAcceptCall,
call_id.to_string(),
);
match coordinator.accept_call_with_sdp(&call_id, sdp).await {
Ok(()) => {
accept_guard.finish_success();
let should_notify = {
let mut callbacks = established_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
let handle =
SessionHandle::new(call_id.clone(), coordinator.clone());
handler.on_call_established(handle).await;
}
}
Err(e) => {
tracing::debug!(
"Callback accept-with-SDP decision for {} was not applied: {}",
call_id,
e
);
accept_guard.finish_failure();
}
}
}
CallHandlerDecision::Reject { status, reason } => {
if fast_auto_accept_incoming_calls {
tracing::debug!(
"Callback reject decision for {} ignored because fast auto-accept already answered the call",
call_id
);
return;
}
let _ = coordinator
.reject(&call_id)
.with_status(status)
.with_reason(reason)
.send()
.await;
}
CallHandlerDecision::Redirect(target) => {
if fast_auto_accept_incoming_calls {
tracing::debug!(
"Callback redirect decision for {} ignored because fast auto-accept already answered the call",
call_id
);
return;
}
let _ = coordinator
.redirect(&call_id)
.with_status(302)
.with_contacts(vec![target])
.send()
.await;
}
CallHandlerDecision::Defer(guard) => {
if fast_auto_accept_incoming_calls {
guard.resolve_without_response();
tracing::debug!(
"Callback defer decision for {} ignored because fast auto-accept already answered the call",
call_id
);
return;
}
deferred_calls.lock().await.insert(call_id, guard);
}
}
}
Event::CallAnswered { call_id, .. } => {
if let Some(guard) = deferred_calls.lock().await.remove(&call_id) {
guard.resolve_without_response();
}
let should_notify = {
let mut callbacks = established_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_call_established(handle).await;
}
}
Event::CallProgress {
call_id,
status_code,
reason,
sdp,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler
.on_call_progress(handle, status_code, reason, sdp)
.await;
}
Event::CallEnded { call_id, reason } => {
if let Some(guard) = deferred_calls.lock().await.remove(&call_id) {
guard.resolve_without_response();
}
established_callbacks.lock().await.remove(&call_id);
let should_notify = {
let mut callbacks = terminal_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
let end_reason = EndReason::from(reason);
handler.on_call_ended(call_id, end_reason).await;
}
}
Event::CallFailed {
call_id,
status_code,
reason,
} => {
if let Some(guard) = deferred_calls.lock().await.remove(&call_id) {
guard.resolve_without_response();
}
established_callbacks.lock().await.remove(&call_id);
let should_notify = {
let mut callbacks = terminal_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
handler.on_call_failed(call_id, status_code, reason).await;
}
}
Event::CallCancelled { call_id } => {
if let Some(guard) = deferred_calls.lock().await.remove(&call_id) {
guard.resolve_without_response();
}
established_callbacks.lock().await.remove(&call_id);
let should_notify = {
let mut callbacks = terminal_callbacks.lock().await;
callbacks.insert(call_id.clone())
};
if should_notify {
handler.on_call_cancelled(call_id).await;
}
}
Event::CallOnHold { call_id } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_call_on_hold(handle).await;
}
Event::CallResumed { call_id } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_call_resumed(handle).await;
}
Event::RemoteCallOnHold { call_id } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_remote_call_on_hold(handle).await;
}
Event::RemoteCallResumed { call_id } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_remote_call_resumed(handle).await;
}
Event::DtmfReceived { call_id, digit } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_dtmf(handle, digit).await;
}
Event::MediaSecurityNegotiated {
call_id,
keying,
suite,
profile,
contexts_installed,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler
.on_media_security_negotiated(
handle,
MediaSecurityState {
keying,
suite,
profile,
contexts_installed,
},
)
.await;
}
Event::ReferReceived {
call_id: _, refer_to: _, request, ..
} => {
if let Some(mut req) = request {
req.set_coordinator(coordinator.clone());
handler.on_refer_received(req).await;
}
}
Event::TransferAccepted { call_id, refer_to } => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_transfer_accepted(handle, refer_to).await;
}
Event::ReferNotify {
call_id,
status_code,
reason,
subscription_state,
body,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler
.on_refer_notify(handle, status_code, reason, subscription_state, body)
.await;
}
Event::ReferProgress {
call_id,
status_code,
reason,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler.on_refer_progress(handle, status_code, reason).await;
}
Event::ReferCompleted {
call_id,
target,
status_code,
reason,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler
.on_refer_completed(handle, target, status_code, reason)
.await;
}
Event::TransferFailed {
call_id,
status_code,
reason,
} => {
let handle = SessionHandle::new(call_id, coordinator);
handler
.on_transfer_failed(handle, status_code, reason)
.await;
}
Event::TransferTargetAnswered {
transfer_call_id,
target_uri,
evidence,
} => {
let handle = SessionHandle::new(transfer_call_id, coordinator);
handler
.on_transfer_target_answered(handle, target_uri, evidence)
.await;
}
Event::TransferReplacementDialogObserved {
transfer_call_id,
dialog,
} => {
let handle = SessionHandle::new(transfer_call_id, coordinator);
handler
.on_transfer_replacement_dialog_observed(handle, dialog)
.await;
}
Event::TransferReplacementDialogTerminated {
transfer_call_id,
dialog,
reason,
} => {
let handle = SessionHandle::new(transfer_call_id, coordinator);
handler
.on_transfer_replacement_dialog_terminated(handle, dialog, reason)
.await;
}
Event::DialogPackageNotify {
subscription_id,
entity,
version,
dialogs,
document,
} => {
handler
.on_dialog_package_notify(subscription_id, entity, version, dialogs, document)
.await;
}
Event::DialogStateChanged {
subscription_id,
dialog,
} => {
handler
.on_dialog_state_changed(subscription_id, dialog)
.await;
}
Event::NotifyReceived {
call_id: _,
event_package: _,
subscription_state: _,
content_type: _,
body: _,
request,
} => {
if let Some(mut req) = request {
req.set_coordinator(coordinator.clone());
handler.on_notify_received(req).await;
}
}
Event::RegistrationSuccess {
registrar,
expires,
contact,
} => {
handler
.on_registration_success(registrar, expires, contact)
.await;
}
Event::RegistrationFailed {
registrar,
status_code,
reason,
} => {
handler
.on_registration_failed(registrar, status_code, reason)
.await;
}
Event::UnregistrationSuccess { registrar } => {
handler.on_unregistration_success(registrar).await;
}
Event::UnregistrationFailed { registrar, reason } => {
handler.on_unregistration_failed(registrar, reason).await;
}
Event::SipTrace(trace) => {
handler.on_sip_trace(trace).await;
}
Event::CallAuthRetrying {
call_id,
status_code,
realm,
} => {
handler.on_auth_retrying(call_id, status_code, realm).await;
}
Event::SessionRefreshed { .. }
| Event::SessionRefreshFailed { .. }
| Event::CallMuted { .. }
| Event::CallUnmuted { .. }
| Event::MediaQualityChanged { .. }
| Event::NetworkError { .. }
| Event::AuthenticationRequired { .. }
| Event::CallProgressDetailed(_)
| Event::CallEstablishedDetailed(_)
| Event::CallFailedDetailed(_) => {}
Event::InfoReceived { call_id: _, mut request } => {
request.set_coordinator(coordinator.clone());
handler.on_info_received(request).await;
}
Event::MessageReceived { call_id: _, mut request } => {
request.set_coordinator(coordinator.clone());
handler.on_message_received(request).await;
}
Event::OptionsReceived { call_id: _, mut request } => {
request.set_coordinator(coordinator.clone());
handler.on_options_received(request).await;
}
Event::UpdateReceived { call_id: _, mut request } => {
request.set_coordinator(coordinator.clone());
handler.on_update_received(request).await;
}
Event::IncomingRegister { mut register } => {
register.set_coordinator(coordinator.clone());
handler.on_register_received(register).await;
}
}
dispatch_guard.finish_success();
});
}
}
fn reap_ready_handlers(handlers: &mut tokio::task::JoinSet<()>, context: &str) {
while let Some(join_result) = handlers.try_join_next() {
if let Err(e) = join_result {
if !e.is_cancelled() {
tracing::warn!("[CallbackPeer] Handler task panicked or errored ({context}): {e}");
}
}
}
}
fn callback_stage_for_event(event: &Event) -> CleanupStage {
match event {
Event::IncomingCall { .. } => CleanupStage::CallbackIncomingDispatch,
_ => CleanupStage::CallbackEventDispatch,
}
}
fn callback_label_for_event(event: &Event) -> String {
event
.call_id()
.map(|call_id| call_id.to_string())
.unwrap_or_else(|| "-".to_string())
}
use crate::api::handlers::{AutoAnswerHandler, RejectAllHandler};
impl CallbackPeer<AutoAnswerHandler> {
pub async fn with_auto_answer(config: Config) -> Result<Self> {
Self::new(AutoAnswerHandler, config).await
}
}
pub struct ClosureHandler {
f: Box<dyn Fn(&IncomingCall) -> CallHandlerDecision + Send + Sync>,
}
#[async_trait]
impl CallHandler for ClosureHandler {
async fn on_incoming_call(&self, call: IncomingCall) -> CallHandlerDecision {
match (self.f)(&call) {
CallHandlerDecision::Defer(_) => {
tracing::warn!("[ClosureHandler] Defer decision not supported; rejecting");
call.reject(503, "Service Unavailable");
CallHandlerDecision::Reject {
status: 503,
reason: "Service Unavailable".to_string(),
}
}
decision => decision,
}
}
}
impl CallbackPeer<ClosureHandler> {
pub async fn from_fn(
config: Config,
handler: impl Fn(&IncomingCall) -> CallHandlerDecision + Send + Sync + 'static,
) -> Result<Self> {
Self::new(
ClosureHandler {
f: Box::new(handler),
},
config,
)
.await
}
}
impl CallbackPeer<RejectAllHandler> {
pub async fn with_reject_all(config: Config) -> Result<Self> {
Self::new(RejectAllHandler::default(), config).await
}
pub async fn with_reject(
config: Config,
status: u16,
reason: impl Into<String>,
) -> Result<Self> {
Self::new(RejectAllHandler::new(status, reason), config).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::events::{MediaSecurityKeying, MediaSecurityProfile};
use crate::state_table::types::SessionId;
use rvoip_sip_core::types::sdp::CryptoSuite;
use std::sync::Mutex;
use tokio::task::JoinSet;
#[test]
fn bounded_terminal_callback_dedupe_evicts_oldest_entries() {
let mut dedupe = BoundedCallDedupe::with_capacity(2);
assert!(dedupe.insert(SessionId("call-1".to_string())));
assert!(!dedupe.insert(SessionId("call-1".to_string())));
assert!(dedupe.insert(SessionId("call-2".to_string())));
assert_eq!(dedupe.len(), 2);
assert!(dedupe.insert(SessionId("call-3".to_string())));
assert_eq!(dedupe.len(), 2);
assert!(dedupe.insert(SessionId("call-1".to_string())));
}
#[derive(Default)]
struct RecordingHandler {
events: Arc<Mutex<Vec<String>>>,
}
impl RecordingHandler {
fn push(&self, value: impl Into<String>) {
self.events.lock().unwrap().push(value.into());
}
}
#[async_trait]
impl CallHandler for RecordingHandler {
async fn on_event(&self, _event: Event) {
self.push("event");
}
async fn on_incoming_call(&self, call: IncomingCall) -> CallHandlerDecision {
self.push("incoming");
CallHandlerDecision::Defer(call.defer(std::time::Duration::from_millis(10)))
}
async fn on_call_established(&self, _handle: SessionHandle) {
self.push("answered");
}
async fn on_call_progress(
&self,
_handle: SessionHandle,
status_code: u16,
_reason: String,
_sdp: Option<String>,
) {
self.push(format!("progress:{status_code}"));
}
async fn on_call_ended(&self, _call_id: CallId, _reason: EndReason) {
self.push("ended");
}
async fn on_call_failed(&self, _call_id: CallId, status_code: u16, _reason: String) {
self.push(format!("failed:{status_code}"));
}
async fn on_call_cancelled(&self, _call_id: CallId) {
self.push("cancelled");
}
async fn on_dtmf(&self, _handle: SessionHandle, digit: char) {
self.push(format!("dtmf:{digit}"));
}
async fn on_media_security_negotiated(
&self,
_handle: SessionHandle,
state: MediaSecurityState,
) {
self.push(format!("media-security:{}", state.contexts_installed));
}
async fn on_call_on_hold(&self, _handle: SessionHandle) {
self.push("hold");
}
async fn on_call_resumed(&self, _handle: SessionHandle) {
self.push("resume");
}
async fn on_remote_call_on_hold(&self, _handle: SessionHandle) {
self.push("remote-hold");
}
async fn on_remote_call_resumed(&self, _handle: SessionHandle) {
self.push("remote-resume");
}
async fn on_transfer_accepted(&self, _handle: SessionHandle, _refer_to: String) {
self.push("transfer-accepted");
}
async fn on_refer_notify(
&self,
_handle: SessionHandle,
status_code: u16,
_reason: String,
_subscription_state: Option<SubscriptionState>,
_body: Option<String>,
) {
self.push(format!("refer-notify:{status_code}"));
}
async fn on_refer_progress(
&self,
_handle: SessionHandle,
status_code: u16,
_reason: String,
) {
self.push(format!("refer-progress:{status_code}"));
}
async fn on_refer_completed(
&self,
_handle: SessionHandle,
_target: String,
status_code: u16,
_reason: String,
) {
self.push(format!("refer-completed:{status_code}"));
}
async fn on_transfer_failed(
&self,
_handle: SessionHandle,
status_code: u16,
_reason: String,
) {
self.push(format!("transfer-failed:{status_code}"));
}
async fn on_registration_success(
&self,
_registrar: String,
_expires: u32,
_contact: String,
) {
self.push("registration-success");
}
async fn on_registration_failed(
&self,
_registrar: String,
status_code: u16,
_reason: String,
) {
self.push(format!("registration-failed:{status_code}"));
}
async fn on_unregistration_success(&self, _registrar: String) {
self.push("unregistration-success");
}
async fn on_unregistration_failed(&self, _registrar: String, _reason: String) {
self.push("unregistration-failed");
}
}
async fn drain(mut handlers: JoinSet<()>) {
while let Some(result) = handlers.join_next().await {
result.unwrap();
}
}
#[tokio::test]
async fn callback_dispatch_invokes_typed_hooks_for_public_events() {
let seen = Arc::new(Mutex::new(Vec::new()));
let handler = RecordingHandler {
events: seen.clone(),
};
let peer = CallbackPeer::new(handler, Config::local("callback-test", 15440))
.await
.unwrap();
let mut handlers = JoinSet::new();
let call_id = SessionId::new();
let failed_call_id = SessionId::new();
let cancelled_call_id = SessionId::new();
let events = vec![
Event::IncomingCall {
call_id: call_id.clone(),
from: "sip:a@example.test".into(),
to: "sip:b@example.test".into(),
sdp: None,
},
Event::CallAnswered {
call_id: call_id.clone(),
sdp: None,
},
Event::CallAnswered {
call_id: call_id.clone(),
sdp: None,
},
Event::CallProgress {
call_id: call_id.clone(),
status_code: 180,
reason: "Ringing".into(),
sdp: None,
},
Event::CallEnded {
call_id: call_id.clone(),
reason: "normal".into(),
},
Event::CallFailed {
call_id: failed_call_id.clone(),
status_code: 486,
reason: "Busy Here".into(),
},
Event::CallCancelled {
call_id: cancelled_call_id.clone(),
},
Event::CallCancelled {
call_id: cancelled_call_id.clone(),
},
Event::CallOnHold {
call_id: call_id.clone(),
},
Event::CallResumed {
call_id: call_id.clone(),
},
Event::RemoteCallOnHold {
call_id: call_id.clone(),
},
Event::RemoteCallResumed {
call_id: call_id.clone(),
},
Event::DtmfReceived {
call_id: call_id.clone(),
digit: '5',
},
Event::MediaSecurityNegotiated {
call_id: call_id.clone(),
keying: MediaSecurityKeying::Sdes,
suite: CryptoSuite::AesCm128HmacSha1_80,
profile: MediaSecurityProfile::RtpSavp,
contexts_installed: true,
},
Event::ReferReceived {
call_id: call_id.clone(),
refer_to: "sip:c@example.test".into(),
referred_by: None,
replaces: None,
transaction_id: "tx-1".into(),
transfer_type: "blind".into(),
request: None,
},
Event::TransferAccepted {
call_id: call_id.clone(),
refer_to: "sip:c@example.test".into(),
},
Event::ReferNotify {
call_id: call_id.clone(),
status_code: 100,
reason: "Trying".into(),
subscription_state: Some(SubscriptionState::parse("active;expires=60")),
body: Some("SIP/2.0 100 Trying".into()),
},
Event::ReferProgress {
call_id: call_id.clone(),
status_code: 180,
reason: "Ringing".into(),
},
Event::ReferCompleted {
call_id: call_id.clone(),
target: "sip:c@example.test".into(),
status_code: 200,
reason: "OK".into(),
},
Event::TransferFailed {
call_id: call_id.clone(),
status_code: 503,
reason: "Service Unavailable".into(),
},
Event::NotifyReceived {
call_id: call_id.clone(),
event_package: "refer".into(),
subscription_state: Some("active".into()),
content_type: Some("message/sipfrag".into()),
body: Some("SIP/2.0 100 Trying".into()),
request: None,
},
Event::RegistrationSuccess {
registrar: "sip:registrar.example.test".into(),
expires: 300,
contact: "sip:callback-test@example.test".into(),
},
Event::RegistrationFailed {
registrar: "sip:registrar.example.test".into(),
status_code: 403,
reason: "Forbidden".into(),
},
Event::UnregistrationSuccess {
registrar: "sip:registrar.example.test".into(),
},
Event::UnregistrationFailed {
registrar: "sip:registrar.example.test".into(),
reason: "timeout".into(),
},
];
for event in events {
peer.dispatch(event, &mut handlers).await;
}
drain(handlers).await;
let seen = seen.lock().unwrap().clone();
for expected in [
"incoming",
"answered",
"progress:180",
"ended",
"failed:486",
"cancelled",
"hold",
"resume",
"remote-hold",
"remote-resume",
"dtmf:5",
"media-security:true",
"transfer-accepted",
"refer-notify:100",
"refer-progress:180",
"refer-completed:200",
"transfer-failed:503",
"registration-success",
"registration-failed:403",
"unregistration-success",
"unregistration-failed",
] {
assert!(
seen.iter().any(|value| value == expected),
"missing {expected}"
);
}
assert_eq!(
seen.iter()
.filter(|value| value.as_str() == "event")
.count(),
25
);
assert_eq!(
seen.iter()
.filter(|value| value.as_str() == "answered")
.count(),
1
);
assert_eq!(
seen.iter()
.filter(|value| value.as_str() == "cancelled")
.count(),
1
);
}
#[tokio::test]
async fn callback_builder_invokes_common_closure_hooks() {
let seen = Arc::new(Mutex::new(Vec::new()));
let peer = CallbackPeer::builder(Config::local("callback-builder", 15441))
.on_incoming({
let seen = seen.clone();
move |_call| {
let seen = seen.clone();
async move {
seen.lock().unwrap().push("incoming".to_string());
CallHandlerDecision::Reject {
status: 486,
reason: "Busy Here".into(),
}
}
}
})
.on_established({
let seen = seen.clone();
move |_handle| {
let seen = seen.clone();
async move {
seen.lock().unwrap().push("established".to_string());
Ok(())
}
}
})
.on_dtmf({
let seen = seen.clone();
move |_handle, digit| {
let seen = seen.clone();
async move {
seen.lock().unwrap().push(format!("dtmf:{digit}"));
Ok(())
}
}
})
.on_ended({
let seen = seen.clone();
move |_call_id, _reason| {
let seen = seen.clone();
async move {
seen.lock().unwrap().push("ended".to_string());
Ok(())
}
}
})
.build()
.await
.unwrap();
let mut handlers = JoinSet::new();
let call_id = SessionId::new();
for event in [
Event::IncomingCall {
call_id: call_id.clone(),
from: "sip:a@example.test".into(),
to: "sip:b@example.test".into(),
sdp: None,
},
Event::CallAnswered {
call_id: call_id.clone(),
sdp: None,
},
Event::DtmfReceived {
call_id: call_id.clone(),
digit: '7',
},
Event::ReferReceived {
call_id: call_id.clone(),
refer_to: "sip:c@example.test".into(),
referred_by: None,
replaces: None,
transaction_id: "tx-builder".into(),
transfer_type: "blind".into(),
request: None,
},
Event::CallEnded {
call_id,
reason: "normal".into(),
},
] {
peer.dispatch(event, &mut handlers).await;
}
drain(handlers).await;
let seen = seen.lock().unwrap().clone();
for expected in ["incoming", "established", "dtmf:7", "ended"] {
assert!(
seen.iter().any(|value| value == expected),
"missing {expected}; saw {seen:?}"
);
}
}
#[tokio::test]
async fn callback_builder_requires_incoming_hook() {
let result = CallbackPeer::builder(Config::local("callback-builder-missing", 15443))
.build()
.await;
let err = match result {
Ok(_) => panic!("builder without on_incoming should fail"),
Err(err) => err,
};
assert!(err.to_string().contains("on_incoming"));
}
#[tokio::test]
async fn callback_control_can_initiate_calls_while_peer_can_be_moved_to_run() {
struct NoopHandler;
#[async_trait]
impl CallHandler for NoopHandler {
async fn on_incoming_call(&self, _call: IncomingCall) -> CallHandlerDecision {
CallHandlerDecision::Reject {
status: 486,
reason: "Busy Here".into(),
}
}
}
let peer = CallbackPeer::new(NoopHandler, Config::local("callback-control", 15442))
.await
.unwrap();
let control = peer.control();
let stop = peer.shutdown_handle();
let run_task = tokio::spawn(async move { peer.run().await });
let call_id = control
.invite("sip:unreachable@127.0.0.1:15443")
.send()
.await
.unwrap();
assert!(!call_id.to_string().is_empty());
control.shutdown();
stop.shutdown();
tokio::time::timeout(std::time::Duration::from_secs(2), run_task)
.await
.unwrap()
.unwrap()
.unwrap();
}
}