ftth_rsipstack/dialog/invitation.rs
1use super::{
2 authenticate::Credential,
3 client_dialog::ClientInviteDialog,
4 dialog::{DialogInner, DialogStateSender},
5 dialog_layer::DialogLayer,
6};
7use crate::{
8 dialog::{dialog::Dialog, dialog_layer::DialogLayerInnerRef, DialogId},
9 transaction::{
10 key::{TransactionKey, TransactionRole},
11 make_tag,
12 transaction::Transaction,
13 },
14 transport::SipAddr,
15 Result,
16};
17use rsip::{Request, Response};
18use std::sync::Arc;
19use tracing::{debug, info, warn};
20
21/// INVITE Request Options
22///
23/// `InviteOption` contains all the parameters needed to create and send
24/// an INVITE request to establish a SIP session. This structure provides
25/// a convenient way to specify all the necessary information for initiating
26/// a call or session.
27///
28/// # Fields
29///
30/// * `caller` - URI of the calling party (From header)
31/// * `callee` - URI of the called party (To header and Request-URI)
32/// * `content_type` - MIME type of the message body (default: "application/sdp")
33/// * `offer` - Optional message body (typically SDP offer)
34/// * `contact` - Contact URI for this user agent
35/// * `credential` - Optional authentication credentials
36/// * `headers` - Optional additional headers to include
37///
38/// # Examples
39///
40/// ## Basic Voice Call
41///
42/// ```rust,no_run
43/// # use rsipstack::dialog::invitation::InviteOption;
44/// # fn example() -> rsipstack::Result<()> {
45/// # let sdp_offer_bytes = vec![];
46/// let invite_option = InviteOption {
47/// caller: "sip:alice@example.com".try_into()?,
48/// callee: "sip:bob@example.com".try_into()?,
49/// content_type: Some("application/sdp".to_string()),
50/// destination: None,
51/// offer: Some(sdp_offer_bytes),
52/// contact: "sip:alice@192.168.1.100:5060".try_into()?,
53/// credential: None,
54/// headers: None,
55/// };
56/// # Ok(())
57/// # }
58/// ```
59///
60/// ```rust,no_run
61/// # use rsipstack::dialog::dialog_layer::DialogLayer;
62/// # use rsipstack::dialog::invitation::InviteOption;
63/// # fn example() -> rsipstack::Result<()> {
64/// # let dialog_layer: DialogLayer = todo!();
65/// # let invite_option: InviteOption = todo!();
66/// let request = dialog_layer.make_invite_request(&invite_option)?;
67/// println!("Created INVITE to: {}", request.uri);
68/// # Ok(())
69/// # }
70/// ```
71///
72/// ## Call with Custom Headers
73///
74/// ```rust,no_run
75/// # use rsipstack::dialog::invitation::InviteOption;
76/// # fn example() -> rsipstack::Result<()> {
77/// # let sdp_bytes = vec![];
78/// # let auth_credential = todo!();
79/// let custom_headers = vec![
80/// rsip::Header::UserAgent("MyApp/1.0".into()),
81/// rsip::Header::Subject("Important Call".into()),
82/// ];
83///
84/// let invite_option = InviteOption {
85/// caller: "sip:alice@example.com".try_into()?,
86/// callee: "sip:bob@example.com".try_into()?,
87/// content_type: Some("application/sdp".to_string()),
88/// destination: None,
89/// offer: Some(sdp_bytes),
90/// contact: "sip:alice@192.168.1.100:5060".try_into()?,
91/// credential: Some(auth_credential),
92/// headers: Some(custom_headers),
93/// };
94/// # Ok(())
95/// # }
96/// ```
97///
98/// ## Call with Authentication
99///
100/// ```rust,no_run
101/// # use rsipstack::dialog::invitation::InviteOption;
102/// # use rsipstack::dialog::authenticate::Credential;
103/// # fn example() -> rsipstack::Result<()> {
104/// # let sdp_bytes = vec![];
105/// let credential = Credential {
106/// username: "alice".to_string(),
107/// password: "secret123".to_string(),
108/// realm: Some("example.com".to_string()),
109/// };
110///
111/// let invite_option = InviteOption {
112/// caller: "sip:alice@example.com".try_into()?,
113/// callee: "sip:bob@example.com".try_into()?,
114/// content_type: None, // Will default to "application/sdp"
115/// destination: None,
116/// offer: Some(sdp_bytes),
117/// contact: "sip:alice@192.168.1.100:5060".try_into()?,
118/// credential: Some(credential),
119/// headers: None,
120/// };
121/// # Ok(())
122/// # }
123/// ```
124#[derive(Default, Clone)]
125pub struct InviteOption {
126 pub caller: rsip::Uri,
127 pub callee: rsip::Uri,
128 pub destination: Option<SipAddr>,
129 pub content_type: Option<String>,
130 pub offer: Option<Vec<u8>>,
131 pub contact: rsip::Uri,
132 pub credential: Option<Credential>,
133 pub headers: Option<Vec<rsip::Header>>,
134}
135
136pub struct DialogGuard {
137 pub dialog_layer_inner: DialogLayerInnerRef,
138 pub id: DialogId,
139}
140
141impl DialogGuard {
142 pub fn new(dialog_layer: &Arc<DialogLayer>, id: DialogId) -> Self {
143 Self {
144 dialog_layer_inner: dialog_layer.inner.clone(),
145 id,
146 }
147 }
148}
149
150impl Drop for DialogGuard {
151 fn drop(&mut self) {
152 let dlg = match self.dialog_layer_inner.dialogs.write() {
153 Ok(mut dialogs) => match dialogs.remove(&self.id) {
154 Some(dlg) => dlg,
155 None => return,
156 },
157 _ => return,
158 };
159 let _ = tokio::spawn(async move {
160 if let Err(e) = dlg.hangup().await {
161 info!(id=%dlg.id(), "failed to hangup dialog: {}", e);
162 }
163 });
164 }
165}
166
167pub(super) struct DialogGuardForUnconfirmed<'a> {
168 pub dialog_layer_inner: &'a DialogLayerInnerRef,
169 pub id: &'a DialogId,
170}
171
172impl<'a> Drop for DialogGuardForUnconfirmed<'a> {
173 fn drop(&mut self) {
174 // If the dialog is still unconfirmed, we should try to cancel it
175 match self.dialog_layer_inner.dialogs.write() {
176 Ok(mut dialogs) => {
177 if let Some(dlg) = dialogs.get(self.id) {
178 if !dlg.can_cancel() {
179 return;
180 }
181 match dialogs.remove(self.id) {
182 Some(dlg) => {
183 info!(%self.id, "unconfirmed dialog dropped, cancelling it");
184 let _ = tokio::spawn(async move {
185 if let Err(e) = dlg.hangup().await {
186 info!(id=%dlg.id(), "failed to hangup unconfirmed dialog: {}", e);
187 }
188 });
189 }
190 None => {}
191 }
192 }
193 }
194 Err(e) => {
195 warn!(%self.id, "failed to acquire write lock on dialogs: {}", e);
196 }
197 }
198 }
199}
200
201impl DialogLayer {
202 /// Create an INVITE request from options
203 ///
204 /// Constructs a properly formatted SIP INVITE request based on the
205 /// provided options. This method handles all the required headers
206 /// and parameters according to RFC 3261.
207 ///
208 /// # Parameters
209 ///
210 /// * `opt` - INVITE options containing all necessary parameters
211 ///
212 /// # Returns
213 ///
214 /// * `Ok(Request)` - Properly formatted INVITE request
215 /// * `Err(Error)` - Failed to create request
216 ///
217 /// # Generated Headers
218 ///
219 /// The method automatically generates:
220 /// * Via header with branch parameter
221 /// * From header with tag parameter
222 /// * To header (without tag for initial request)
223 /// * Contact header
224 /// * Content-Type header
225 /// * CSeq header with incremented sequence number
226 /// * Call-ID header
227 ///
228 /// # Examples
229 ///
230 /// ```rust,no_run
231 /// # use rsipstack::dialog::dialog_layer::DialogLayer;
232 /// # use rsipstack::dialog::invitation::InviteOption;
233 /// # fn example() -> rsipstack::Result<()> {
234 /// # let dialog_layer: DialogLayer = todo!();
235 /// # let invite_option: InviteOption = todo!();
236 /// let request = dialog_layer.make_invite_request(&invite_option)?;
237 /// println!("Created INVITE to: {}", request.uri);
238 /// # Ok(())
239 /// # }
240 /// ```
241 pub fn make_invite_request(&self, opt: &InviteOption) -> Result<Request> {
242 let last_seq = self.increment_last_seq();
243 let to = rsip::typed::To {
244 display_name: None,
245 uri: opt.callee.clone(),
246 params: vec![],
247 };
248 let recipient = to.uri.clone();
249
250 let form = rsip::typed::From {
251 display_name: None,
252 uri: opt.caller.clone(),
253 params: vec![],
254 }
255 .with_tag(make_tag());
256
257 let via = self.endpoint.get_via(None, None)?;
258 let mut request =
259 self.endpoint
260 .make_request(rsip::Method::Invite, recipient, via, form, to, last_seq);
261
262 let contact = rsip::typed::Contact {
263 display_name: None,
264 uri: opt.contact.clone(),
265 params: vec![],
266 };
267
268 request
269 .headers
270 .unique_push(rsip::Header::Contact(contact.into()));
271
272 request.headers.unique_push(rsip::Header::ContentType(
273 opt.content_type
274 .clone()
275 .unwrap_or("application/sdp".to_string())
276 .into(),
277 ));
278 // can override default headers
279 if let Some(headers) = opt.headers.as_ref() {
280 for header in headers {
281 request.headers.unique_push(header.clone());
282 }
283 }
284 Ok(request)
285 }
286
287 /// Send an INVITE request and create a client dialog
288 ///
289 /// This is the main method for initiating outbound calls. It creates
290 /// an INVITE request, sends it, and manages the resulting dialog.
291 /// The method handles the complete INVITE transaction including
292 /// authentication challenges and response processing.
293 ///
294 /// # Parameters
295 ///
296 /// * `opt` - INVITE options containing all call parameters
297 /// * `state_sender` - Channel for receiving dialog state updates
298 ///
299 /// # Returns
300 ///
301 /// * `Ok((ClientInviteDialog, Option<Response>))` - Created dialog and final response
302 /// * `Err(Error)` - Failed to send INVITE or process responses
303 ///
304 /// # Call Flow
305 ///
306 /// 1. Creates INVITE request from options
307 /// 2. Creates client dialog and transaction
308 /// 3. Sends INVITE request
309 /// 4. Processes responses (1xx, 2xx, 3xx-6xx)
310 /// 5. Handles authentication challenges if needed
311 /// 6. Returns established dialog and final response
312 ///
313 /// # Examples
314 ///
315 /// ## Basic Call Setup
316 ///
317 /// ```rust,no_run
318 /// # use rsipstack::dialog::dialog_layer::DialogLayer;
319 /// # use rsipstack::dialog::invitation::InviteOption;
320 /// # async fn example() -> rsipstack::Result<()> {
321 /// # let dialog_layer: DialogLayer = todo!();
322 /// # let invite_option: InviteOption = todo!();
323 /// # let state_sender = todo!();
324 /// let (dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?;
325 ///
326 /// if let Some(resp) = response {
327 /// match resp.status_code {
328 /// rsip::StatusCode::OK => {
329 /// println!("Call answered!");
330 /// // Process SDP answer in resp.body
331 /// },
332 /// rsip::StatusCode::BusyHere => {
333 /// println!("Called party is busy");
334 /// },
335 /// _ => {
336 /// println!("Call failed: {}", resp.status_code);
337 /// }
338 /// }
339 /// }
340 /// # Ok(())
341 /// # }
342 /// ```
343 ///
344 /// ## Monitoring Dialog State
345 ///
346 /// ```rust,no_run
347 /// # use rsipstack::dialog::dialog_layer::DialogLayer;
348 /// # use rsipstack::dialog::invitation::InviteOption;
349 /// # use rsipstack::dialog::dialog::DialogState;
350 /// # async fn example() -> rsipstack::Result<()> {
351 /// # let dialog_layer: DialogLayer = todo!();
352 /// # let invite_option: InviteOption = todo!();
353 /// let (state_tx, mut state_rx) = tokio::sync::mpsc::unbounded_channel();
354 /// let (dialog, response) = dialog_layer.do_invite(invite_option, state_tx).await?;
355 ///
356 /// // Monitor dialog state changes
357 /// tokio::spawn(async move {
358 /// while let Some(state) = state_rx.recv().await {
359 /// match state {
360 /// DialogState::Early(_, resp) => {
361 /// println!("Ringing: {}", resp.status_code);
362 /// },
363 /// DialogState::Confirmed(_,_) => {
364 /// println!("Call established");
365 /// },
366 /// DialogState::Terminated(_, code) => {
367 /// println!("Call ended: {:?}", code);
368 /// break;
369 /// },
370 /// _ => {}
371 /// }
372 /// }
373 /// });
374 /// # Ok(())
375 /// # }
376 /// ```
377 ///
378 /// # Error Handling
379 ///
380 /// The method can fail for various reasons:
381 /// * Network connectivity issues
382 /// * Authentication failures
383 /// * Invalid SIP URIs or headers
384 /// * Transaction timeouts
385 /// * Protocol violations
386 ///
387 /// # Authentication
388 ///
389 /// If credentials are provided in the options, the method will
390 /// automatically handle 401/407 authentication challenges by
391 /// resending the request with proper authentication headers.
392 pub async fn do_invite(
393 &self,
394 opt: InviteOption,
395 state_sender: DialogStateSender,
396 ) -> Result<(ClientInviteDialog, Option<Response>)> {
397 let (dialog, tx) = self.create_client_invite_dialog(opt, state_sender)?;
398 let id = dialog.id();
399 self.inner
400 .dialogs
401 .write()
402 .unwrap()
403 .insert(id.clone(), Dialog::ClientInvite(dialog.clone()));
404 info!(%id, "client invite dialog created");
405
406 let _guard = DialogGuardForUnconfirmed {
407 dialog_layer_inner: &self.inner,
408 id: &id,
409 };
410
411 match dialog.process_invite(tx).await {
412 Ok((new_dialog_id, resp)) => {
413 debug!(
414 "client invite dialog confirmed: {} => {}",
415 id, new_dialog_id
416 );
417 self.inner.dialogs.write().unwrap().remove(&id);
418 // update with new dialog id
419 self.inner
420 .dialogs
421 .write()
422 .unwrap()
423 .insert(new_dialog_id, Dialog::ClientInvite(dialog.clone()));
424 return Ok((dialog, resp));
425 }
426 Err(e) => {
427 self.inner.dialogs.write().unwrap().remove(&id);
428 return Err(e);
429 }
430 }
431 }
432
433 pub fn create_client_invite_dialog(
434 &self,
435 opt: InviteOption,
436 state_sender: DialogStateSender,
437 ) -> Result<(ClientInviteDialog, Transaction)> {
438 let mut request = self.make_invite_request(&opt)?;
439 request.body = opt.offer.unwrap_or_default();
440 request.headers.unique_push(rsip::Header::ContentLength(
441 (request.body.len() as u32).into(),
442 ));
443 let key = TransactionKey::from_request(&request, TransactionRole::Client)?;
444 let mut tx = Transaction::new_client(key, request.clone(), self.endpoint.clone(), None);
445 tx.destination = opt.destination;
446
447 let id = DialogId::try_from(&request)?;
448 let mut dlg_inner = DialogInner::new(
449 TransactionRole::Client,
450 id.clone(),
451 request.clone(),
452 self.endpoint.clone(),
453 state_sender,
454 opt.credential,
455 Some(opt.contact),
456 tx.tu_sender.clone(),
457 )?;
458 dlg_inner.initial_destination = tx.destination.clone();
459
460 let dialog = ClientInviteDialog {
461 inner: Arc::new(dlg_inner),
462 };
463 Ok((dialog, tx))
464 }
465
466 pub fn confirm_client_dialog(&self, dialog: ClientInviteDialog) {
467 self.inner
468 .dialogs
469 .write()
470 .unwrap()
471 .insert(dialog.id(), Dialog::ClientInvite(dialog));
472 }
473}