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