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}