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}