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}