Crates.io | rvoip-dialog-core |
lib.rs | rvoip-dialog-core |
version | 0.1.26 |
created_at | 2025-07-03 07:49:06.564683+00 |
updated_at | 2025-08-15 17:50:04.805904+00 |
description | RFC 3261 SIP Dialog Management Layer for RVOIP |
homepage | https://github.com/eisenzopf/rvoip |
repository | https://github.com/eisenzopf/rvoip |
max_upload_size | |
id | 1735971 |
size | 871,230 |
RFC 3261 SIP Dialog Management Layer for the rvoip VoIP stack, providing clean separation between session coordination and SIP protocol operations.
rvoip-dialog-core
implements the SIP dialog layer as defined in RFC 3261, serving as the protocol processing engine between session coordination (handled by session-core
) and transaction reliability (handled by transaction-core
). This crate manages SIP dialogs, routes messages within dialog contexts, and coordinates with the session layer through well-defined events.
SIP Protocol Processing
Dialog State Management
SIP Header Management
Session Coordination
session-core
Recovery & Reliability
Advanced Dialog Management
Protocol Extensions
Performance Optimizations
Event System Integration
┌─────────────────────────────────────────┐
│ Application Layer │
│ (client-core, call-engine) │
├─────────────────────────────────────────┤
│ Session Layer │
│ (session-core) │
├─────────────────────────────────────────┤
│ Dialog Layer │
│ (dialog-core) ⬅️ YOU ARE HERE │
├─────────────────────────────────────────┤
│ Transaction Layer │
│ (transaction-core) │
├─────────────────────────────────────────┤
│ Transport Layer │
│ (sip-transport) │
└─────────────────────────────────────────┘
pub struct DialogManager {
// Core components
transaction_manager: Arc<TransactionManager>,
transport: Arc<dyn SipTransport>,
// Dialog storage and routing
dialogs: Arc<RwLock<DialogStore>>,
routing_table: Arc<RwLock<DialogRoutingTable>>,
// Session coordination
session_coordinator: Option<mpsc::Sender<SessionCoordinationEvent>>,
// Event processing
event_processor: Arc<DialogEventProcessor>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DialogState {
Early, // After 1xx response received/sent
Confirmed, // After 2xx response received/sent
Terminated, // After BYE or error
}
use rvoip_dialog_core::{DialogManager, DialogError, SessionCoordinationEvent};
use rvoip_transaction_core::TransactionManager;
use rvoip_sip_transport::UdpTransport;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), DialogError> {
// Create dependencies
let transaction_manager = Arc::new(TransactionManager::new().await?);
let transport = Arc::new(UdpTransport::new("0.0.0.0:5060").await?);
// Create dialog manager
let dialog_manager = DialogManager::new(
transaction_manager,
transport
).await?;
// Set up session coordination
let (session_tx, mut session_rx) = tokio::sync::mpsc::channel(100);
dialog_manager.set_session_coordinator(session_tx);
// Handle session events
tokio::spawn(async move {
while let Some(event) = session_rx.recv().await {
match event {
SessionCoordinationEvent::IncomingCall { dialog_id, .. } => {
println!("New incoming call: {:?}", dialog_id);
}
SessionCoordinationEvent::CallAnswered { dialog_id, .. } => {
println!("Call answered: {:?}", dialog_id);
}
_ => {}
}
}
});
// Start processing
dialog_manager.start().await?;
Ok(())
}
use rvoip_dialog_core::{DialogManager, DialogId};
use rvoip_sip_core::{Method, Request, Uri};
async fn make_call(
dialog_manager: &DialogManager,
from_uri: Uri,
to_uri: Uri,
sdp_offer: String,
) -> Result<DialogId, DialogError> {
// Create INVITE request
let invite_request = Request::builder()
.method(Method::INVITE)
.uri(to_uri.clone())
.header("From", format!("<{}>;tag={}", from_uri, generate_tag()))
.header("To", format!("<{}>", to_uri))
.header("Call-ID", generate_call_id())
.header("CSeq", "1 INVITE")
.header("Content-Type", "application/sdp")
.body(sdp_offer)
.build()?;
// Create dialog and send INVITE
let dialog_id = dialog_manager.create_dialog(&invite_request).await?;
let transaction_id = dialog_manager.send_request(
&dialog_id,
Method::INVITE,
Some(invite_request.body().clone())
).await?;
Ok(dialog_id)
}
async fn handle_registration(
dialog_manager: &DialogManager,
user_uri: Uri,
contact_uri: Uri,
expires: u32,
) -> Result<(), DialogError> {
let register_request = Request::builder()
.method(Method::REGISTER)
.uri(user_uri.clone())
.header("From", format!("<{}>", user_uri))
.header("To", format!("<{}>", user_uri))
.header("Contact", format!("<{}>;expires={}", contact_uri, expires))
.header("Call-ID", generate_call_id())
.header("CSeq", "1 REGISTER")
.build()?;
dialog_manager.handle_register(
register_request,
"0.0.0.0:5060".parse()?
).await?;
Ok(())
}
use rvoip_dialog_core::recovery::{DialogRecoveryManager, RecoveryConfig};
async fn setup_dialog_recovery(
dialog_manager: &DialogManager,
) -> Result<(), DialogError> {
let recovery_config = RecoveryConfig {
enable_state_persistence: true,
recovery_timeout: Duration::from_secs(30),
max_recovery_attempts: 3,
};
let recovery_manager = DialogRecoveryManager::new(
recovery_config,
dialog_manager.clone()
).await?;
// Enable automatic recovery
recovery_manager.enable_auto_recovery().await?;
Ok(())
}
rvoip-sip-core
: Provides SIP message types, parsing, and core protocol structuresrvoip-transaction-core
: Handles transaction reliability and retransmissionrvoip-sip-transport
: Provides network transport abstractiontokio
: Async runtime for concurrent dialog processingasync-trait
: Async trait support for transport abstractionrvoip-infra-common
: Event bus integration (planned)serde
: Serialization support for dialog persistence (recovery feature)tracing
: Enhanced logging and observability (monitoring feature)┌─────────────────────────────────────────┐
│ Application Layer │
│ (client-core, call-engine) │
├─────────────────────────────────────────┤
│ rvoip-session-core │ ← Coordinates sessions
│ ↕️ │
│ rvoip-dialog-core ⬅️ YOU ARE HERE │ ← Manages SIP dialogs
│ ↕️ │
│ rvoip-transaction-core │ ← Handles reliability
├─────────────────────────────────────────┤
│ rvoip-sip-transport │ ← Network transport
└─────────────────────────────────────────┘
The dialog layer provides:
session-core
transaction-core
RwLock
for dialog accessThe crate provides comprehensive error handling with categorized error types:
use rvoip_dialog_core::{DialogError, DialogResult};
match dialog_result {
Err(DialogError::DialogNotFound(dialog_id)) => {
// Handle missing dialog - often recoverable for new requests
if request.method() == Method::INVITE {
create_new_dialog(request).await?;
}
}
Err(DialogError::InvalidDialogState { current, expected }) => {
// Handle state violations - typically not recoverable
log::error!("Dialog state error: expected {:?}, got {:?}", expected, current);
terminate_dialog(dialog_id).await?;
}
Err(DialogError::TransactionError(tx_error)) => {
// Handle transaction layer errors - may be recoverable
if tx_error.is_recoverable() {
retry_operation().await?;
}
}
Err(DialogError::SessionCoordinationFailed(msg)) => {
// Handle session layer communication errors
log::warn!("Session coordination failed: {}", msg);
// Continue dialog processing without session coordination
}
Ok(result) => {
// Handle success
}
}
impl DialogError {
pub fn is_recoverable(&self) -> bool {
match self {
DialogError::DialogNotFound(_) => true,
DialogError::TransactionError(e) => e.is_recoverable(),
DialogError::InvalidDialogState { .. } => false,
DialogError::SessionCoordinationFailed(_) => true,
_ => false,
}
}
}
Run the comprehensive test suite:
# Run all tests
cargo test -p rvoip-dialog-core
# Run with specific features
cargo test -p rvoip-dialog-core --features "recovery events testing"
# Run integration tests
cargo test -p rvoip-dialog-core --test integration_tests
# Run RFC compliance tests
cargo test -p rvoip-dialog-core --test rfc_compliance
# Run with SIPp interoperability tests
cargo test -p rvoip-dialog-core --test sipp_integration
# Run performance benchmarks
cargo bench -p rvoip-dialog-core
The crate supports the following optional features:
recovery
(default): Dialog recovery and persistence capabilitiesevents
(default): Enhanced event system with filteringmonitoring
(default): Metrics and observability supporttesting
: Additional test utilities and mock implementationsDisable default features and enable only what you need:
[dependencies]
rvoip-dialog-core = { version = "0.1", default-features = false, features = ["recovery"] }
The examples/
directory contains comprehensive examples:
basic_dialog.rs
- Basic dialog creation and managementdialog_recovery.rs
- Dialog recovery and failure handlingmulti_dialog.rs
- Managing multiple concurrent dialogsoutgoing_call.rs
- Complete outgoing call flowregistration_server.rs
- Registration processing examplesession_coordination.rs
- Integration with session-coreRun examples:
cargo run -p rvoip-dialog-core --example basic_dialog
cargo run -p rvoip-dialog-core --example outgoing_call --features "recovery"
The main interface for dialog management:
impl DialogManager {
// Lifecycle
pub async fn new(
transaction_manager: Arc<TransactionManager>,
transport: Arc<dyn SipTransport>
) -> Result<Self, DialogError>;
pub async fn start(&self) -> Result<(), DialogError>;
pub async fn stop(&self) -> Result<(), DialogError>;
// Dialog operations
pub async fn create_dialog(&self, request: &Request) -> Result<DialogId, DialogError>;
pub async fn find_dialog(&self, request: &Request) -> Option<DialogId>;
pub async fn terminate_dialog(&self, dialog_id: &DialogId) -> Result<(), DialogError>;
// Protocol handling
pub async fn handle_invite(&self, request: Request, source: SocketAddr) -> Result<(), DialogError>;
pub async fn handle_bye(&self, request: Request) -> Result<(), DialogError>;
pub async fn handle_register(&self, request: Request, source: SocketAddr) -> Result<(), DialogError>;
// Request/Response operations
pub async fn send_request(&self, dialog_id: &DialogId, method: Method, body: Option<Bytes>) -> Result<TransactionKey, DialogError>;
pub async fn send_response(&self, transaction_id: &TransactionKey, response: Response) -> Result<(), DialogError>;
// Session coordination
pub fn set_session_coordinator(&self, sender: mpsc::Sender<SessionCoordinationEvent>);
// Monitoring and diagnostics
pub fn get_dialog_count(&self) -> usize;
pub fn get_dialog_stats(&self) -> DialogStats;
}
Events sent to session-core
for session management:
#[derive(Debug, Clone)]
pub enum SessionCoordinationEvent {
IncomingCall {
dialog_id: DialogId,
transaction_id: TransactionKey,
request: Request,
source: SocketAddr,
},
CallAnswered {
dialog_id: DialogId,
session_answer: String, // SDP
},
CallTerminated {
dialog_id: DialogId,
reason: String,
},
RegistrationRequest {
transaction_id: TransactionKey,
from_uri: Uri,
contact_uri: Uri,
expires: u32,
},
DialogStateChanged {
dialog_id: DialogId,
old_state: DialogState,
new_state: DialogState,
},
}
This crate is designed to be used by session-core
as its dialog management layer:
// In session-core
use rvoip_dialog_core::{DialogManager, SessionCoordinationEvent};
impl SessionManager {
pub async fn new() -> Result<Self, Error> {
let dialog_manager = DialogManager::new(
transaction_manager,
transport
).await?;
// Set up coordination
let (coord_tx, coord_rx) = mpsc::channel(100);
dialog_manager.set_session_coordinator(coord_tx);
// Handle coordination events
self.spawn_coordination_handler(coord_rx);
Ok(SessionManager {
dialog_manager,
// ... other fields
})
}
async fn spawn_coordination_handler(&self, mut coord_rx: mpsc::Receiver<SessionCoordinationEvent>) {
tokio::spawn(async move {
while let Some(event) = coord_rx.recv().await {
match event {
SessionCoordinationEvent::IncomingCall { dialog_id, .. } => {
// Create new session for incoming call
self.create_session(dialog_id).await;
}
SessionCoordinationEvent::CallTerminated { dialog_id, .. } => {
// Clean up session resources
self.terminate_session(dialog_id).await;
}
_ => {}
}
}
});
}
}
See TODO.md for a comprehensive list of planned enhancements, including:
This crate is part of the RVOIP architecture refactoring to establish clean layer separation. Current status:
Contributions are welcome! Please see the main rvoip contributing guidelines for details.
When contributing to dialog-core:
This project is licensed under either of
at your option.