Module client

Source
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

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

  1. Always use dependency injection: Use with_dependencies() or with_global_events() in production
  2. Handle session coordination: Set up proper event handling for media integration
  3. Monitor client health: Regularly check statistics and active dialog counts
  4. Implement proper error handling: Match on specific error types for appropriate responses
  5. Clean shutdown: Always call stop() before terminating the application
  6. Use configuration validation: Call validate() on configurations before use
  7. 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§

DialogClient
High-level client interface for SIP dialog management