Expand description
Dialog Client API
This module provides a high-level client interface for SIP dialog management, abstracting the complexity of the underlying DialogManager for client use cases.
§Overview
The DialogClient is the primary interface for client-side SIP operations, providing a clean, intuitive API that handles the complexities of SIP dialog management while offering powerful features for call control, media negotiation, and session coordination.
§Key Features
- Call Management: Make outgoing calls, handle responses, manage call lifecycle
- Dialog Operations: Create, manage, and terminate SIP dialogs
- Session Integration: Built-in coordination with session-core for media management
- Request/Response Handling: Send arbitrary SIP methods and handle responses
- Statistics & Monitoring: Track call metrics and dialog states
- Dependency Injection: Clean architecture with proper separation of concerns
§Quick Start
§Basic Client Setup
use rvoip_dialog_core::api::{DialogClient, DialogApi, ClientConfig};
use rvoip_transaction_core::TransactionManager;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set up dependencies (transport setup omitted for brevity)
let tx_mgr = Arc::new(TransactionManager::new_sync(transport));
let config = ClientConfig::new("127.0.0.1:0".parse()?)
.with_from_uri("sip:alice@example.com");
// Create and start the client
let client = DialogClient::with_dependencies(tx_mgr, config).await?;
client.start().await?;
// Make a call
let call = client.make_call(
"sip:alice@example.com",
"sip:bob@example.com",
Some("v=0\r\no=- 123 456 IN IP4 192.168.1.100\r\n...".to_string())
).await?;
println!("Call created: {}", call.call_id());
// Clean up
call.hangup().await?;
client.stop().await?;
Ok(())
}
§Architecture & Design Patterns
§Dependency Injection Pattern (Recommended)
The DialogClient follows a clean dependency injection pattern to maintain proper separation of concerns. The client does not manage transport concerns directly:
use rvoip_dialog_core::api::{DialogClient, ClientConfig};
use rvoip_transaction_core::TransactionManager;
use std::sync::Arc;
// 1. Set up transport layer (your responsibility)
// 2. Create transaction manager with transport
let tx_mgr = Arc::new(TransactionManager::new_sync(transport));
// 3. Configure client behavior
let config = ClientConfig::new("192.168.1.100:5060".parse()?)
.with_from_uri("sip:user@domain.com")
.with_auth("username", "password");
// 4. Create client with dependencies
let client = DialogClient::with_dependencies(tx_mgr, config).await?;
§Session Coordination Pattern
For applications that need media management, use the session coordination pattern:
use rvoip_dialog_core::api::{DialogClient, DialogApi};
use rvoip_dialog_core::events::SessionCoordinationEvent;
use tokio::sync::mpsc;
let client = DialogClient::with_dependencies(tx_mgr, config).await?;
// Set up session coordination
let (session_tx, mut session_rx) = mpsc::channel(100);
client.set_session_coordinator(session_tx).await?;
client.start().await?;
// Handle session events
tokio::spawn(async move {
while let Some(event) = session_rx.recv().await {
match event {
SessionCoordinationEvent::IncomingCall { dialog_id, .. } => {
println!("Incoming call: {}", dialog_id);
// Coordinate with media layer
},
SessionCoordinationEvent::CallAnswered { dialog_id, session_answer } => {
println!("Call answered: {}", dialog_id);
// Set up media based on SDP answer
},
_ => {}
}
}
});
§Usage Patterns
§Pattern 1: Simple Call Management
For basic voice calls with minimal complexity:
use rvoip_dialog_core::api::DialogClient;
// Make a call
let call = client.make_call(
"sip:caller@example.com",
"sip:callee@example.com",
Some("SDP offer".to_string())
).await?;
// Basic call operations
call.hold(Some("SDP with hold".to_string())).await?;
call.resume(Some("SDP with active media".to_string())).await?;
call.transfer("sip:transfer@example.com".to_string()).await?;
call.hangup().await?;
§Pattern 2: Advanced Dialog Management
For applications requiring fine-grained control over SIP dialogs:
use rvoip_dialog_core::api::DialogClient;
use rvoip_sip_core::Method;
// Create dialog without initial request
let dialog = client.create_dialog("sip:me@here.com", "sip:you@there.com").await?;
// Send custom SIP methods
client.send_notify(&dialog.id(), "presence".to_string(), Some("online".to_string())).await?;
client.send_update(&dialog.id(), Some("Updated SDP".to_string())).await?;
client.send_info(&dialog.id(), "Application data".to_string()).await?;
// Monitor dialog state
let state = client.get_dialog_state(&dialog.id()).await?;
println!("Dialog state: {:?}", state);
// Clean up
client.terminate_dialog(&dialog.id()).await?;
§Pattern 3: Response Handling & Server-like Behavior
For applications that need to handle incoming requests and send responses:
use rvoip_dialog_core::api::DialogClient;
use rvoip_sip_core::StatusCode;
use rvoip_transaction_core::TransactionKey;
// Build and send responses
let response = client.build_response(&transaction_id, StatusCode::Ok, Some("Response body".to_string())).await?;
client.send_response(&transaction_id, response).await?;
// Or use convenience method
client.send_status_response(&transaction_id, StatusCode::Accepted, None).await?;
// Dialog-aware responses
let dialog_response = client.build_dialog_response(
&transaction_id,
&dialog_id,
StatusCode::Ok,
Some("Dialog response".to_string())
).await?;
client.send_response(&transaction_id, dialog_response).await?;
§Error Handling
The DialogClient provides comprehensive error handling with specific error types:
use rvoip_dialog_core::api::{DialogClient, ApiError};
match client.make_call("invalid-uri", "another-invalid", None).await {
Ok(call) => {
println!("Call successful: {}", call.call_id());
},
Err(ApiError::Configuration { message }) => {
eprintln!("Configuration issue: {}", message);
// Fix configuration and retry
},
Err(ApiError::Network { message }) => {
eprintln!("Network problem: {}", message);
// Check connectivity, retry with backoff
},
Err(ApiError::Protocol { message }) => {
eprintln!("SIP protocol error: {}", message);
// Log for debugging, potentially report upstream
},
Err(ApiError::Dialog { message }) => {
eprintln!("Dialog state error: {}", message);
// Handle dialog lifecycle issues
},
Err(ApiError::Internal { message }) => {
eprintln!("Internal error: {}", message);
// Log for debugging, potentially restart client
},
}
§Configuration Examples
§Production Client Configuration
use rvoip_dialog_core::api::ClientConfig;
use std::time::Duration;
let config = ClientConfig::new("192.168.1.100:5060".parse().unwrap())
.with_from_uri("sip:service@company.com")
.with_auth("service_user", "secure_password");
// Customize for production load
let mut prod_config = config;
prod_config.dialog = prod_config.dialog
.with_timeout(Duration::from_secs(300))
.with_max_dialogs(10000)
.with_user_agent("MyApp/2.0 (Production)");
§Development Configuration
use rvoip_dialog_core::api::ClientConfig;
use std::time::Duration;
let dev_config = ClientConfig::new("127.0.0.1:0".parse().unwrap())
.with_from_uri("sip:dev@localhost");
// Fast timeouts for testing
let mut test_config = dev_config;
test_config.dialog = test_config.dialog
.with_timeout(Duration::from_secs(30))
.with_user_agent("MyApp-Dev/1.0");
§Integration with Other Components
§Media Layer Integration
use rvoip_dialog_core::api::{DialogClient, DialogApi};
use rvoip_dialog_core::events::SessionCoordinationEvent;
// Set up session coordination for media management
let (session_tx, mut session_rx) = tokio::sync::mpsc::channel(100);
client.set_session_coordinator(session_tx).await?;
// Handle media-related events
tokio::spawn(async move {
while let Some(event) = session_rx.recv().await {
match event {
SessionCoordinationEvent::IncomingCall { dialog_id, request, .. } => {
// Extract SDP from request, set up media
println!("Setting up media for call: {}", dialog_id);
},
SessionCoordinationEvent::CallAnswered { dialog_id, session_answer } => {
// Use SDP answer to configure media streams
println!("Configuring media with answer: {}", session_answer);
},
_ => {}
}
}
});
§Monitoring & Statistics
use rvoip_dialog_core::api::{DialogClient, DialogApi};
// Get client statistics
let stats = client.get_stats().await;
println!("=== Client Statistics ===");
println!("Active dialogs: {}", stats.active_dialogs);
println!("Total dialogs: {}", stats.total_dialogs);
println!("Success rate: {:.1}%",
100.0 * stats.successful_calls as f64 / (stats.successful_calls + stats.failed_calls) as f64);
println!("Average call duration: {:.1}s", stats.avg_call_duration);
// List active dialogs
let active = client.active_dialogs().await;
for dialog in active {
let info = dialog.info().await?;
println!("Dialog {}: {} -> {} ({})",
dialog.id(), info.local_uri, info.remote_uri, info.state);
}
§Best Practices
- Always use dependency injection: Use
with_dependencies()
orwith_global_events()
in production - Handle session coordination: Set up proper event handling for media integration
- Monitor client health: Regularly check statistics and active dialog counts
- Implement proper error handling: Match on specific error types for appropriate responses
- Clean shutdown: Always call
stop()
before terminating the application - Use configuration validation: Call
validate()
on configurations before use - Leverage handles: Use DialogHandle and CallHandle for dialog-specific operations
§Thread Safety
DialogClient is designed to be thread-safe and can be safely shared across async tasks:
use rvoip_dialog_core::api::DialogClient;
use std::sync::Arc;
let client = Arc::new(client);
// Spawn multiple tasks using the same client
let client1 = client.clone();
let task1 = tokio::spawn(async move {
client1.make_call("sip:a@test.com", "sip:b@test.com", None).await
});
let client2 = client.clone();
let task2 = tokio::spawn(async move {
client2.make_call("sip:c@test.com", "sip:d@test.com", None).await
});
// Both tasks can safely use the client concurrently
let (call1, call2) = tokio::try_join!(task1, task2)?;
Structs§
- Dialog
Client - High-level client interface for SIP dialog management