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}