ftth_rsipstack/dialog/
dialog_layer.rs

1use super::authenticate::Credential;
2use super::dialog::DialogStateSender;
3use super::{dialog::Dialog, server_dialog::ServerInviteDialog, DialogId};
4use crate::dialog::dialog::DialogInner;
5use crate::transaction::key::TransactionRole;
6use crate::transaction::make_tag;
7use crate::transaction::{endpoint::EndpointInnerRef, transaction::Transaction};
8use crate::Result;
9use rsip::Request;
10use std::sync::atomic::{AtomicU32, Ordering};
11use std::{
12    collections::HashMap,
13    sync::{Arc, RwLock},
14};
15use tracing::info;
16
17/// Internal Dialog Layer State
18///
19/// `DialogLayerInner` contains the core state for managing multiple SIP dialogs.
20/// It maintains a registry of active dialogs and tracks sequence numbers for
21/// dialog creation.
22///
23/// # Fields
24///
25/// * `last_seq` - Atomic counter for generating unique sequence numbers
26/// * `dialogs` - Thread-safe map of active dialogs indexed by DialogId
27///
28/// # Thread Safety
29///
30/// This structure is designed to be shared across multiple threads safely:
31/// * `last_seq` uses atomic operations for lock-free increments
32/// * `dialogs` uses RwLock for concurrent read access with exclusive writes
33pub struct DialogLayerInner {
34    pub(super) last_seq: AtomicU32,
35    pub(super) dialogs: RwLock<HashMap<DialogId, Dialog>>,
36}
37pub type DialogLayerInnerRef = Arc<DialogLayerInner>;
38
39/// SIP Dialog Layer
40///
41/// `DialogLayer` provides high-level dialog management functionality for SIP
42/// applications. It handles dialog creation, lookup, and lifecycle management
43/// while coordinating with the transaction layer.
44///
45/// # Key Responsibilities
46///
47/// * Creating and managing SIP dialogs
48/// * Dialog identification and routing
49/// * Dialog state tracking and cleanup
50/// * Integration with transaction layer
51/// * Sequence number management
52///
53/// # Usage Patterns
54///
55/// ## Server-side Dialog Creation
56///
57/// ```rust,no_run
58/// use rsipstack::dialog::dialog_layer::DialogLayer;
59/// use rsipstack::transaction::endpoint::EndpointInner;
60/// use std::sync::Arc;
61///
62/// # fn example() -> rsipstack::Result<()> {
63/// # let endpoint: Arc<EndpointInner> = todo!();
64/// # let transaction = todo!();
65/// # let state_sender = todo!();
66/// # let credential = None;
67/// # let contact_uri = None;
68/// // Create dialog layer
69/// let dialog_layer = DialogLayer::new(endpoint.clone());
70///
71/// // Handle incoming INVITE transaction
72/// let server_dialog = dialog_layer.get_or_create_server_invite(
73///     &transaction,
74///     state_sender,
75///     credential,
76///     contact_uri
77/// )?;
78///
79/// // Accept the call
80/// server_dialog.accept(None, None)?;
81/// # Ok(())
82/// # }
83/// ```
84///
85/// ## Dialog Lookup and Routing
86///
87/// ```rust,no_run
88/// # use rsipstack::dialog::dialog_layer::DialogLayer;
89/// # async fn example() -> rsipstack::Result<()> {
90/// # let dialog_layer: DialogLayer = todo!();
91/// # let request = todo!();
92/// # let transaction = todo!();
93/// // Find existing dialog for incoming request
94/// if let Some(mut dialog) = dialog_layer.match_dialog(&request) {
95///     // Route to existing dialog
96///     dialog.handle(transaction).await?;
97/// } else {
98///     // Create new dialog or reject
99/// }
100/// # Ok(())
101/// # }
102/// ```
103///
104/// ## Dialog Cleanup
105///
106/// ```rust,no_run
107/// # use rsipstack::dialog::dialog_layer::DialogLayer;
108/// # fn example() {
109/// # let dialog_layer: DialogLayer = todo!();
110/// # let dialog_id = todo!();
111/// // Remove completed dialog
112/// dialog_layer.remove_dialog(&dialog_id);
113/// # }
114/// ```
115///
116/// # Dialog Lifecycle
117///
118/// 1. **Creation** - Dialog created from incoming INVITE or outgoing request
119/// 2. **Early State** - Dialog exists but not yet confirmed
120/// 3. **Confirmed** - Dialog established with 2xx response and ACK
121/// 4. **Active** - Dialog can exchange in-dialog requests
122/// 5. **Terminated** - Dialog ended with BYE or error
123/// 6. **Cleanup** - Dialog removed from layer
124///
125/// # Thread Safety
126///
127/// DialogLayer is thread-safe and can be shared across multiple tasks:
128/// * Dialog lookup operations are concurrent
129/// * Dialog creation is serialized when needed
130/// * Automatic cleanup prevents memory leaks
131pub struct DialogLayer {
132    pub endpoint: EndpointInnerRef,
133    pub inner: DialogLayerInnerRef,
134}
135
136impl DialogLayer {
137    pub fn new(endpoint: EndpointInnerRef) -> Self {
138        Self {
139            endpoint,
140            inner: Arc::new(DialogLayerInner {
141                last_seq: AtomicU32::new(0),
142                dialogs: RwLock::new(HashMap::new()),
143            }),
144        }
145    }
146
147    pub fn get_or_create_server_invite(
148        &self,
149        tx: &Transaction,
150        state_sender: DialogStateSender,
151        credential: Option<Credential>,
152        contact: Option<rsip::Uri>,
153    ) -> Result<ServerInviteDialog> {
154        let mut id = DialogId::try_from(&tx.original)?;
155        if !id.to_tag.is_empty() {
156            let dlg = self.inner.dialogs.read().unwrap().get(&id).cloned();
157            match dlg {
158                Some(Dialog::ServerInvite(dlg)) => return Ok(dlg),
159                _ => {
160                    return Err(crate::Error::DialogError(
161                        "the dialog not found".to_string(),
162                        id,
163                        rsip::StatusCode::CallTransactionDoesNotExist,
164                    ));
165                }
166            }
167        }
168        id.to_tag = make_tag().to_string(); // generate to tag
169
170        let dlg_inner = DialogInner::new(
171            TransactionRole::Server,
172            id.clone(),
173            tx.original.clone(),
174            self.endpoint.clone(),
175            state_sender,
176            credential,
177            contact,
178            tx.tu_sender.clone(),
179        )?;
180
181        let dialog = ServerInviteDialog {
182            inner: Arc::new(dlg_inner),
183        };
184        self.inner
185            .dialogs
186            .write()
187            .unwrap()
188            .insert(id.clone(), Dialog::ServerInvite(dialog.clone()));
189        info!("server invite dialog created: {id}");
190        Ok(dialog)
191    }
192
193    pub fn increment_last_seq(&self) -> u32 {
194        self.inner.last_seq.fetch_add(1, Ordering::Relaxed);
195        self.inner.last_seq.load(Ordering::Relaxed)
196    }
197
198    pub fn len(&self) -> usize {
199        self.inner.dialogs.read().unwrap().len()
200    }
201
202    pub fn get_dialog(&self, id: &DialogId) -> Option<Dialog> {
203        match self.inner.dialogs.read() {
204            Ok(dialogs) => match dialogs.get(id) {
205                Some(dialog) => Some(dialog.clone()),
206                None => None,
207            },
208            Err(_) => None,
209        }
210    }
211
212    pub fn remove_dialog(&self, id: &DialogId) {
213        info!(%id, "remove dialog");
214        self.inner
215            .dialogs
216            .write()
217            .unwrap()
218            .remove(id)
219            .map(|d| d.on_remove());
220    }
221
222    pub fn match_dialog(&self, req: &Request) -> Option<Dialog> {
223        let id = DialogId::try_from(req).ok()?;
224        self.get_dialog(&id)
225    }
226}