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}