ftth_rsipstack/dialog/
client_dialog.rs

1use super::dialog::DialogInnerRef;
2use super::DialogId;
3use crate::dialog::dialog::DialogInner;
4use crate::transaction::transaction::Transaction;
5use crate::Result;
6use crate::{
7    dialog::{
8        authenticate::handle_client_authenticate,
9        dialog::{DialogState, TerminatedReason},
10    },
11    rsip_ext::extract_uri_from_contact,
12};
13use rsip::prelude::HasHeaders;
14use rsip::{
15    headers::Route,
16    prelude::{HeadersExt, ToTypedHeader, UntypedHeader},
17    Header,
18};
19use rsip::{Response, SipMessage, StatusCode};
20use std::sync::atomic::Ordering;
21use tokio_util::sync::CancellationToken;
22use tracing::{info, trace};
23
24/// Client-side INVITE Dialog (UAC)
25///
26/// `ClientInviteDialog` represents a client-side INVITE dialog in SIP. This is used
27/// when the local user agent acts as a User Agent Client (UAC) and initiates
28/// an INVITE transaction to establish a session with a remote party.
29///
30/// # Key Features
31///
32/// * **Session Initiation** - Initiates INVITE transactions to establish calls
33/// * **In-dialog Requests** - Sends UPDATE, INFO, OPTIONS within established dialogs
34/// * **Session Termination** - Handles BYE and CANCEL for ending sessions
35/// * **Re-INVITE Support** - Supports session modification via re-INVITE
36/// * **Authentication** - Handles 401/407 authentication challenges
37/// * **State Management** - Tracks dialog state transitions
38///
39/// # Dialog Lifecycle
40///
41/// 1. **Creation** - Dialog created when sending INVITE
42/// 2. **Early State** - Receives provisional responses (1xx)
43/// 3. **Confirmed** - Receives 2xx response and sends ACK
44/// 4. **Active** - Can send in-dialog requests (UPDATE, INFO, etc.)
45/// 5. **Termination** - Sends BYE or CANCEL to end session
46///
47/// # Examples
48///
49/// ## Basic Call Flow
50///
51/// ```rust,no_run
52/// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
53/// # async fn example() -> rsipstack::Result<()> {
54/// # let dialog: ClientInviteDialog = todo!(); // Dialog is typically created by DialogLayer.do_invite()
55/// # let new_sdp_body = vec![];
56/// # let info_body = vec![];
57/// // After dialog is established:
58///
59/// // Send an UPDATE request
60/// let response = dialog.update(None, Some(new_sdp_body)).await?;
61///
62/// // Send INFO request
63/// let response = dialog.info(None, Some(info_body)).await?;
64///
65/// // End the call
66/// dialog.bye().await?;
67/// # Ok(())
68/// # }
69/// ```
70///
71/// ## Session Modification
72///
73/// ```rust,no_run
74/// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
75/// # async fn example() -> rsipstack::Result<()> {
76/// # let dialog: ClientInviteDialog = todo!();
77/// # let new_sdp = vec![];
78/// // Modify session with re-INVITE
79/// let headers = vec![
80///     rsip::Header::ContentType("application/sdp".into())
81/// ];
82/// let response = dialog.reinvite(Some(headers), Some(new_sdp)).await?;
83///
84/// if let Some(resp) = response {
85///     if resp.status_code == rsip::StatusCode::OK {
86///         println!("Session modified successfully");
87///     }
88/// }
89/// # Ok(())
90/// # }
91/// ```
92///
93/// # Thread Safety
94///
95/// ClientInviteDialog is thread-safe and can be cloned and shared across tasks.
96/// All operations are atomic and properly synchronized.
97#[derive(Clone)]
98pub struct ClientInviteDialog {
99    pub(super) inner: DialogInnerRef,
100}
101
102impl ClientInviteDialog {
103    /// Get the dialog identifier
104    ///
105    /// Returns the unique DialogId that identifies this dialog instance.
106    /// The DialogId consists of Call-ID, from-tag, and to-tag.
107    pub fn id(&self) -> DialogId {
108        self.inner.id.lock().unwrap().clone()
109    }
110
111    pub fn state(&self) -> DialogState {
112        self.inner.state.lock().unwrap().clone()
113    }
114
115    /// Get the cancellation token for this dialog
116    ///
117    /// Returns a reference to the CancellationToken that can be used to
118    /// cancel ongoing operations for this dialog.
119    pub fn cancel_token(&self) -> &CancellationToken {
120        &self.inner.cancel_token
121    }
122    /// Hang up the call
123    ///
124    /// If the dialog is confirmed, send a BYE request to terminate the call.
125    /// If the dialog is not confirmed, send a CANCEL request to cancel the call.
126    pub async fn hangup(&self) -> Result<()> {
127        if self.inner.can_cancel() {
128            self.cancel().await
129        } else {
130            self.bye().await
131        }
132    }
133
134    /// Send a BYE request to terminate the dialog
135    ///
136    /// Sends a BYE request to gracefully terminate an established dialog.
137    /// This should only be called for confirmed dialogs. If the dialog
138    /// is not confirmed, this method returns immediately without error.
139    ///
140    /// # Returns
141    ///
142    /// * `Ok(())` - BYE was sent successfully or dialog not confirmed
143    /// * `Err(Error)` - Failed to send BYE request
144    ///
145    /// # Examples
146    ///
147    /// ```rust,no_run
148    /// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
149    /// # async fn example() -> rsipstack::Result<()> {
150    /// # let dialog: ClientInviteDialog = todo!();
151    /// // End an established call
152    /// dialog.bye().await?;
153    /// # Ok(())
154    /// # }
155    /// ```
156    pub async fn bye(&self) -> Result<()> {
157        if !self.inner.is_confirmed() {
158            return Ok(());
159        }
160        let request = self
161            .inner
162            .make_request(rsip::Method::Bye, None, None, None, None, None)?;
163
164        match self.inner.do_request(request).await {
165            Ok(_) => {}
166            Err(e) => {
167                info!("bye error: {}", e);
168            }
169        };
170        self.inner
171            .transition(DialogState::Terminated(self.id(), TerminatedReason::UacBye))?;
172        Ok(())
173    }
174
175    /// Send a CANCEL request to cancel an ongoing INVITE
176    ///
177    /// Sends a CANCEL request to cancel an INVITE transaction that has not
178    /// yet been answered with a final response. This is used to abort
179    /// call setup before the call is established.
180    ///
181    /// # Returns
182    ///
183    /// * `Ok(())` - CANCEL was sent successfully
184    /// * `Err(Error)` - Failed to send CANCEL request
185    ///
186    /// # Examples
187    ///
188    /// ```rust,no_run
189    /// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
190    /// # async fn example() -> rsipstack::Result<()> {
191    /// # let dialog: ClientInviteDialog = todo!();
192    /// // Cancel an outgoing call before it's answered
193    /// dialog.cancel().await?;
194    /// # Ok(())
195    /// # }
196    /// ```
197    pub async fn cancel(&self) -> Result<()> {
198        if self.inner.is_confirmed() {
199            return Ok(());
200        }
201        info!(id=%self.id(),"sending cancel request");
202        let mut cancel_request = self.inner.initial_request.clone();
203        cancel_request
204            .headers_mut()
205            .retain(|h| !matches!(h, Header::ContentLength(_) | Header::ContentType(_)));
206
207        cancel_request
208            .to_header_mut()?
209            .mut_tag(self.id().to_tag.clone().into())?; // ensure to-tag has tag param
210
211        cancel_request.method = rsip::Method::Cancel;
212        cancel_request
213            .cseq_header_mut()?
214            .mut_seq(self.inner.get_local_seq())?
215            .mut_method(rsip::Method::Cancel)?;
216        cancel_request.body = vec![];
217        self.inner.do_request(cancel_request).await?;
218        Ok(())
219    }
220
221    /// Send a re-INVITE request to modify the session
222    ///
223    /// Sends a re-INVITE request within an established dialog to modify
224    /// the session parameters (e.g., change media, add/remove streams).
225    /// This can only be called for confirmed dialogs.
226    ///
227    /// # Parameters
228    ///
229    /// * `headers` - Optional additional headers to include
230    /// * `body` - Optional message body (typically new SDP)
231    ///
232    /// # Returns
233    ///
234    /// * `Ok(Some(Response))` - Response to the re-INVITE
235    /// * `Ok(None)` - Dialog not confirmed, no request sent
236    /// * `Err(Error)` - Failed to send re-INVITE
237    ///
238    /// # Examples
239    ///
240    /// ```rust,no_run
241    /// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
242    /// # async fn example() -> rsipstack::Result<()> {
243    /// # let dialog: ClientInviteDialog = todo!();
244    /// let new_sdp = b"v=0\r\no=- 123 456 IN IP4 192.168.1.1\r\n...";
245    /// let response = dialog.reinvite(None, Some(new_sdp.to_vec())).await?;
246    /// # Ok(())
247    /// # }
248    /// ```
249    pub async fn reinvite(
250        &self,
251        headers: Option<Vec<rsip::Header>>,
252        body: Option<Vec<u8>>,
253    ) -> Result<Option<rsip::Response>> {
254        if !self.inner.is_confirmed() {
255            return Ok(None);
256        }
257        info!(id=%self.id(),"sending re-invite request, body:\n{:?}", body);
258        let request =
259            self.inner
260                .make_request(rsip::Method::Invite, None, None, None, headers, body)?;
261        let resp = self.inner.do_request(request.clone()).await;
262        match resp {
263            Ok(Some(ref resp)) => {
264                if resp.status_code == StatusCode::OK {
265                    self.inner
266                        .transition(DialogState::Updated(self.id(), request))?;
267                }
268            }
269            _ => {}
270        }
271        resp
272    }
273
274    /// Send an UPDATE request to modify session parameters
275    ///
276    /// Sends an UPDATE request within an established dialog to modify
277    /// session parameters without the complexity of a re-INVITE.
278    /// This is typically used for smaller session modifications.
279    ///
280    /// # Parameters
281    ///
282    /// * `headers` - Optional additional headers to include
283    /// * `body` - Optional message body (typically SDP)
284    ///
285    /// # Returns
286    ///
287    /// * `Ok(Some(Response))` - Response to the UPDATE
288    /// * `Ok(None)` - Dialog not confirmed, no request sent
289    /// * `Err(Error)` - Failed to send UPDATE
290    ///
291    /// # Examples
292    ///
293    /// ```rust,no_run
294    /// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
295    /// # async fn example() -> rsipstack::Result<()> {
296    /// # let dialog: ClientInviteDialog = todo!();
297    /// # let sdp_body = vec![];
298    /// let response = dialog.update(None, Some(sdp_body)).await?;
299    /// # Ok(())
300    /// # }
301    /// ```
302    pub async fn update(
303        &self,
304        headers: Option<Vec<rsip::Header>>,
305        body: Option<Vec<u8>>,
306    ) -> Result<Option<rsip::Response>> {
307        if !self.inner.is_confirmed() {
308            return Ok(None);
309        }
310        info!(id=%self.id(),"sending update request, body:\n{:?}", body);
311        let request =
312            self.inner
313                .make_request(rsip::Method::Update, None, None, None, headers, body)?;
314        self.inner.do_request(request.clone()).await
315    }
316
317    /// Send an INFO request for mid-dialog information
318    ///
319    /// Sends an INFO request within an established dialog to exchange
320    /// application-level information. This is commonly used for DTMF
321    /// tones, but can carry any application-specific data.
322    ///
323    /// # Parameters
324    ///
325    /// * `headers` - Optional additional headers to include
326    /// * `body` - Optional message body (application-specific data)
327    ///
328    /// # Returns
329    ///
330    /// * `Ok(Some(Response))` - Response to the INFO
331    /// * `Ok(None)` - Dialog not confirmed, no request sent
332    /// * `Err(Error)` - Failed to send INFO
333    ///
334    /// # Examples
335    ///
336    /// ```rust,no_run
337    /// # use rsipstack::dialog::client_dialog::ClientInviteDialog;
338    /// # async fn example() -> rsipstack::Result<()> {
339    /// # let dialog: ClientInviteDialog = todo!();
340    /// // Send DTMF tone
341    /// let dtmf_body = b"Signal=1\r\nDuration=100\r\n";
342    /// let headers = vec![
343    ///     rsip::Header::ContentType("application/dtmf-relay".into())
344    /// ];
345    /// let response = dialog.info(Some(headers), Some(dtmf_body.to_vec())).await?;
346    /// # Ok(())
347    /// # }
348    /// ```
349    pub async fn info(
350        &self,
351        headers: Option<Vec<rsip::Header>>,
352        body: Option<Vec<u8>>,
353    ) -> Result<Option<rsip::Response>> {
354        if !self.inner.is_confirmed() {
355            return Ok(None);
356        }
357        info!(id=%self.id(),"sending info request, body:\n{:?}", body);
358        let request =
359            self.inner
360                .make_request(rsip::Method::Info, None, None, None, headers, body)?;
361        self.inner.do_request(request.clone()).await
362    }
363
364    pub async fn options(
365        &self,
366        headers: Option<Vec<rsip::Header>>,
367        body: Option<Vec<u8>>,
368    ) -> Result<Option<rsip::Response>> {
369        if !self.inner.is_confirmed() {
370            return Ok(None);
371        }
372        info!(id=%self.id(),"sending option request, body:\n{:?}", body);
373        let request =
374            self.inner
375                .make_request(rsip::Method::Options, None, None, None, headers, body)?;
376        self.inner.do_request(request.clone()).await
377    }
378    /// Handle incoming transaction for this dialog
379    ///
380    /// Processes incoming SIP requests that are routed to this dialog.
381    /// This method handles sequence number validation and dispatches
382    /// to appropriate handlers based on the request method.
383    ///
384    /// # Parameters
385    ///
386    /// * `tx` - The incoming transaction to handle
387    ///
388    /// # Returns
389    ///
390    /// * `Ok(())` - Request handled successfully
391    /// * `Err(Error)` - Failed to handle request
392    ///
393    /// # Supported Methods
394    ///
395    /// * `BYE` - Terminates the dialog
396    /// * `INFO` - Handles information exchange
397    /// * `OPTIONS` - Handles capability queries
398    /// * `UPDATE` - Handles session updates
399    /// * `INVITE` - Handles re-INVITE (when confirmed)
400    pub async fn handle(&mut self, tx: &mut Transaction) -> Result<()> {
401        trace!(
402            id=%self.id(),
403            "handle request: {:?} state:{}",
404            tx.original,
405            self.inner.state.lock().unwrap()
406        );
407
408        let cseq = tx.original.cseq_header()?.seq()?;
409        let remote_seq = self.inner.remote_seq.load(Ordering::Relaxed);
410        if remote_seq > 0 && cseq < remote_seq {
411            info!(id=%self.id(),"received old request remote_seq: {} > {}", remote_seq, cseq);
412            tx.reply(rsip::StatusCode::ServerInternalError).await?;
413            return Ok(());
414        }
415
416        self.inner
417            .remote_seq
418            .compare_exchange(remote_seq, cseq, Ordering::Relaxed, Ordering::Relaxed)
419            .ok();
420
421        if self.inner.is_confirmed() {
422            match tx.original.method {
423                rsip::Method::Invite => {}
424                rsip::Method::Bye => return self.handle_bye(tx).await,
425                rsip::Method::Info => return self.handle_info(tx).await,
426                rsip::Method::Options => return self.handle_options(tx).await,
427                rsip::Method::Update => return self.handle_update(tx).await,
428                _ => {
429                    info!(id=%self.id(), "invalid request method: {:?}", tx.original.method);
430                    tx.reply(rsip::StatusCode::MethodNotAllowed).await?;
431                    return Err(crate::Error::DialogError(
432                        "invalid request".to_string(),
433                        self.id(),
434                        rsip::StatusCode::MethodNotAllowed,
435                    ));
436                }
437            }
438        } else {
439            info!(id=%self.id(),
440                "received request before confirmed: {:?}",
441                tx.original.method
442            );
443        }
444        Ok(())
445    }
446
447    async fn handle_bye(&mut self, tx: &mut Transaction) -> Result<()> {
448        info!(id=%self.id(), "received bye {}", tx.original.uri);
449        self.inner
450            .transition(DialogState::Terminated(self.id(), TerminatedReason::UasBye))?;
451        tx.reply(rsip::StatusCode::OK).await?;
452        Ok(())
453    }
454
455    async fn handle_info(&mut self, tx: &mut Transaction) -> Result<()> {
456        info!(id=%self.id(),"received info {}", tx.original.uri);
457        self.inner
458            .transition(DialogState::Info(self.id(), tx.original.clone()))?;
459        tx.reply(rsip::StatusCode::OK).await?;
460        Ok(())
461    }
462
463    async fn handle_options(&mut self, tx: &mut Transaction) -> Result<()> {
464        info!(id=%self.id(),"received options {}", tx.original.uri);
465        self.inner
466            .transition(DialogState::Options(self.id(), tx.original.clone()))?;
467        tx.reply(rsip::StatusCode::OK).await?;
468        Ok(())
469    }
470
471    async fn handle_update(&mut self, tx: &mut Transaction) -> Result<()> {
472        info!(id=%self.id(),"received update {}", tx.original.uri);
473        self.inner
474            .transition(DialogState::Updated(self.id(), tx.original.clone()))?;
475        tx.reply(rsip::StatusCode::OK).await?;
476        Ok(())
477    }
478
479    pub async fn process_invite(
480        &self,
481        mut tx: Transaction,
482    ) -> Result<(DialogId, Option<Response>)> {
483        self.inner.transition(DialogState::Calling(self.id()))?;
484        let mut auth_sent = false;
485        tx.send().await?;
486        let mut dialog_id = self.id();
487        let mut final_response = None;
488        while let Some(msg) = tx.receive().await {
489            match msg {
490                SipMessage::Request(_) => {}
491                SipMessage::Response(resp) => {
492                    match resp.status_code {
493                        StatusCode::Trying => {
494                            self.inner.transition(DialogState::Trying(self.id()))?;
495                            continue;
496                        }
497                        StatusCode::Ringing | StatusCode::SessionProgress => {
498                            match resp.to_header()?.tag() {
499                                Ok(Some(tag)) => self.inner.update_remote_tag(tag.value())?,
500                                _ => {}
501                            }
502                            self.inner.transition(DialogState::Early(self.id(), resp))?;
503                            continue;
504                        }
505                        StatusCode::ProxyAuthenticationRequired | StatusCode::Unauthorized => {
506                            if auth_sent {
507                                final_response = Some(resp.clone());
508                                info!(id=%self.id(),"received {} response after auth sent", resp.status_code);
509                                self.inner.transition(DialogState::Terminated(
510                                    self.id(),
511                                    TerminatedReason::ProxyAuthRequired,
512                                ))?;
513                                break;
514                            }
515                            auth_sent = true;
516                            if let Some(credential) = &self.inner.credential {
517                                tx = handle_client_authenticate(
518                                    self.inner.increment_local_seq(),
519                                    tx,
520                                    resp,
521                                    credential,
522                                )
523                                .await?;
524                                tx.send().await?;
525                                self.inner.update_remote_tag("").ok();
526                                continue;
527                            } else {
528                                info!(id=%self.id(),"received 407 response without auth option");
529                                self.inner.transition(DialogState::Terminated(
530                                    self.id(),
531                                    TerminatedReason::ProxyAuthRequired,
532                                ))?;
533                            }
534                            continue;
535                        }
536                        _ => {}
537                    };
538                    final_response = Some(resp.clone());
539                    match resp.to_header()?.tag()? {
540                        Some(tag) => self.inner.update_remote_tag(tag.value())?,
541                        None => {}
542                    }
543
544                    if let Ok(id) = DialogId::try_from(&resp) {
545                        dialog_id = id;
546                    }
547                    match resp.status_code {
548                        StatusCode::OK => {
549                            // 200 response to INVITE always contains Contact header
550                            let contact = resp.contact_header()?;
551                            self.inner
552                                .remote_contact
553                                .lock()
554                                .unwrap()
555                                .replace(contact.clone());
556
557                            // update remote uri
558                            let uri = if let Ok(typed_contact) = contact.typed() {
559                                typed_contact.uri
560                            } else {
561                                let mut uri = extract_uri_from_contact(contact.value())?;
562                                uri.headers.clear();
563                                uri
564                            };
565                            *self.inner.remote_uri.lock().unwrap() = uri;
566
567                            // update route set from Record-Route header
568                            let mut route_set = Vec::new();
569                            for header in resp.headers.iter() {
570                                if let Header::RecordRoute(record_route) = header {
571                                    route_set.push(Route::from(record_route.value()));
572                                }
573                            }
574                            *self.inner.route_set.lock().unwrap() = route_set;
575
576                            self.inner
577                                .transition(DialogState::Confirmed(dialog_id.clone(), resp))?;
578                            DialogInner::serve_keepalive_options(self.inner.clone());
579                        }
580                        _ => {
581                            self.inner.transition(DialogState::Terminated(
582                                self.id(),
583                                TerminatedReason::UasOther(resp.status_code.clone()),
584                            ))?;
585                        }
586                    }
587                    break;
588                }
589            }
590        }
591        Ok((dialog_id, final_response))
592    }
593}