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