rvoip_client_core/
lib.rs

1//! High-level SIP client library for VoIP applications
2//! 
3//! This crate provides a user-friendly API for building SIP/VoIP client applications.
4//! It handles the complexity of SIP signaling, media negotiation, and call management
5//! while presenting a simple, async-first interface.
6//! 
7//! # Architecture
8//! 
9//! ```text
10//! ┌─────────────────────────┐
11//! │    Your Application     │
12//! └───────────┬─────────────┘
13//!             │ 
14//! ┌───────────▼─────────────┐
15//! │     client-core         │ ◄── You are here
16//! │ ┌─────────────────────┐ │
17//! │ │   ClientManager     │ │     • High-level call control
18//! │ │   Registration      │ │     • Event handling  
19//! │ │   Media Control     │ │     • Clean async API
20//! │ └─────────────────────┘ │
21//! └───────────┬─────────────┘
22//!             │
23//! ┌───────────▼─────────────┐
24//! │     session-core        │     • Session management
25//! │                         │     • Protocol coordination
26//! └───────────┬─────────────┘     • Infrastructure abstraction
27//!             │
28//! ┌───────────▼─────────────┐
29//! │   Lower-level crates    │     • SIP, RTP, Media
30//! │ (transaction, dialog,   │     • Transport layers
31//! │  sip-core, etc.)        │     • Codec processing
32//! └─────────────────────────┘
33//! ```
34//! 
35//! # Quick Start
36//! 
37//! ## Basic Client Setup
38//! 
39//! ```rust,no_run
40//! use rvoip_client_core::{ClientBuilder, Client, ClientEvent};
41//! use std::sync::Arc;
42//! 
43//! #[tokio::main]
44//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
45//!     // Build and start a client
46//!     let client = ClientBuilder::new()
47//!         .user_agent("MyApp/1.0")
48//!         .local_address("0.0.0.0:5060".parse()?)
49//!         .build()
50//!         .await?;
51//!         
52//!     client.start().await?;
53//!     
54//!     // Subscribe to events
55//!     let mut events = client.subscribe_events();
56//!     
57//!     // Make a call
58//!     let call_id = client.make_call(
59//!         "sip:alice@example.com".to_string(),
60//!         "sip:bob@example.com".to_string(),
61//!         None, // Let session-core generate SDP
62//!     ).await?;
63//!     
64//!     // Handle events (in real applications, this would run until shutdown)
65//!     tokio::select! {
66//!         result = events.recv() => {
67//!             if let Ok(event) = result {
68//!                 match event {
69//!                     ClientEvent::CallStateChanged { info, .. } => {
70//!                         println!("Call {} state: {:?}", info.call_id, info.new_state);
71//!                     }
72//!                     _ => {}
73//!                 }
74//!             }
75//!         }
76//!         _ = tokio::time::sleep(tokio::time::Duration::from_secs(30)) => {
77//!             println!("Application timeout");
78//!         }
79//!     }
80//!     
81//!     Ok(())
82//! }
83//! ```
84//! 
85//! ## Registration Example
86//! 
87//! ```rust
88//! # use rvoip_client_core::{ClientBuilder, registration::RegistrationConfig};
89//! # use std::sync::Arc;
90//! # async fn example(client: Arc<rvoip_client_core::Client>) -> Result<(), Box<dyn std::error::Error>> {
91//! // Register with a SIP server
92//! let config = RegistrationConfig::new(
93//!     "sip:registrar.example.com".to_string(),
94//!     "sip:alice@example.com".to_string(),
95//!     "sip:alice@192.168.1.100:5060".to_string(),
96//! )
97//! .with_credentials("alice".to_string(), "secret123".to_string())
98//! .with_expires(3600); // 1 hour
99//! 
100//! let reg_id = client.register(config).await?;
101//! 
102//! // Later, refresh the registration
103//! client.refresh_registration(reg_id).await?;
104//! 
105//! // Or unregister
106//! client.unregister(reg_id).await?;
107//! # Ok(())
108//! # }
109//! ```
110//! 
111//! # Network Configuration
112//! 
113//! ## Bind Address Propagation
114//! 
115//! When you configure a specific IP address, it propagates through all layers:
116//! 
117//! ```rust,no_run
118//! use rvoip_client_core::ClientBuilder;
119//! 
120//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
121//! // This ensures 192.168.1.100 is used at all layers
122//! let client = ClientBuilder::new()
123//!     .local_address("192.168.1.100:5060".parse()?)
124//!     .media_address("192.168.1.100:0".parse()?)  // Same IP, auto port
125//!     .build()
126//!     .await?;
127//! # Ok(())
128//! # }
129//! ```
130//! 
131//! The configured IP address propagates to session-core, dialog-core, and transport layers.
132//! No more hardcoded 0.0.0.0 addresses when you specify an IP.
133//! 
134//! ## Automatic Port Allocation
135//! 
136//! Set media port to 0 for automatic allocation:
137//! 
138//! ```rust,no_run
139//! use rvoip_client_core::ClientBuilder;
140//! 
141//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
142//! let client = ClientBuilder::new()
143//!     .local_address("127.0.0.1:5060".parse()?)      // SIP on standard port
144//!     .media_address("127.0.0.1:0".parse()?)         // Port 0 = auto
145//!     .rtp_ports(10000, 20000)                       // RTP port range
146//!     .build()
147//!     .await?;
148//! # Ok(())
149//! # }
150//! ```
151//! 
152//! When port is set to 0:
153//! - It signals automatic allocation from the RTP port range
154//! - Actual allocation happens when media sessions are created
155//! - Each media session gets unique ports from the pool
156//! 
157//! # Features
158//! 
159//! - **Call Management**: Make, receive, hold, transfer calls
160//! - **Registration**: SIP REGISTER support with authentication
161//! - **Media Control**: Audio mute/unmute, codec selection, SDP handling
162//! - **Event System**: Async event notifications for all operations
163//! - **Clean Architecture**: All complexity handled through session-core
164//! - **Network Flexibility**: Automatic port allocation and bind address control
165//! 
166//! # Error Handling
167//! 
168//! All operations return `ClientResult<T>` which wraps `ClientError`:
169//! 
170//! ```rust
171//! use rvoip_client_core::{Client, ClientError};
172//! use std::sync::Arc;
173//! 
174//! async fn example(client: Arc<Client>) -> Result<(), Box<dyn std::error::Error>> {
175//!     // Example error handling pattern
176//!     match client.make_call(
177//!         "sip:alice@example.com".to_string(), 
178//!         "sip:bob@example.com".to_string(), 
179//!         None
180//!     ).await {
181//!         Ok(call_id) => {
182//!             println!("Call started: {}", call_id);
183//!         }
184//!         Err(ClientError::NetworkError { reason }) => {
185//!             eprintln!("Network problem: {}", reason);
186//!             // Could retry with exponential backoff
187//!         }
188//!         Err(ClientError::InvalidConfiguration { field, reason }) => {
189//!             eprintln!("Configuration error in {}: {}", field, reason);
190//!             // Fix configuration and retry
191//!         }
192//!         Err(e) => {
193//!             eprintln!("Call failed: {}", e);
194//!             // Handle other error types
195//!         }
196//!     }
197//!     Ok(())
198//! }
199//! ```
200
201#![warn(missing_docs)]
202#![doc(html_root_url = "https://docs.rs/rvoip-client-core/0.1.0")]
203
204pub mod client;
205pub mod call;
206pub mod registration;
207pub mod events;
208pub mod error;
209
210// Public API exports (only high-level client-core types)
211pub use client::{
212    ClientManager,
213    ClientCallHandler,
214    Client,
215    ClientBuilder,
216    // All types are now re-exported from types module via client::mod
217    ClientStats,
218    CallCapabilities,
219    CallMediaInfo,
220    AudioCodecInfo,
221    AudioQualityMetrics,
222    MediaCapabilities,
223    MediaSessionInfo,
224    NegotiatedMediaParams,
225    EnhancedMediaCapabilities,
226    ClientConfig,
227    MediaConfig,
228    MediaPreset,
229    MediaConfigBuilder,
230};
231pub use call::{CallId, CallInfo, CallDirection, CallState};
232pub use registration::{RegistrationConfig, RegistrationInfo, RegistrationStatus};
233pub use events::{
234    ClientEventHandler, 
235    ClientEvent, 
236    IncomingCallInfo, 
237    CallStatusInfo, 
238    RegistrationStatusInfo,
239    CallAction,
240    MediaEventType,
241    MediaEventInfo,
242    EventFilter,
243    EventPriority,
244    EventSubscription,
245    EventEmitter,
246};
247pub use error::{ClientError, ClientResult};
248
249// Re-export commonly used types from session-core (for convenience)
250pub use rvoip_session_core::api::types::SessionId;
251
252// Re-export streaming audio types from session-core
253pub use rvoip_session_core::api::types::{
254    AudioFrame, 
255    AudioFrameSubscriber, 
256    AudioStreamConfig
257};
258
259/// Client-core version information
260pub const VERSION: &str = env!("CARGO_PKG_VERSION");
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use std::sync::Arc;
266    
267    #[tokio::test]
268    async fn test_client_manager_creation() {
269        // Test that we can create a ClientManager
270        let config = ClientConfig::new()
271            .with_sip_addr("127.0.0.1:5080".parse().unwrap())
272            .with_media_addr("127.0.0.1:5081".parse().unwrap());
273        
274        let client_result = ClientManager::new(config).await;
275        assert!(client_result.is_ok(), "ClientManager creation should succeed");
276        
277        let _client = client_result.unwrap();
278        // Test passes if we can create the client without panicking
279    }
280    
281    #[tokio::test]
282    async fn test_client_manager_lifecycle() {
283        // Test start/stop lifecycle
284        let config = ClientConfig::new()
285            .with_sip_addr("127.0.0.1:5082".parse().unwrap())
286            .with_media_addr("127.0.0.1:5083".parse().unwrap());
287        
288        let client = ClientManager::new(config).await
289            .expect("Failed to create client");
290        
291        // Test start
292        let start_result = client.start().await;
293        assert!(start_result.is_ok(), "Client start should succeed");
294        
295        // Test stop
296        let stop_result = client.stop().await;
297        assert!(stop_result.is_ok(), "Client stop should succeed");
298    }
299    
300    // Helper event handler for testing
301    struct TestEventHandler;
302    
303    #[async_trait::async_trait]
304    impl ClientEventHandler for TestEventHandler {
305        async fn on_incoming_call(&self, _call_info: IncomingCallInfo) -> CallAction {
306            CallAction::Accept
307        }
308        
309        async fn on_call_state_changed(&self, _status_info: CallStatusInfo) {
310            // No-op for test
311        }
312        
313        async fn on_registration_status_changed(&self, _status_info: RegistrationStatusInfo) {
314            // No-op for test
315        }
316        
317        async fn on_media_event(&self, _media_info: MediaEventInfo) {
318            // No-op for test  
319        }
320        
321        async fn on_client_error(&self, _error: ClientError, _call_id: Option<CallId>) {
322            // No-op for test
323        }
324        
325        async fn on_network_event(&self, _connected: bool, _reason: Option<String>) {
326            // No-op for test
327        }
328    }
329
330    #[tokio::test]
331    async fn test_client_core_compiles() {
332        // Basic compilation test
333        assert!(true);
334    }
335    
336    #[tokio::test]
337    async fn test_registration_not_implemented() {
338        // Test that registration operations return NotImplemented error
339        let config = ClientConfig::new()
340            .with_sip_addr("127.0.0.1:5084".parse().unwrap())
341            .with_media_addr("127.0.0.1:5085".parse().unwrap());
342        
343        let client = ClientManager::new(config).await.unwrap();
344        let reg_config = RegistrationConfig::new(
345            "sip:server.example.com".to_string(),
346            "sip:user@example.com".to_string(),
347            "sip:user@127.0.0.1:5084".to_string(),
348        );
349        
350        let result = client.register(reg_config).await;
351        assert!(result.is_err());
352        match result.unwrap_err() {
353            ClientError::NotImplemented { .. } => {
354                // Expected - registration not available in session-core
355            }
356            ClientError::InternalError { message } if message.contains("Registration failed") => {
357                // Also acceptable - session-core might have registration support now
358            }
359            other => panic!("Expected NotImplemented or InternalError, got: {:?}", other),
360        }
361    }
362    
363    #[tokio::test]
364    async fn test_priority_4_1_media_integration() {
365        // Test Priority 4.1: Enhanced Media Integration APIs
366        let config = ClientConfig::new()
367            .with_sip_addr("127.0.0.1:5086".parse().unwrap())
368            .with_media_addr("127.0.0.1:5087".parse().unwrap());
369        
370        let client = ClientManager::new(config).await.unwrap();
371        let handler = Arc::new(TestEventHandler);
372        client.set_event_handler(handler).await;
373        
374        // Start the client
375        client.start().await.unwrap();
376        
377        // Test media capabilities API
378        let capabilities = client.get_media_capabilities().await;
379        assert!(capabilities.can_mute_microphone);
380        assert!(capabilities.can_mute_speaker);
381        assert!(capabilities.can_hold);
382        assert!(capabilities.can_send_dtmf);
383        assert!(capabilities.supports_rtp);
384        assert!(!capabilities.supported_codecs.is_empty());
385        
386        // Test codec enumeration
387        let codecs = client.get_available_codecs().await;
388        assert!(!codecs.is_empty());
389        
390        // Verify standard codecs are available
391        let codec_names: Vec<String> = codecs.iter().map(|c| c.name.clone()).collect();
392        assert!(codec_names.contains(&"PCMU".to_string()));
393        assert!(codec_names.contains(&"PCMA".to_string()));
394        assert!(codec_names.contains(&"OPUS".to_string()));
395        
396        // Test codec preference setting
397        let preferred_codecs = vec!["OPUS".to_string(), "PCMU".to_string()];
398        let result = client.set_preferred_codecs(preferred_codecs).await;
399        assert!(result.is_ok());
400        
401        // Test with a mock call (this would normally fail without an actual session)
402        // but we're testing the API structure
403        let fake_call_id = CallId::new_v4();
404        
405        // Test media info for non-existent call (should fail gracefully)
406        let media_info_result = client.get_call_media_info(&fake_call_id).await;
407        assert!(media_info_result.is_err());
408        match media_info_result.unwrap_err() {
409            ClientError::CallNotFound { .. } => {
410                // Expected for non-existent call
411            }
412            other => panic!("Expected CallNotFound error, got: {:?}", other),
413        }
414        
415        // Test mute state for non-existent call
416        let mute_result = client.get_microphone_mute_state(&fake_call_id).await;
417        assert!(mute_result.is_err());
418        
419        // Test speaker mute state for non-existent call
420        let speaker_mute_result = client.get_speaker_mute_state(&fake_call_id).await;
421        assert!(speaker_mute_result.is_err());
422        
423        // Test audio transmission status for non-existent call
424        let audio_active_result = client.is_audio_transmission_active(&fake_call_id).await;
425        assert!(audio_active_result.is_err());
426        
427        client.stop().await.unwrap();
428        
429        println!("✅ Priority 4.1 Media Integration APIs validated successfully!");
430    }
431    
432    #[tokio::test]
433    async fn test_priority_4_2_media_session_coordination() {
434        // Test Priority 4.2: Media Session Coordination APIs
435        let config = ClientConfig::new()
436            .with_sip_addr("127.0.0.1:5088".parse().unwrap())
437            .with_media_addr("127.0.0.1:5089".parse().unwrap());
438        
439        let client = ClientManager::new(config).await.unwrap();
440        let handler = Arc::new(TestEventHandler);
441        client.set_event_handler(handler).await;
442        
443        // Start the client
444        client.start().await.unwrap();
445        
446        // Test enhanced media capabilities
447        let enhanced_capabilities = client.get_enhanced_media_capabilities().await;
448        assert!(enhanced_capabilities.supports_sdp_offer_answer);
449        assert!(enhanced_capabilities.supports_media_session_lifecycle);
450        assert!(enhanced_capabilities.supports_sdp_renegotiation);
451        assert!(enhanced_capabilities.supports_early_media);
452        assert!(enhanced_capabilities.supports_media_session_updates);
453        assert!(enhanced_capabilities.supports_codec_negotiation);
454        assert_eq!(enhanced_capabilities.supported_sdp_version, "0");
455        assert_eq!(enhanced_capabilities.preferred_rtp_port_range, (10000, 20000));
456        assert!(enhanced_capabilities.supported_transport_protocols.contains(&"RTP/AVP".to_string()));
457        
458        // Test with fake call ID (APIs should fail gracefully)
459        let fake_call_id = CallId::new_v4();
460        
461        // Test SDP generation for non-existent call
462        let sdp_offer_result = client.generate_sdp_offer(&fake_call_id).await;
463        assert!(sdp_offer_result.is_err());
464        match sdp_offer_result.unwrap_err() {
465            ClientError::CallNotFound { .. } => {
466                // Expected for non-existent call
467            }
468            other => panic!("Expected CallNotFound error, got: {:?}", other),
469        }
470        
471        // Test SDP answer processing for non-existent call
472        let sdp_answer_result = client.process_sdp_answer(&fake_call_id, "v=0\r\nm=audio 5004 RTP/AVP 0\r\n").await;
473        assert!(sdp_answer_result.is_err());
474        
475        // Test empty SDP answer validation
476        let empty_sdp_result = client.process_sdp_answer(&fake_call_id, "").await;
477        assert!(empty_sdp_result.is_err());
478        match empty_sdp_result.unwrap_err() {
479            ClientError::InvalidConfiguration { field, .. } => {
480                assert_eq!(field, "sdp_answer");
481            }
482            other => panic!("Expected InvalidConfiguration error, got: {:?}", other),
483        }
484        
485        // Test media session lifecycle for non-existent call
486        let start_media_result = client.start_media_session(&fake_call_id).await;
487        assert!(start_media_result.is_err());
488        
489        let stop_media_result = client.stop_media_session(&fake_call_id).await;
490        assert!(stop_media_result.is_err());
491        
492        // Test media session status for non-existent call
493        let is_active_result = client.is_media_session_active(&fake_call_id).await;
494        assert!(is_active_result.is_err());
495        
496        // Test media session info for non-existent call
497        let session_info_result = client.get_media_session_info(&fake_call_id).await;
498        assert!(session_info_result.is_err());
499        
500        // Test negotiated media params for non-existent call
501        let negotiated_params_result = client.get_negotiated_media_params(&fake_call_id).await;
502        assert!(negotiated_params_result.is_err());
503        
504        // Test media session update for non-existent call
505        let update_result = client.update_media_session(&fake_call_id, "v=0\r\nm=audio 5006 RTP/AVP 0\r\n").await;
506        assert!(update_result.is_err());
507        
508        // Test empty SDP update validation
509        let empty_update_result = client.update_media_session(&fake_call_id, "").await;
510        assert!(empty_update_result.is_err());
511        match empty_update_result.unwrap_err() {
512            ClientError::InvalidConfiguration { field, .. } => {
513                assert_eq!(field, "new_sdp");
514            }
515            other => panic!("Expected InvalidConfiguration error, got: {:?}", other),
516        }
517        
518        client.stop().await.unwrap();
519        
520        println!("✅ Priority 4.2 Media Session Coordination APIs validated successfully!");
521    }
522}