rvoip_dialog_core/api/common.rs
1//! Common API Types
2//!
3//! This module provides shared types and handles used across the dialog-core API,
4//! offering convenient access to dialog operations and events.
5//!
6//! ## Overview
7//!
8//! The common types provide high-level abstractions over SIP dialogs and calls:
9//!
10//! - **DialogHandle**: General-purpose dialog operations and state management
11//! - **CallHandle**: Call-specific operations built on top of DialogHandle
12//! - **CallInfo**: Information about active calls
13//! - **DialogEvent**: Events that can be monitored by applications
14//!
15//! ## Quick Start
16//!
17//! ### Using DialogHandle
18//!
19//! ```rust,no_run
20//! use rvoip_dialog_core::api::common::DialogHandle;
21//! use rvoip_sip_core::Method;
22//!
23//! # async fn example(dialog: DialogHandle) -> Result<(), Box<dyn std::error::Error>> {
24//! // Send a request within the dialog
25//! let tx_key = dialog.send_request(Method::Info, Some("Hello".to_string())).await?;
26//! println!("Sent INFO request: {}", tx_key);
27//!
28//! // Check dialog state
29//! let state = dialog.state().await?;
30//! println!("Dialog state: {:?}", state);
31//!
32//! // Send BYE to terminate
33//! dialog.send_bye().await?;
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! ### Using CallHandle
39//!
40//! ```rust,no_run
41//! use rvoip_dialog_core::api::common::CallHandle;
42//!
43//! # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
44//! // Get call information
45//! let info = call.info().await?;
46//! println!("Call from {} to {}", info.local_uri, info.remote_uri);
47//!
48//! // Put call on hold
49//! call.hold(Some("SDP with hold attributes".to_string())).await?;
50//!
51//! // Transfer the call
52//! call.transfer("sip:voicemail@example.com".to_string()).await?;
53//!
54//! // Hang up
55//! call.hangup().await?;
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! ## Handle Architecture
61//!
62//! ```text
63//! CallHandle
64//! │
65//! └── DialogHandle
66//! │
67//! └── DialogManager
68//! │
69//! └── Dialog (actual state)
70//! ```
71//!
72//! CallHandle provides call-specific operations while DialogHandle provides
73//! general dialog operations. Both are lightweight wrappers that delegate
74//! to the underlying DialogManager.
75//!
76//! ## Event Monitoring
77//!
78//! Applications can monitor dialog events for state changes:
79//!
80//! ```rust,no_run
81//! use rvoip_dialog_core::api::common::DialogEvent;
82//!
83//! fn handle_dialog_event(event: DialogEvent) {
84//! match event {
85//! DialogEvent::Created { dialog_id } => {
86//! println!("New dialog: {}", dialog_id);
87//! },
88//! DialogEvent::StateChanged { dialog_id, old_state, new_state } => {
89//! println!("Dialog {} changed from {:?} to {:?}", dialog_id, old_state, new_state);
90//! },
91//! DialogEvent::Terminated { dialog_id, reason } => {
92//! println!("Dialog {} terminated: {}", dialog_id, reason);
93//! },
94//! _ => {}
95//! }
96//! }
97//! ```
98
99use std::sync::Arc;
100use tracing::{debug, info, warn};
101
102use rvoip_sip_core::{Method, StatusCode, Response};
103use rvoip_transaction_core::TransactionKey;
104use crate::manager::DialogManager;
105use crate::dialog::{DialogId, Dialog, DialogState};
106use super::{ApiResult, ApiError};
107
108/// A handle to a SIP dialog for convenient operations
109///
110/// Provides a high-level interface to dialog operations without exposing
111/// the underlying DialogManager complexity. DialogHandle is the primary
112/// way applications interact with SIP dialogs, offering both basic and
113/// advanced dialog management capabilities.
114///
115/// ## Key Features
116///
117/// - **State Management**: Query and monitor dialog state changes
118/// - **Request Sending**: Send arbitrary SIP methods within the dialog
119/// - **Response Handling**: Send responses to incoming requests
120/// - **Lifecycle Control**: Terminate dialogs gracefully or immediately
121/// - **SIP Methods**: Convenient methods for common SIP operations
122///
123/// ## Examples
124///
125/// ### Basic Dialog Operations
126///
127/// ```rust,no_run
128/// use rvoip_dialog_core::api::common::DialogHandle;
129/// use rvoip_sip_core::Method;
130///
131/// # async fn example(dialog: DialogHandle) -> Result<(), Box<dyn std::error::Error>> {
132/// // Check if dialog is still active
133/// if dialog.is_active().await {
134/// println!("Dialog {} is active", dialog.id());
135///
136/// // Get current state
137/// let state = dialog.state().await?;
138/// println!("State: {:?}", state);
139///
140/// // Send a custom request
141/// let tx_key = dialog.send_request(Method::Update, Some("new parameters".to_string())).await?;
142/// println!("Sent UPDATE: {}", tx_key);
143/// }
144/// # Ok(())
145/// # }
146/// ```
147///
148/// ### Advanced Dialog Usage
149///
150/// ```rust,no_run
151/// use rvoip_dialog_core::api::common::DialogHandle;
152/// use rvoip_sip_core::Method;
153///
154/// # async fn example(dialog: DialogHandle) -> Result<(), Box<dyn std::error::Error>> {
155/// // Get full dialog information
156/// let info = dialog.info().await?;
157/// println!("Dialog: {} -> {}", info.local_uri, info.remote_uri);
158/// println!("Call-ID: {}, State: {:?}", info.call_id, info.state);
159///
160/// // Send application-specific information
161/// dialog.send_info("Custom application data".to_string()).await?;
162///
163/// // Send a notification about an event
164/// dialog.send_notify("presence".to_string(), Some("online".to_string())).await?;
165///
166/// // Gracefully terminate the dialog
167/// dialog.terminate().await?;
168/// # Ok(())
169/// # }
170/// ```
171///
172/// ### Dialog State Monitoring
173///
174/// ```rust,no_run
175/// use rvoip_dialog_core::api::common::DialogHandle;
176/// use rvoip_dialog_core::dialog::DialogState;
177///
178/// # async fn example(dialog: DialogHandle) -> Result<(), Box<dyn std::error::Error>> {
179/// // Monitor dialog state changes
180/// loop {
181/// let state = dialog.state().await?;
182/// match state {
183/// DialogState::Initial => println!("Dialog starting..."),
184/// DialogState::Early => println!("Dialog in early state"),
185/// DialogState::Confirmed => {
186/// println!("Dialog confirmed - ready for operations");
187/// break;
188/// },
189/// DialogState::Terminated => {
190/// println!("Dialog terminated");
191/// break;
192/// },
193/// DialogState::Recovering => {
194/// println!("Dialog recovering...");
195/// },
196/// }
197/// tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
198/// }
199/// # Ok(())
200/// # }
201/// ```
202#[derive(Debug, Clone)]
203pub struct DialogHandle {
204 dialog_id: DialogId,
205 dialog_manager: Arc<DialogManager>,
206}
207
208impl DialogHandle {
209 /// Create a new dialog handle
210 pub(crate) fn new(dialog_id: DialogId, dialog_manager: Arc<DialogManager>) -> Self {
211 Self {
212 dialog_id,
213 dialog_manager,
214 }
215 }
216
217 /// Get the dialog ID
218 pub fn id(&self) -> &DialogId {
219 &self.dialog_id
220 }
221
222 /// Get the current dialog information
223 pub async fn info(&self) -> ApiResult<Dialog> {
224 self.dialog_manager.get_dialog(&self.dialog_id)
225 .map_err(ApiError::from)
226 }
227
228 /// Get the current dialog state
229 pub async fn state(&self) -> ApiResult<DialogState> {
230 self.dialog_manager.get_dialog_state(&self.dialog_id)
231 .map_err(ApiError::from)
232 }
233
234 /// Send a request within this dialog
235 ///
236 /// # Arguments
237 /// * `method` - SIP method to send
238 /// * `body` - Optional message body
239 ///
240 /// # Returns
241 /// Transaction key for tracking the request
242 pub async fn send_request(&self, method: Method, body: Option<String>) -> ApiResult<String> {
243 debug!("Sending {} request in dialog {}", method, self.dialog_id);
244
245 let body_bytes = body.map(|s| bytes::Bytes::from(s));
246 let transaction_key = self.dialog_manager.send_request(&self.dialog_id, method, body_bytes).await
247 .map_err(ApiError::from)?;
248
249 Ok(transaction_key.to_string())
250 }
251
252 /// **NEW**: Send a request within this dialog (returns TransactionKey)
253 ///
254 /// Enhanced version that returns the actual TransactionKey for advanced usage.
255 ///
256 /// # Arguments
257 /// * `method` - SIP method to send
258 /// * `body` - Optional message body
259 ///
260 /// # Returns
261 /// Transaction key for tracking the request
262 pub async fn send_request_with_key(&self, method: Method, body: Option<bytes::Bytes>) -> ApiResult<TransactionKey> {
263 debug!("Sending {} request in dialog {}", method, self.dialog_id);
264
265 self.dialog_manager.send_request(&self.dialog_id, method, body).await
266 .map_err(ApiError::from)
267 }
268
269 /// **NEW**: Send a SIP response for a transaction
270 ///
271 /// Allows sending responses directly through the dialog handle.
272 ///
273 /// # Arguments
274 /// * `transaction_id` - Transaction to respond to
275 /// * `response` - Complete SIP response
276 ///
277 /// # Returns
278 /// Success or error
279 pub async fn send_response(&self, transaction_id: &TransactionKey, response: Response) -> ApiResult<()> {
280 debug!("Sending response for transaction {} in dialog {}", transaction_id, self.dialog_id);
281
282 self.dialog_manager.send_response(transaction_id, response).await
283 .map_err(ApiError::from)
284 }
285
286 /// **NEW**: Send specific SIP methods with convenience
287
288 /// Send a BYE request to terminate the dialog
289 pub async fn send_bye(&self) -> ApiResult<TransactionKey> {
290 info!("Sending BYE for dialog {}", self.dialog_id);
291 self.send_request_with_key(Method::Bye, None).await
292 }
293
294 /// Send a REFER request for call transfer
295 pub async fn send_refer(&self, target_uri: String, refer_body: Option<String>) -> ApiResult<TransactionKey> {
296 info!("Sending REFER for dialog {} to {}", self.dialog_id, target_uri);
297
298 let body = if let Some(custom_body) = refer_body {
299 custom_body
300 } else {
301 format!("Refer-To: {}\r\n", target_uri)
302 };
303
304 self.send_request_with_key(Method::Refer, Some(bytes::Bytes::from(body))).await
305 }
306
307 /// Send a NOTIFY request for event notifications
308 pub async fn send_notify(&self, event: String, body: Option<String>) -> ApiResult<TransactionKey> {
309 info!("Sending NOTIFY for dialog {} event {}", self.dialog_id, event);
310
311 let notify_body = body.map(|b| bytes::Bytes::from(b));
312 self.send_request_with_key(Method::Notify, notify_body).await
313 }
314
315 /// Send an UPDATE request for media modifications
316 pub async fn send_update(&self, sdp: Option<String>) -> ApiResult<TransactionKey> {
317 info!("Sending UPDATE for dialog {}", self.dialog_id);
318
319 let update_body = sdp.map(|s| bytes::Bytes::from(s));
320 self.send_request_with_key(Method::Update, update_body).await
321 }
322
323 /// Send an INFO request for application-specific information
324 pub async fn send_info(&self, info_body: String) -> ApiResult<TransactionKey> {
325 info!("Sending INFO for dialog {}", self.dialog_id);
326
327 self.send_request_with_key(Method::Info, Some(bytes::Bytes::from(info_body))).await
328 }
329
330 /// Send BYE to terminate the dialog
331 pub async fn terminate(&self) -> ApiResult<()> {
332 info!("Terminating dialog {}", self.dialog_id);
333
334 // Send BYE request
335 self.send_request(Method::Bye, None).await?;
336
337 // Terminate dialog
338 self.dialog_manager.terminate_dialog(&self.dialog_id).await
339 .map_err(ApiError::from)?;
340
341 Ok(())
342 }
343
344 /// **NEW**: Terminate dialog directly without sending BYE
345 ///
346 /// For cases where you want to clean up the dialog state without
347 /// sending a BYE request (e.g., after receiving a BYE).
348 pub async fn terminate_immediately(&self) -> ApiResult<()> {
349 info!("Terminating dialog {} immediately", self.dialog_id);
350
351 self.dialog_manager.terminate_dialog(&self.dialog_id).await
352 .map_err(ApiError::from)
353 }
354
355 /// Check if the dialog is still active
356 pub async fn is_active(&self) -> bool {
357 self.dialog_manager.has_dialog(&self.dialog_id)
358 }
359}
360
361/// A handle to a SIP call (specific type of dialog) for call-related operations
362///
363/// Provides call-specific convenience methods on top of the basic dialog operations.
364/// CallHandle extends DialogHandle with operations that are specifically relevant
365/// to voice/video calls, such as hold/resume, transfer, and media management.
366///
367/// ## Key Features
368///
369/// - **Call Lifecycle**: Answer, reject, and hang up calls
370/// - **Call Control**: Hold, resume, transfer, and mute operations
371/// - **Media Management**: Update media parameters and handle media events
372/// - **Call Information**: Access to call-specific metadata and state
373/// - **Dialog Access**: Full access to underlying dialog operations
374///
375/// ## Examples
376///
377/// ### Basic Call Operations
378///
379/// ```rust,no_run
380/// use rvoip_dialog_core::api::common::CallHandle;
381/// use rvoip_sip_core::StatusCode;
382///
383/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
384/// // Get call information
385/// let info = call.info().await?;
386/// println!("Call {} from {} to {}", info.call_id, info.local_uri, info.remote_uri);
387/// println!("State: {:?}", info.state);
388///
389/// // Answer an incoming call
390/// call.answer(Some("SDP answer with media info".to_string())).await?;
391///
392/// // Or reject a call
393/// // call.reject(StatusCode::Busy, Some("Busy right now".to_string())).await?;
394/// # Ok(())
395/// # }
396/// ```
397///
398/// ### Call Control Operations
399///
400/// ```rust,no_run
401/// use rvoip_dialog_core::api::common::CallHandle;
402///
403/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
404/// // Put call on hold
405/// let hold_sdp = "v=0\r\no=- 123 456 IN IP4 0.0.0.0\r\n...";
406/// call.hold(Some(hold_sdp.to_string())).await?;
407/// println!("Call on hold");
408///
409/// // Resume from hold
410/// let resume_sdp = "v=0\r\no=- 123 457 IN IP4 192.168.1.100\r\n...";
411/// call.resume(Some(resume_sdp.to_string())).await?;
412/// println!("Call resumed");
413///
414/// // Transfer to another party
415/// call.transfer("sip:alice@example.com".to_string()).await?;
416/// println!("Call transferred");
417/// # Ok(())
418/// # }
419/// ```
420///
421/// ### Advanced Call Management
422///
423/// ```rust,no_run
424/// use rvoip_dialog_core::api::common::CallHandle;
425///
426/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
427/// // Update media parameters
428/// let new_sdp = "v=0\r\no=- 123 458 IN IP4 192.168.1.100\r\n...";
429/// call.update_media(Some(new_sdp.to_string())).await?;
430///
431/// // Send call-related information
432/// call.send_info("DTMF: 1".to_string()).await?;
433///
434/// // Send call event notification
435/// call.notify("call-status".to_string(), Some("ringing".to_string())).await?;
436///
437/// // Advanced transfer with custom REFER
438/// let refer_body = "Refer-To: sip:bob@example.com\r\nReplaces: abc123";
439/// call.transfer_with_body("sip:bob@example.com".to_string(), refer_body.to_string()).await?;
440/// # Ok(())
441/// # }
442/// ```
443///
444/// ### Call State Monitoring
445///
446/// ```rust,no_run
447/// use rvoip_dialog_core::api::common::CallHandle;
448/// use rvoip_dialog_core::dialog::DialogState;
449///
450/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
451/// // Monitor call state
452/// while call.is_active().await {
453/// let state = call.dialog_state().await?;
454/// match state {
455/// DialogState::Early => println!("Call ringing..."),
456/// DialogState::Confirmed => {
457/// println!("Call answered and active");
458/// break;
459/// },
460/// DialogState::Terminated => {
461/// println!("Call ended");
462/// break;
463/// },
464/// _ => {}
465/// }
466/// tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
467/// }
468/// # Ok(())
469/// # }
470/// ```
471///
472/// ### Integration with Dialog Operations
473///
474/// ```rust,no_run
475/// use rvoip_dialog_core::api::common::CallHandle;
476/// use rvoip_sip_core::Method;
477///
478/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
479/// // Access underlying dialog handle
480/// let dialog = call.dialog();
481///
482/// // Use dialog operations directly
483/// let tx_key = dialog.send_request(Method::Options, None).await?;
484/// println!("Sent OPTIONS via dialog: {}", tx_key);
485///
486/// // Or use call-specific shortcuts
487/// let tx_key = call.send_request(Method::Info, Some("Call info".to_string())).await?;
488/// println!("Sent INFO via call: {}", tx_key);
489/// # Ok(())
490/// # }
491/// ```
492#[derive(Debug, Clone)]
493pub struct CallHandle {
494 dialog_handle: DialogHandle,
495}
496
497impl CallHandle {
498 /// Create a new call handle
499 pub(crate) fn new(dialog_id: DialogId, dialog_manager: Arc<DialogManager>) -> Self {
500 Self {
501 dialog_handle: DialogHandle::new(dialog_id, dialog_manager),
502 }
503 }
504
505 /// Get the underlying dialog handle
506 pub fn dialog(&self) -> &DialogHandle {
507 &self.dialog_handle
508 }
509
510 /// Get the call ID (same as dialog ID)
511 pub fn call_id(&self) -> &DialogId {
512 self.dialog_handle.id()
513 }
514
515 /// Get call information
516 pub async fn info(&self) -> ApiResult<CallInfo> {
517 let dialog = self.dialog_handle.info().await?;
518 Ok(CallInfo {
519 call_id: dialog.id.clone(),
520 state: dialog.state,
521 local_uri: dialog.local_uri.to_string(),
522 remote_uri: dialog.remote_uri.to_string(),
523 call_id_header: dialog.call_id,
524 local_tag: dialog.local_tag,
525 remote_tag: dialog.remote_tag,
526 })
527 }
528
529 /// Answer the call (send 200 OK)
530 ///
531 /// # Arguments
532 /// * `sdp_answer` - Optional SDP answer for media negotiation
533 ///
534 /// # Returns
535 /// Success or error
536 pub async fn answer(&self, sdp_answer: Option<String>) -> ApiResult<()> {
537 info!("Answering call {}", self.call_id());
538
539 // Find the transaction associated with this dialog
540 // We need to look through the transaction-to-dialog mappings to find the INVITE transaction
541 let transaction_id = {
542 let dialog_manager = &self.dialog_handle.dialog_manager;
543 let mut found_tx_id = None;
544
545 // Search through transaction mappings to find the INVITE transaction for this dialog
546 for entry in dialog_manager.transaction_to_dialog.iter() {
547 if entry.value() == self.call_id() {
548 // Check if this is an INVITE transaction (server-side)
549 let tx_key = entry.key();
550 if tx_key.to_string().contains("INVITE") && tx_key.to_string().contains("server") {
551 found_tx_id = Some(tx_key.clone());
552 break;
553 }
554 }
555 }
556
557 found_tx_id.ok_or_else(|| ApiError::Internal {
558 message: "No INVITE transaction found for this call".to_string()
559 })?
560 };
561
562 // Get the original INVITE request to build a proper response
563 let original_request = self.dialog_handle.dialog_manager
564 .transaction_manager()
565 .original_request(&transaction_id)
566 .await
567 .map_err(|e| ApiError::Internal {
568 message: format!("Failed to get original INVITE request: {}", e)
569 })?
570 .ok_or_else(|| ApiError::Internal {
571 message: "Original INVITE request not found".to_string()
572 })?;
573
574 // Build 200 OK response with SDP and proper To tag for dialog establishment
575 let response = {
576 use rvoip_transaction_core::utils::response_builders;
577
578 // Use the dialog-aware response builder that adds To tags
579 // Get the actual local address from the dialog handle
580 let local_addr = self.dialog_handle.dialog_manager.local_address();
581 let mut response = response_builders::create_ok_response_with_dialog_info(
582 &original_request,
583 "server", // contact_user
584 &local_addr.ip().to_string(), // contact_host - use actual local IP
585 Some(local_addr.port()) // contact_port - use actual local port
586 );
587
588 // Add SDP body if provided
589 if let Some(sdp) = &sdp_answer {
590 response = response.with_body(sdp.as_bytes().to_vec());
591 // Add Content-Type header for SDP
592 use rvoip_sip_core::{TypedHeader, types::content_type::ContentType};
593 use rvoip_sip_core::parser::headers::content_type::ContentTypeValue;
594 response.headers.push(TypedHeader::ContentType(ContentType::new(
595 ContentTypeValue {
596 m_type: "application".to_string(),
597 m_subtype: "sdp".to_string(),
598 parameters: std::collections::HashMap::new(),
599 }
600 )));
601 }
602
603 response
604 };
605
606 // CRITICAL FIX: Extract the tag from the response BEFORE sending it
607 let response_local_tag = response.to()
608 .and_then(|to_header| to_header.tag())
609 .map(|tag| tag.to_string());
610
611 // Send the 200 OK response
612 self.dialog_handle.dialog_manager.send_response(&transaction_id, response).await
613 .map_err(|e| ApiError::Internal {
614 message: format!("Failed to send 200 OK response: {}", e)
615 })?;
616
617 // Update dialog state to Confirmed and set local tag to match the response
618 {
619 let mut dialog = self.dialog_handle.dialog_manager.get_dialog_mut(self.call_id())
620 .map_err(|e| ApiError::Internal {
621 message: format!("Failed to get dialog for state update: {}", e)
622 })?;
623
624 // CRITICAL FIX: Use the tag from the response we just sent to ensure consistency
625 if dialog.local_tag.is_none() {
626 if let Some(response_tag) = response_local_tag {
627 dialog.local_tag = Some(response_tag.clone());
628 info!("Dialog {} local tag set to match response: {}", self.call_id(), response_tag);
629 } else {
630 // Fallback: generate tag if response doesn't have one (shouldn't happen)
631 let local_tag = dialog.generate_local_tag();
632 dialog.local_tag = Some(local_tag);
633 warn!("Dialog {} fallback tag generation used", self.call_id());
634 }
635 }
636
637 // Transition from Early to Confirmed
638 if dialog.state == crate::dialog::DialogState::Early {
639 dialog.state = crate::dialog::DialogState::Confirmed;
640 info!("Dialog {} transitioned to Confirmed state", self.call_id());
641
642 // CRITICAL FIX: Update dialog lookup now that we have both tags
643 if let Some(tuple) = dialog.dialog_id_tuple() {
644 use crate::manager::utils::DialogUtils;
645 let key = DialogUtils::create_lookup_key(&tuple.0, &tuple.1, &tuple.2);
646 self.dialog_handle.dialog_manager.dialog_lookup.insert(key, dialog.id.clone());
647 info!("Updated dialog lookup for confirmed dialog {}", dialog.id);
648 }
649 }
650 }
651
652 // Emit session coordination event
653 if let Some(sdp) = sdp_answer {
654 let event = crate::events::SessionCoordinationEvent::CallAnswered {
655 dialog_id: self.call_id().clone(),
656 session_answer: sdp,
657 };
658 self.dialog_handle.dialog_manager.emit_session_coordination_event(event).await;
659 }
660
661 info!("Successfully answered call {}", self.call_id());
662 Ok(())
663 }
664
665 /// Reject the call
666 ///
667 /// # Arguments
668 /// * `status_code` - SIP status code for rejection
669 /// * `reason` - Optional reason phrase
670 ///
671 /// # Returns
672 /// Success or error
673 pub async fn reject(&self, status_code: StatusCode, reason: Option<String>) -> ApiResult<()> {
674 info!("Rejecting call {} with status {}", self.call_id(), status_code);
675
676 // TODO: This should send an error response when response API is available
677 debug!("Call {} would be rejected with status {} reason: {:?}",
678 self.call_id(), status_code, reason);
679
680 Ok(())
681 }
682
683 /// Hang up the call (send BYE)
684 pub async fn hangup(&self) -> ApiResult<()> {
685 info!("Hanging up call {}", self.call_id());
686 self.dialog_handle.terminate().await
687 }
688
689 /// Put the call on hold
690 ///
691 /// # Arguments
692 /// * `hold_sdp` - SDP with hold attributes
693 ///
694 /// # Returns
695 /// Success or error
696 pub async fn hold(&self, hold_sdp: Option<String>) -> ApiResult<()> {
697 info!("Putting call {} on hold", self.call_id());
698
699 // Send re-INVITE with hold SDP
700 self.dialog_handle.send_request(Method::Invite, hold_sdp).await?;
701
702 Ok(())
703 }
704
705 /// Resume the call from hold
706 ///
707 /// # Arguments
708 /// * `resume_sdp` - SDP with active media attributes
709 ///
710 /// # Returns
711 /// Success or error
712 pub async fn resume(&self, resume_sdp: Option<String>) -> ApiResult<()> {
713 info!("Resuming call {} from hold", self.call_id());
714
715 // Send re-INVITE with active SDP
716 self.dialog_handle.send_request(Method::Invite, resume_sdp).await?;
717
718 Ok(())
719 }
720
721 /// Transfer the call
722 ///
723 /// # Arguments
724 /// * `transfer_target` - URI to transfer the call to
725 ///
726 /// # Returns
727 /// Success or error
728 pub async fn transfer(&self, transfer_target: String) -> ApiResult<()> {
729 info!("Transferring call {} to {}", self.call_id(), transfer_target);
730
731 // Use the enhanced dialog handle method
732 self.dialog_handle.send_refer(transfer_target, None).await?;
733
734 Ok(())
735 }
736
737 /// **NEW**: Advanced transfer with custom REFER body
738 ///
739 /// Allows sending custom REFER bodies for advanced transfer scenarios.
740 ///
741 /// # Arguments
742 /// * `transfer_target` - URI to transfer the call to
743 /// * `refer_body` - Custom REFER body with additional headers
744 ///
745 /// # Returns
746 /// Transaction key for the REFER request
747 pub async fn transfer_with_body(&self, transfer_target: String, refer_body: String) -> ApiResult<TransactionKey> {
748 info!("Transferring call {} to {} with custom body", self.call_id(), transfer_target);
749
750 self.dialog_handle.send_refer(transfer_target, Some(refer_body)).await
751 }
752
753 /// **NEW**: Send call-related notifications
754 ///
755 /// Send NOTIFY requests for call-related events.
756 ///
757 /// # Arguments
758 /// * `event` - Event type being notified
759 /// * `body` - Optional notification body
760 ///
761 /// # Returns
762 /// Transaction key for the NOTIFY request
763 pub async fn notify(&self, event: String, body: Option<String>) -> ApiResult<TransactionKey> {
764 info!("Sending call notification for {} event {}", self.call_id(), event);
765
766 self.dialog_handle.send_notify(event, body).await
767 }
768
769 /// **NEW**: Update call media parameters
770 ///
771 /// Send UPDATE request to modify media parameters without re-INVITE.
772 ///
773 /// # Arguments
774 /// * `sdp` - Optional SDP body with new media parameters
775 ///
776 /// # Returns
777 /// Transaction key for the UPDATE request
778 pub async fn update_media(&self, sdp: Option<String>) -> ApiResult<TransactionKey> {
779 info!("Updating media for call {}", self.call_id());
780
781 self.dialog_handle.send_update(sdp).await
782 }
783
784 /// **NEW**: Send call information
785 ///
786 /// Send INFO request with call-related information.
787 ///
788 /// # Arguments
789 /// * `info_body` - Information to send
790 ///
791 /// # Returns
792 /// Transaction key for the INFO request
793 pub async fn send_info(&self, info_body: String) -> ApiResult<TransactionKey> {
794 info!("Sending call info for {}", self.call_id());
795
796 self.dialog_handle.send_info(info_body).await
797 }
798
799 /// **NEW**: Direct dialog operations for advanced use cases
800
801 /// Get dialog state
802 pub async fn dialog_state(&self) -> ApiResult<DialogState> {
803 self.dialog_handle.state().await
804 }
805
806 /// Send custom request in dialog
807 pub async fn send_request(&self, method: Method, body: Option<String>) -> ApiResult<TransactionKey> {
808 self.dialog_handle.send_request_with_key(method, body.map(|s| bytes::Bytes::from(s))).await
809 }
810
811 /// Send response for transaction
812 pub async fn send_response(&self, transaction_id: &TransactionKey, response: Response) -> ApiResult<()> {
813 self.dialog_handle.send_response(transaction_id, response).await
814 }
815
816 /// Check if the call is still active
817 pub async fn is_active(&self) -> bool {
818 self.dialog_handle.is_active().await
819 }
820}
821
822/// Information about a call
823///
824/// Provides comprehensive metadata about an active call including identifiers,
825/// state information, and participant details. This is typically obtained from
826/// CallHandle.info() and used for monitoring and debugging purposes.
827///
828/// ## Examples
829///
830/// ### Displaying Call Information
831///
832/// ```rust,no_run
833/// use rvoip_dialog_core::api::common::{CallHandle, CallInfo};
834/// use rvoip_dialog_core::dialog::DialogState;
835///
836/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
837/// let info = call.info().await?;
838///
839/// println!("=== Call Information ===");
840/// println!("Call ID: {}", info.call_id);
841/// println!("SIP Call-ID: {}", info.call_id_header);
842/// println!("From: {} (tag: {:?})", info.local_uri, info.local_tag);
843/// println!("To: {} (tag: {:?})", info.remote_uri, info.remote_tag);
844/// println!("State: {:?}", info.state);
845///
846/// match info.state {
847/// DialogState::Early => println!("Call is ringing"),
848/// DialogState::Confirmed => println!("Call is active"),
849/// DialogState::Terminated => println!("Call has ended"),
850/// _ => println!("Call in transition"),
851/// }
852/// # Ok(())
853/// # }
854/// ```
855///
856/// ### Conditional Operations Based on Call Info
857///
858/// ```rust,no_run
859/// use rvoip_dialog_core::api::common::{CallHandle, CallInfo};
860/// use rvoip_dialog_core::dialog::DialogState;
861///
862/// # async fn example(call: CallHandle) -> Result<(), Box<dyn std::error::Error>> {
863/// let info = call.info().await?;
864///
865/// // Only allow transfer if call is confirmed
866/// if info.state == DialogState::Confirmed {
867/// call.transfer("sip:voicemail@example.com".to_string()).await?;
868/// println!("Call transferred to voicemail");
869/// } else {
870/// println!("Cannot transfer call in state: {:?}", info.state);
871/// }
872///
873/// // Check if this is an outbound call (local tag exists)
874/// if info.local_tag.is_some() {
875/// println!("This is an outbound call");
876/// } else {
877/// println!("This is an inbound call");
878/// }
879/// # Ok(())
880/// # }
881/// ```
882#[derive(Debug, Clone)]
883pub struct CallInfo {
884 /// Call ID (dialog ID)
885 pub call_id: DialogId,
886
887 /// Current call state
888 pub state: DialogState,
889
890 /// Local URI
891 pub local_uri: String,
892
893 /// Remote URI
894 pub remote_uri: String,
895
896 /// SIP Call-ID header value
897 pub call_id_header: String,
898
899 /// Local tag
900 pub local_tag: Option<String>,
901
902 /// Remote tag
903 pub remote_tag: Option<String>,
904}
905
906/// Dialog events that applications can listen for
907///
908/// Represents various events that occur during the dialog lifecycle,
909/// allowing applications to monitor and react to dialog state changes,
910/// incoming requests, and responses.
911///
912/// ## Event Categories
913///
914/// - **Lifecycle Events**: Created, StateChanged, Terminated
915/// - **Message Events**: RequestReceived, ResponseReceived
916///
917/// ## Examples
918///
919/// ### Basic Event Handling
920///
921/// ```rust,no_run
922/// use rvoip_dialog_core::api::common::DialogEvent;
923/// use rvoip_dialog_core::dialog::DialogState;
924/// use rvoip_sip_core::Method;
925///
926/// fn handle_dialog_event(event: DialogEvent) {
927/// match event {
928/// DialogEvent::Created { dialog_id } => {
929/// println!("New dialog created: {}", dialog_id);
930/// },
931/// DialogEvent::StateChanged { dialog_id, old_state, new_state } => {
932/// println!("Dialog {} transitioned from {:?} to {:?}",
933/// dialog_id, old_state, new_state);
934///
935/// if new_state == DialogState::Confirmed {
936/// println!("Dialog is now ready for operations");
937/// }
938/// },
939/// DialogEvent::Terminated { dialog_id, reason } => {
940/// println!("Dialog {} ended: {}", dialog_id, reason);
941/// },
942/// DialogEvent::RequestReceived { dialog_id, method, body } => {
943/// println!("Dialog {} received {} request", dialog_id, method);
944/// if let Some(body) = body {
945/// println!("Request body: {}", body);
946/// }
947/// },
948/// DialogEvent::ResponseReceived { dialog_id, status_code, body } => {
949/// println!("Dialog {} received {} response", dialog_id, status_code);
950/// if let Some(body) = body {
951/// println!("Response body: {}", body);
952/// }
953/// },
954/// }
955/// }
956/// ```
957///
958/// ### Advanced Event Processing
959///
960/// ```rust,no_run
961/// use rvoip_dialog_core::api::common::DialogEvent;
962/// use rvoip_dialog_core::dialog::DialogState;
963/// use rvoip_sip_core::{Method, StatusCode};
964/// use std::collections::HashMap;
965///
966/// struct CallManager {
967/// active_calls: HashMap<String, CallInfo>,
968/// }
969///
970/// impl CallManager {
971/// fn handle_event(&mut self, event: DialogEvent) {
972/// match event {
973/// DialogEvent::Created { dialog_id } => {
974/// println!("Tracking new dialog: {}", dialog_id);
975/// // Initialize call tracking
976/// },
977/// DialogEvent::StateChanged { dialog_id, new_state, .. } => {
978/// match new_state {
979/// DialogState::Confirmed => {
980/// println!("Call {} is now active", dialog_id);
981/// // Start call timer, enable features, etc.
982/// },
983/// DialogState::Terminated => {
984/// println!("Call {} ended", dialog_id);
985/// // Cleanup call resources
986/// },
987/// _ => {}
988/// }
989/// },
990/// DialogEvent::RequestReceived { dialog_id, method, .. } => {
991/// match method {
992/// Method::Invite => println!("Re-INVITE received on {}", dialog_id),
993/// Method::Bye => println!("BYE received on {}", dialog_id),
994/// Method::Info => println!("INFO received on {}", dialog_id),
995/// _ => println!("Other request: {} on {}", method, dialog_id),
996/// }
997/// },
998/// DialogEvent::ResponseReceived { dialog_id, status_code, .. } => {
999/// // Pattern match on specific status codes
1000/// match status_code {
1001/// StatusCode::Ok | StatusCode::Accepted => {
1002/// println!("Success response {} on {}", status_code, dialog_id);
1003/// },
1004/// StatusCode::BadRequest | StatusCode::NotFound | StatusCode::ServerInternalError => {
1005/// println!("Error response {} on {}", status_code, dialog_id);
1006/// },
1007/// _ => {
1008/// println!("Other response {} on {}", status_code, dialog_id);
1009/// }
1010/// }
1011/// },
1012/// _ => {}
1013/// }
1014/// }
1015/// }
1016/// # struct CallInfo;
1017/// ```
1018///
1019/// ### Event Filtering and Routing
1020///
1021/// ```rust,no_run
1022/// use rvoip_dialog_core::api::common::DialogEvent;
1023/// use rvoip_dialog_core::dialog::{DialogId, DialogState};
1024///
1025/// fn route_dialog_event(event: DialogEvent, call_id: &DialogId) {
1026/// // Only process events for the dialog we care about
1027/// let event_dialog_id = match &event {
1028/// DialogEvent::Created { dialog_id } => dialog_id,
1029/// DialogEvent::StateChanged { dialog_id, .. } => dialog_id,
1030/// DialogEvent::Terminated { dialog_id, .. } => dialog_id,
1031/// DialogEvent::RequestReceived { dialog_id, .. } => dialog_id,
1032/// DialogEvent::ResponseReceived { dialog_id, .. } => dialog_id,
1033/// };
1034///
1035/// if event_dialog_id == call_id {
1036/// println!("Processing event for our dialog: {:?}", event);
1037///
1038/// // Handle the specific event
1039/// match event {
1040/// DialogEvent::StateChanged { new_state: DialogState::Confirmed, .. } => {
1041/// println!("Our call is now active!");
1042/// },
1043/// DialogEvent::Terminated { reason, .. } => {
1044/// println!("Our call ended: {}", reason);
1045/// },
1046/// _ => {}
1047/// }
1048/// }
1049/// }
1050/// ```
1051#[derive(Debug, Clone)]
1052pub enum DialogEvent {
1053 /// Dialog was created
1054 Created {
1055 dialog_id: DialogId,
1056 },
1057
1058 /// Dialog state changed
1059 StateChanged {
1060 dialog_id: DialogId,
1061 old_state: DialogState,
1062 new_state: DialogState,
1063 },
1064
1065 /// Dialog was terminated
1066 Terminated {
1067 dialog_id: DialogId,
1068 reason: String,
1069 },
1070
1071 /// Request received in dialog
1072 RequestReceived {
1073 dialog_id: DialogId,
1074 method: Method,
1075 body: Option<String>,
1076 },
1077
1078 /// Response received in dialog
1079 ResponseReceived {
1080 dialog_id: DialogId,
1081 status_code: StatusCode,
1082 body: Option<String>,
1083 },
1084}