#![deny(missing_docs)]
use std::net::IpAddr;
use std::sync::Arc;
use tokio::sync::mpsc;
use crate::adapters::SessionApiCrossCrateEvent;
use crate::api::endpoint::SipAccount;
use crate::api::events::{Event, MediaSecurityState, SipTrace};
use crate::api::handle::{CallId, SessionHandle};
use crate::api::incoming::IncomingCall;
use crate::api::performance::PerformanceConfig;
use crate::api::unified::{
Config, MediaMode, MediaSessionControllerConfig, RegistrationHandle, RegistrationInfo,
RtpSessionBufferConfig, RtpTransportBufferConfig, UnifiedCoordinator,
};
use crate::auth::SipClientAuth;
use crate::errors::{Result, SessionError};
pub use crate::api::unified::Config as PeerConfig;
pub struct EventReceiver {
rx: mpsc::Receiver<Arc<dyn rvoip_infra_common::events::cross_crate::CrossCrateEvent>>,
filter: Option<CallId>,
primed: std::collections::VecDeque<Event>,
}
impl EventReceiver {
pub(crate) fn new(
rx: mpsc::Receiver<Arc<dyn rvoip_infra_common::events::cross_crate::CrossCrateEvent>>,
) -> Self {
Self {
rx,
filter: None,
primed: std::collections::VecDeque::new(),
}
}
pub(crate) fn filtered(
rx: mpsc::Receiver<Arc<dyn rvoip_infra_common::events::cross_crate::CrossCrateEvent>>,
call_id: CallId,
) -> Self {
Self {
rx,
filter: Some(call_id),
primed: std::collections::VecDeque::new(),
}
}
pub(crate) fn prime(&mut self, event: Event) {
self.primed.push_back(event);
}
pub async fn next(&mut self) -> Option<Event> {
if let Some(event) = self.primed.pop_front() {
return Some(event);
}
loop {
let raw = self.rx.recv().await?;
let session_event = raw.as_any().downcast_ref::<SessionApiCrossCrateEvent>()?;
let event = session_event.event.clone();
if let Some(ref filter) = self.filter {
if event.call_id() != Some(filter) {
continue;
}
}
return Some(event);
}
}
pub fn try_next(&mut self) -> Option<Event> {
if let Some(event) = self.primed.pop_front() {
return Some(event);
}
loop {
let raw = self.rx.try_recv().ok()?;
let session_event = raw.as_any().downcast_ref::<SessionApiCrossCrateEvent>()?;
let event = session_event.event.clone();
if let Some(ref filter) = self.filter {
if event.call_id() != Some(filter) {
continue;
}
}
return Some(event);
}
}
pub async fn next_incoming(&mut self) -> Option<(CallId, String, String, Option<String>)> {
loop {
match self.next().await? {
Event::IncomingCall {
call_id,
from,
to,
sdp,
} => {
return Some((call_id, from, to, sdp));
}
_ => continue,
}
}
}
pub async fn next_dtmf(&mut self) -> Option<(CallId, char)> {
loop {
match self.next().await? {
Event::DtmfReceived { call_id, digit } => {
return Some((call_id, digit));
}
_ => continue,
}
}
}
pub async fn next_progress(&mut self) -> Option<(CallId, u16, String, Option<String>)> {
loop {
match self.next().await? {
Event::CallProgress {
call_id,
status_code,
reason,
sdp,
} => return Some((call_id, status_code, reason, sdp)),
_ => continue,
}
}
}
pub async fn next_media_security_negotiated(&mut self) -> Option<(CallId, MediaSecurityState)> {
loop {
let event = self.next().await?;
if let Some(state) = media_security_state_from_event(event) {
return Some(state);
}
}
}
pub async fn next_sip_trace(&mut self) -> Option<SipTrace> {
loop {
match self.next().await? {
Event::SipTrace(trace) => return Some(trace),
_ => continue,
}
}
}
pub async fn next_transfer(&mut self) -> Option<Event> {
loop {
let event = self.next().await?;
if event.is_transfer_event() {
return Some(event);
}
}
}
pub async fn next_where<F: FnMut(&Event) -> bool>(
&mut self,
mut predicate: F,
) -> Option<Event> {
loop {
let event = self.next().await?;
if predicate(&event) {
return Some(event);
}
}
}
pub async fn next_for_call(&mut self, call_id: &CallId) -> Option<Event> {
loop {
let event = self.next().await?;
if event.call_id() == Some(call_id) {
return Some(event);
}
}
}
}
#[derive(Clone)]
pub struct PeerControl {
pub(crate) coordinator: Arc<UnifiedCoordinator>,
pub(crate) local_uri: String,
}
impl PeerControl {
pub async fn accept(&self, call_id: &CallId) -> Result<SessionHandle> {
self.coordinator.accept_call(call_id).await?;
Ok(SessionHandle::new(
call_id.clone(),
self.coordinator.clone(),
))
}
pub async fn reject(&self, call_id: &CallId, status: u16, reason: &str) -> Result<()> {
self.coordinator
.reject(call_id)
.with_status(status)
.with_reason(reason.to_string())
.send()
.await
}
pub async fn send_early_media(&self, call_id: &CallId, sdp: Option<String>) -> Result<()> {
self.coordinator.send_early_media(call_id, sdp).await
}
pub async fn subscribe_events(&self) -> Result<EventReceiver> {
let rx = self.coordinator.subscribe_events().await?;
Ok(EventReceiver::new(rx))
}
pub fn coordinator(&self) -> &Arc<UnifiedCoordinator> {
&self.coordinator
}
pub fn session(&self, call_id: &CallId) -> SessionHandle {
SessionHandle::new(call_id.clone(), self.coordinator.clone())
}
pub fn invite(&self, target: impl Into<String>) -> crate::api::send::OutboundCallBuilder {
self.coordinator
.invite(Some(self.local_uri.clone()), target)
}
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 fn register_account(&self, account: &SipAccount) -> crate::api::send::RegisterBuilder {
let mut builder = self
.register(
account.registrar.clone(),
account.effective_auth_username().to_string(),
account.password.clone(),
)
.with_expires(account.expires);
if let Some(from_uri) = &account.from_uri {
builder = builder.with_from_uri(from_uri.clone());
}
if let Some(contact_uri) = &account.contact_uri {
builder = builder.with_contact_uri(contact_uri.clone());
}
builder
}
pub async fn register_and_wait(
&self,
registrar: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
timeout: Option<std::time::Duration>,
) -> Result<RegistrationInfo> {
let mut events = self.subscribe_events().await?;
let handle = self.register(registrar, username, password).send().await?;
wait_for_peer_registration(&self.coordinator, &mut events, &handle, timeout).await
}
}
async fn wait_for_peer_registration(
coordinator: &Arc<UnifiedCoordinator>,
events: &mut EventReceiver,
handle: &RegistrationHandle,
timeout: Option<std::time::Duration>,
) -> Result<RegistrationInfo> {
let registrar = coordinator
.registration_info(handle)
.await?
.registrar
.unwrap_or_default();
let matches = |ev: &str| registrar.is_empty() || ev == registrar;
let fut = async {
loop {
match events.next().await {
Some(Event::RegistrationSuccess { registrar: r, .. }) if matches(&r) => {
return coordinator.registration_info(handle).await;
}
Some(Event::RegistrationFailed {
registrar: r,
status_code,
reason,
}) if matches(&r) => {
return Err(SessionError::Other(format!(
"registration failed for {r}: {status_code} {reason}"
)));
}
Some(_) => {}
None => {
return Err(SessionError::Other(
"event stream closed while waiting for registration".to_string(),
));
}
}
}
};
match timeout {
Some(duration) => tokio::time::timeout(duration, fut)
.await
.map_err(|_| SessionError::Timeout("register_and_wait timed out".to_string()))?,
None => fut.await,
}
}
pub struct StreamPeer {
control: PeerControl,
events: EventReceiver,
}
impl StreamPeer {
pub async fn new(name: &str) -> Result<Self> {
let mut config = Config::default();
config.local_uri = format!("sip:{}@{}:{}", name, config.local_ip, config.sip_port);
Self::with_config(config).await
}
pub async fn with_config(config: Config) -> Result<Self> {
let local_uri = config.local_uri.clone();
let coordinator = UnifiedCoordinator::new(config).await?;
let event_rx = coordinator.subscribe_events().await?;
Ok(Self {
control: PeerControl {
coordinator,
local_uri,
},
events: EventReceiver::new(event_rx),
})
}
pub fn split(self) -> (PeerControl, EventReceiver) {
(self.control, self.events)
}
pub fn control(&self) -> &PeerControl {
&self.control
}
pub fn coordinator(&self) -> &Arc<UnifiedCoordinator> {
self.control.coordinator()
}
pub fn session(&self, call_id: &CallId) -> SessionHandle {
self.control.session(call_id)
}
pub fn invite(&self, target: impl Into<String>) -> crate::api::send::OutboundCallBuilder {
self.control.invite(target)
}
pub fn register(
&self,
registrar: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
) -> crate::api::send::RegisterBuilder {
self.control.register(registrar, username, password)
}
pub fn register_account(&self, account: &SipAccount) -> crate::api::send::RegisterBuilder {
self.control.register_account(account)
}
pub async fn register_and_wait(
&self,
registrar: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
timeout: Option<std::time::Duration>,
) -> Result<RegistrationInfo> {
self.control
.register_and_wait(registrar, username, password, timeout)
.await
}
pub async fn wait_for_incoming(&mut self) -> Result<IncomingCall> {
loop {
match self.events.next().await {
Some(Event::IncomingCall {
call_id,
from,
to,
sdp,
}) => {
let coord = self.control.coordinator.clone();
let parsed = coord.session_registry.peek_pending_incoming_request().await;
let transport = coord
.session_registry
.peek_pending_incoming_transport()
.await;
let incoming = match parsed {
Some(req) => IncomingCall::with_request(call_id, from, to, sdp, coord, req),
None => IncomingCall::new(call_id, from, to, sdp, coord),
}
.with_transport_context(
transport
.as_deref()
.cloned()
.unwrap_or_else(crate::auth::SipTransportSecurityContext::unknown),
);
return Ok(incoming);
}
None => return Err(SessionError::Other("Event channel closed".to_string())),
_ => {}
}
}
}
pub async fn wait_for_answered(&mut self, call_id: &CallId) -> Result<SessionHandle> {
loop {
match self.events.next().await {
Some(Event::CallAnswered {
call_id: answered_id,
..
}) if &answered_id == call_id => {
return Ok(SessionHandle::new(
answered_id,
self.control.coordinator.clone(),
));
}
Some(Event::CallFailed {
call_id: failed_id,
reason,
status_code,
}) if &failed_id == call_id => {
return Err(SessionError::Other(format!(
"Call failed with {}: {}",
status_code, reason
)));
}
None => return Err(SessionError::Other("Event channel closed".to_string())),
_ => {}
}
}
}
pub async fn wait_for_progress<F>(
&mut self,
call_id: &CallId,
mut predicate: F,
) -> Result<Event>
where
F: FnMut(&Event) -> bool,
{
loop {
match self.events.next().await {
Some(event @ Event::CallProgress { .. }) => {
if event.call_id() == Some(call_id) && predicate(&event) {
return Ok(event);
}
}
Some(Event::CallAnswered {
call_id: answered_id,
..
}) if &answered_id == call_id => {
return Err(SessionError::Other(
"call answered before matching provisional progress".to_string(),
));
}
Some(Event::CallFailed {
call_id: failed_id,
reason,
status_code,
}) if &failed_id == call_id => {
return Err(SessionError::Other(format!(
"Call failed with {}: {}",
status_code, reason
)));
}
Some(Event::CallCancelled { call_id: id }) if &id == call_id => {
return Err(SessionError::Other(
"call cancelled before matching provisional progress".to_string(),
));
}
None => return Err(SessionError::Other("Event channel closed".to_string())),
_ => {}
}
}
}
pub async fn wait_for_media_security(
&mut self,
call_id: &CallId,
) -> Result<MediaSecurityState> {
loop {
match self.events.next().await {
Some(event) if event.call_id() == Some(call_id) => {
if let Some((_, state)) = media_security_state_from_event(event) {
return Ok(state);
}
}
None => return Err(SessionError::Other("Event channel closed".to_string())),
_ => {}
}
}
}
pub async fn wait_for_ended(&mut self, call_id: &CallId) -> Result<String> {
loop {
match self.events.next().await {
Some(Event::CallEnded {
call_id: ended_id,
reason,
}) if &ended_id == call_id => {
return Ok(reason);
}
None => return Err(SessionError::Other("Event channel closed".to_string())),
_ => {}
}
}
}
pub async fn next_event(&mut self) -> Option<Event> {
self.events.next().await
}
pub async fn is_registered(
&self,
handle: &crate::api::unified::RegistrationHandle,
) -> Result<bool> {
self.control.coordinator.is_registered(handle).await
}
pub async fn unregister(&self, handle: &crate::api::unified::RegistrationHandle) -> Result<()> {
self.control.coordinator.unregister(handle).await
}
pub async fn shutdown(self) -> Result<()> {
self.control.coordinator.shutdown_gracefully(None).await?;
drop(self);
Ok(())
}
pub fn shutdown_handle(&self) -> crate::api::callback_peer::ShutdownHandle {
self.control.coordinator.shutdown_handle()
}
pub fn builder() -> StreamPeerBuilder {
StreamPeerBuilder::new()
}
}
fn media_security_state_from_event(event: Event) -> Option<(CallId, MediaSecurityState)> {
match event {
Event::MediaSecurityNegotiated {
call_id,
keying,
suite,
profile,
contexts_installed,
} => Some((
call_id,
MediaSecurityState {
keying,
suite,
profile,
contexts_installed,
},
)),
_ => None,
}
}
pub struct StreamPeerBuilder {
config: Config,
name: Option<String>,
}
impl StreamPeerBuilder {
pub fn new() -> Self {
Self {
config: Config::default(),
name: None,
}
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
pub fn sip_port(mut self, port: u16) -> Self {
self.config.sip_port = port;
self.config.bind_addr.set_port(port);
self
}
pub fn local_ip(mut self, ip: IpAddr) -> Self {
self.config.local_ip = ip;
self.config.bind_addr.set_ip(ip);
self
}
pub fn media_ports(mut self, start: u16, end: u16) -> Self {
self.config = self.config.with_media_ports(start, end);
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 rtp_session_buffer_config(mut self, config: RtpSessionBufferConfig) -> Self {
self.config = self.config.with_rtp_session_buffer_config(config);
self
}
pub fn rtp_transport_buffer_config(mut self, config: RtpTransportBufferConfig) -> Self {
self.config = self.config.with_rtp_transport_buffer_config(config);
self
}
pub fn media_session_controller_config(mut self, config: MediaSessionControllerConfig) -> Self {
self.config = self.config.with_media_session_controller_config(config);
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 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 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 config(mut self, config: Config) -> Self {
self.config = config;
self
}
pub fn with_credentials(mut self, username: &str, password: &str) -> Self {
self.config.credentials = Some(crate::types::Credentials::new(username, password));
self
}
pub fn with_auth(mut self, auth: SipClientAuth) -> Self {
self.config.auth = Some(auth);
self
}
pub fn with_bearer_token(mut self, token: impl Into<String>) -> Self {
self.config.auth = Some(SipClientAuth::bearer_token(token));
self
}
pub fn with_basic_credentials(
mut self,
username: impl Into<String>,
password: impl Into<String>,
) -> Self {
self.config.auth = Some(SipClientAuth::basic(username, password));
self
}
pub async fn build(mut self) -> Result<StreamPeer> {
if let Some(name) = self.name {
self.config.local_uri = format!(
"sip:{}@{}:{}",
name, self.config.local_ip, self.config.sip_port
);
}
StreamPeer::with_config(self.config).await
}
}
impl Default for StreamPeerBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{EventReceiver, StreamPeerBuilder};
use crate::adapters::SessionApiCrossCrateEvent;
use crate::api::events::{Event, SipTrace, SipTraceDirection};
use crate::api::unified::{
MediaSessionControllerConfig, RtpSessionBufferConfig, RtpTransportBufferConfig,
};
use crate::state_table::types::SessionId;
use rvoip_infra_common::events::cross_crate::CrossCrateEvent;
use std::sync::Arc;
use tokio::sync::mpsc;
#[test]
fn stream_peer_builder_exposes_rtp_media_buffer_tuning() {
let session_buffers = RtpSessionBufferConfig {
sender_channel_capacity: 7,
receiver_channel_capacity: 5,
event_channel_capacity: 11,
};
let transport_buffers = RtpTransportBufferConfig {
event_channel_capacity: 13,
recv_buffer_size: 2048,
rtcp_recv_buffer_size: 1024,
};
let mut media_config = MediaSessionControllerConfig::default();
media_config.rtp_buffer_size = 960;
media_config.rtp_buffer_initial_count = 3;
media_config.rtp_buffer_max_count = 9;
let builder = StreamPeerBuilder::new()
.media_session_controller_config(media_config)
.rtp_session_buffer_config(session_buffers)
.rtp_transport_buffer_config(transport_buffers);
assert_eq!(builder.config.rtp_session_buffer_config, session_buffers);
assert_eq!(
builder.config.rtp_transport_buffer_config,
transport_buffers
);
assert_eq!(
builder
.config
.media_session_controller_config
.rtp_buffer_size,
960
);
assert_eq!(
builder
.config
.media_session_controller_config
.rtp_buffer_initial_count,
3
);
assert_eq!(
builder
.config
.media_session_controller_config
.rtp_buffer_max_count,
9
);
}
#[tokio::test]
async fn event_receiver_returns_sip_trace_events() {
let (tx, rx) = mpsc::channel::<Arc<dyn CrossCrateEvent>>(4);
tx.send(SessionApiCrossCrateEvent::new(Event::CallEnded {
call_id: SessionId("other".into()),
reason: "done".into(),
}))
.await
.unwrap();
tx.send(SessionApiCrossCrateEvent::new(Event::SipTrace(
trace_event(),
)))
.await
.unwrap();
drop(tx);
let mut receiver = EventReceiver::new(rx);
let trace = receiver.next_sip_trace().await.unwrap();
assert_eq!(trace.sip_call_id.as_deref(), Some("wire-call"));
assert_eq!(trace.session_id, Some(SessionId("session-1".into())));
}
fn trace_event() -> SipTrace {
SipTrace {
direction: SipTraceDirection::Outbound,
transport: "UDP".into(),
local_addr: "127.0.0.1:5060".into(),
remote_addr: "127.0.0.1:5080".into(),
timestamp_unix_millis: 1,
start_line: "INVITE sip:bob@example.com SIP/2.0".into(),
sip_call_id: Some("wire-call".into()),
session_id: Some(SessionId("session-1".into())),
raw_message: "INVITE sip:bob@example.com SIP/2.0\n\n".into(),
original_len: 40,
truncated: false,
redacted: true,
}
}
}