use arc_swap::ArcSwapOption;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use crate::types::{DialogId, IncomingCallInfo, MediaSessionId, SessionId};
#[derive(Clone)]
pub struct SessionRegistry {
current_session: Arc<ArcSwapOption<SessionId>>,
current_dialog: Arc<ArcSwapOption<DialogId>>,
current_media: Arc<ArcSwapOption<MediaSessionId>>,
pending_incoming_call: Arc<ArcSwapOption<IncomingCallInfo>>,
pending_incoming_request: Arc<ArcSwapOption<rvoip_sip_core::Request>>,
pending_incoming_transport: Arc<ArcSwapOption<crate::auth::SipTransportSecurityContext>>,
dialog_mapped_total: Arc<AtomicU64>,
media_mapped_total: Arc<AtomicU64>,
removed_total: Arc<AtomicU64>,
remove_missing_total: Arc<AtomicU64>,
}
impl SessionRegistry {
pub fn new() -> Self {
Self {
current_session: Arc::new(ArcSwapOption::empty()),
current_dialog: Arc::new(ArcSwapOption::empty()),
current_media: Arc::new(ArcSwapOption::empty()),
pending_incoming_call: Arc::new(ArcSwapOption::empty()),
pending_incoming_request: Arc::new(ArcSwapOption::empty()),
pending_incoming_transport: Arc::new(ArcSwapOption::empty()),
dialog_mapped_total: Arc::new(AtomicU64::new(0)),
media_mapped_total: Arc::new(AtomicU64::new(0)),
removed_total: Arc::new(AtomicU64::new(0)),
remove_missing_total: Arc::new(AtomicU64::new(0)),
}
}
pub async fn map_dialog(&self, session_id: SessionId, dialog_id: DialogId) {
let session_id = Arc::new(session_id);
#[cfg(feature = "perf-infra-memory-diagnostics")]
let previous_session = self.current_session.swap(Some(session_id.clone()));
#[cfg(not(feature = "perf-infra-memory-diagnostics"))]
let _previous_session = self.current_session.swap(Some(session_id.clone()));
#[cfg(feature = "perf-infra-memory-diagnostics")]
self.record_session_mapping_for_memory_diagnostics(
previous_session.as_deref(),
&session_id,
);
self.current_dialog.store(Some(Arc::new(dialog_id)));
self.dialog_mapped_total.fetch_add(1, Ordering::Relaxed);
}
pub async fn map_media(&self, session_id: SessionId, media_id: MediaSessionId) {
let session_id = Arc::new(session_id);
#[cfg(feature = "perf-infra-memory-diagnostics")]
let previous_session = self.current_session.swap(Some(session_id.clone()));
#[cfg(not(feature = "perf-infra-memory-diagnostics"))]
let _previous_session = self.current_session.swap(Some(session_id.clone()));
#[cfg(feature = "perf-infra-memory-diagnostics")]
self.record_session_mapping_for_memory_diagnostics(
previous_session.as_deref(),
&session_id,
);
self.current_media.store(Some(Arc::new(media_id)));
self.media_mapped_total.fetch_add(1, Ordering::Relaxed);
}
pub async fn get_session_by_dialog(&self, dialog_id: &DialogId) -> Option<SessionId> {
if self.current_dialog.load().as_deref() == Some(dialog_id) {
self.current_session.load().as_deref().cloned()
} else {
None
}
}
pub async fn get_session_by_media(&self, media_id: &MediaSessionId) -> Option<SessionId> {
if self.current_media.load().as_deref() == Some(media_id) {
self.current_session.load().as_deref().cloned()
} else {
None
}
}
pub async fn get_dialog_by_session(&self, session_id: &SessionId) -> Option<DialogId> {
if self.current_session.load().as_deref() == Some(session_id) {
self.current_dialog.load().as_deref().cloned()
} else {
None
}
}
pub async fn get_media_by_session(&self, session_id: &SessionId) -> Option<MediaSessionId> {
if self.current_session.load().as_deref() == Some(session_id) {
self.current_media.load().as_deref().cloned()
} else {
None
}
}
pub async fn remove_session(&self, session_id: &SessionId) {
loop {
let current = self.current_session.load();
if current.as_deref() != Some(session_id) {
self.remove_missing_total.fetch_add(1, Ordering::Relaxed);
return;
}
let previous = self.current_session.compare_and_swap(&*current, None);
if Self::option_arc_ptr_eq(¤t, &previous) {
self.current_dialog.store(None);
self.current_media.store(None);
self.removed_total.fetch_add(1, Ordering::Relaxed);
#[cfg(feature = "perf-infra-memory-diagnostics")]
rvoip_infra_common::memory_diagnostics::record_dropped(
"sip.session_registry.current_session",
std::mem::size_of::<SessionId>(),
);
return;
}
}
}
#[cfg(feature = "perf-tests")]
pub(crate) fn perf_lifecycle_counts(&self) -> serde_json::Value {
serde_json::json!({
"dialog_mapped_total": self.dialog_mapped_total.load(Ordering::Relaxed),
"media_mapped_total": self.media_mapped_total.load(Ordering::Relaxed),
"removed_total": self.removed_total.load(Ordering::Relaxed),
"remove_missing_total": self.remove_missing_total.load(Ordering::Relaxed),
})
}
pub async fn contains_session(&self, session_id: &SessionId) -> bool {
self.current_session.load().as_deref() == Some(session_id)
}
pub async fn session_count(&self) -> usize {
usize::from(self.current_session.load().is_some())
}
pub async fn clear(&self) {
#[cfg(feature = "perf-infra-memory-diagnostics")]
let previous_session = self.current_session.swap(None);
#[cfg(not(feature = "perf-infra-memory-diagnostics"))]
let _previous_session = self.current_session.swap(None);
#[cfg(feature = "perf-infra-memory-diagnostics")]
if previous_session.is_some() {
rvoip_infra_common::memory_diagnostics::record_dropped(
"sip.session_registry.current_session",
std::mem::size_of::<SessionId>(),
);
}
self.current_dialog.store(None);
self.current_media.store(None);
self.pending_incoming_call.store(None);
self.pending_incoming_request.store(None);
self.pending_incoming_transport.store(None);
}
#[cfg(feature = "perf-infra-memory-diagnostics")]
fn record_session_mapping_for_memory_diagnostics(
&self,
previous: Option<&SessionId>,
session_id: &SessionId,
) {
if previous == Some(session_id) {
return;
}
if previous.is_some() {
rvoip_infra_common::memory_diagnostics::record_dropped(
"sip.session_registry.current_session",
std::mem::size_of::<SessionId>(),
);
}
rvoip_infra_common::memory_diagnostics::record_created(
"sip.session_registry.current_session",
std::mem::size_of::<SessionId>(),
);
}
fn option_arc_ptr_eq<T>(
left: &Option<Arc<T>>,
right: &arc_swap::Guard<Option<Arc<T>>>,
) -> bool {
match (left.as_ref(), right.as_ref()) {
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
(None, None) => true,
_ => false,
}
}
pub async fn store_pending_incoming_call(
&self,
_session_id: SessionId,
info: IncomingCallInfo,
) {
self.pending_incoming_call.store(Some(Arc::new(info)));
}
pub async fn take_pending_incoming_call(
&self,
_session_id: &SessionId,
) -> Option<IncomingCallInfo> {
self.pending_incoming_call
.swap(None)
.map(|arc| (*arc).clone())
}
pub async fn store_pending_incoming_request(&self, request: Arc<rvoip_sip_core::Request>) {
self.pending_incoming_request.store(Some(request));
}
pub async fn store_pending_incoming_transport(
&self,
transport: crate::auth::SipTransportSecurityContext,
) {
self.pending_incoming_transport
.store(Some(Arc::new(transport)));
}
pub async fn peek_pending_incoming_request(&self) -> Option<Arc<rvoip_sip_core::Request>> {
self.pending_incoming_request.load_full()
}
pub async fn peek_pending_incoming_transport(
&self,
) -> Option<Arc<crate::auth::SipTransportSecurityContext>> {
self.pending_incoming_transport.load_full()
}
pub async fn take_pending_incoming_request(&self) -> Option<Arc<rvoip_sip_core::Request>> {
self.pending_incoming_request.swap(None)
}
pub async fn take_pending_incoming_transport(
&self,
) -> Option<Arc<crate::auth::SipTransportSecurityContext>> {
self.pending_incoming_transport.swap(None)
}
}
impl Default for SessionRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_dialog_mapping() {
let registry = SessionRegistry::new();
let session_id = SessionId::new();
let dialog_id = DialogId::new();
registry
.map_dialog(session_id.clone(), dialog_id.clone())
.await;
assert_eq!(
registry.get_session_by_dialog(&dialog_id).await,
Some(session_id.clone())
);
assert_eq!(
registry.get_dialog_by_session(&session_id).await,
Some(dialog_id)
);
}
#[tokio::test]
async fn test_media_mapping() {
let registry = SessionRegistry::new();
let session_id = SessionId::new();
let media_id = MediaSessionId::new_v4();
registry
.map_media(session_id.clone(), media_id.clone())
.await;
assert_eq!(
registry.get_session_by_media(&media_id).await,
Some(session_id.clone())
);
assert_eq!(
registry.get_media_by_session(&session_id).await,
Some(media_id)
);
}
#[tokio::test]
async fn test_remove_session() {
let registry = SessionRegistry::new();
let session_id = SessionId::new();
let dialog_id = DialogId::new();
let media_id = MediaSessionId::new_v4();
registry
.map_dialog(session_id.clone(), dialog_id.clone())
.await;
registry
.map_media(session_id.clone(), media_id.clone())
.await;
assert!(registry.contains_session(&session_id).await);
registry.remove_session(&session_id).await;
assert!(!registry.contains_session(&session_id).await);
assert_eq!(registry.get_session_by_dialog(&dialog_id).await, None);
assert_eq!(registry.get_session_by_media(&media_id).await, None);
}
#[tokio::test]
async fn test_session_count() {
let registry = SessionRegistry::new();
assert_eq!(registry.session_count().await, 0);
let session1 = SessionId::new();
let session2 = SessionId::new();
registry.map_dialog(session1.clone(), DialogId::new()).await;
registry.map_dialog(session2.clone(), DialogId::new()).await;
assert_eq!(registry.session_count().await, 1);
}
#[tokio::test]
async fn test_clear() {
let registry = SessionRegistry::new();
let session_id = SessionId::new();
registry
.map_dialog(session_id.clone(), DialogId::new())
.await;
registry
.map_media(session_id.clone(), MediaSessionId::new_v4())
.await;
registry.clear().await;
assert_eq!(registry.session_count().await, 0);
assert!(!registry.contains_session(&session_id).await);
}
}