rvoip_transaction_core/lib.rs
1pub mod client;
2pub mod error;
3pub mod server;
4pub mod manager;
5pub mod transaction;
6pub mod timer;
7pub mod utils;
8pub mod method;
9pub mod transport;
10pub mod dialog;
11
12// Re-export core types
13pub use error::{Error, Result};
14pub use manager::TransactionManager;
15pub use transaction::{
16 Transaction, TransactionAsync, TransactionState, TransactionKind, TransactionKey, TransactionEvent,
17 InternalTransactionCommand,
18};
19pub use client::{ClientInviteTransaction, ClientNonInviteTransaction, ClientTransaction, TransactionExt as ClientTransactionExt};
20pub use server::{ServerInviteTransaction, ServerNonInviteTransaction, ServerTransaction, TransactionExt as ServerTransactionExt};
21pub use timer::{Timer, TimerManager, TimerFactory, TimerSettings, TimerType};
22pub use transport::TransportManager;
23pub use rvoip_sip_transport::transport::TransportType;
24pub use transport::{
25 TransportCapabilities, TransportInfo, NetworkInfoForSdp,
26 WebSocketStatus, TransportCapabilitiesExt
27};
28
29/// Convenient re-exports for request and response builders
30pub mod builders {
31 /// Client-side request builders for common SIP operations
32 pub use crate::client::builders::{
33 InviteBuilder, ByeBuilder, RegisterBuilder, InDialogRequestBuilder,
34 quick as client_quick
35 };
36
37 /// Server-side response builders for common SIP operations
38 pub use crate::server::builders::{
39 ResponseBuilder, InviteResponseBuilder, RegisterResponseBuilder,
40 quick as server_quick
41 };
42
43 /// Dialog utility functions for bridging dialog-core integration
44 pub mod dialog_utils {
45 pub use crate::dialog::{
46 DialogRequestTemplate, DialogTransactionContext,
47 request_builder_from_dialog_template, response_builder_for_dialog_transaction,
48 extract_dialog_template_from_request, create_dialog_transaction_context,
49 helpers
50 };
51 }
52
53 /// Quick dialog functions for one-liner dialog operations
54 pub mod dialog_quick {
55 pub use crate::dialog::quick::{
56 bye_for_dialog, refer_for_dialog, update_for_dialog, info_for_dialog,
57 notify_for_dialog, message_for_dialog, reinvite_for_dialog,
58 response_for_dialog_transaction
59 };
60 }
61}
62
63/// # SIP Transaction Layer
64///
65/// This crate implements the SIP transaction layer as defined in [RFC 3261](https://datatracker.ietf.org/doc/html/rfc3261),
66/// providing reliable message delivery and state management for SIP request-response exchanges.
67///
68/// ## Purpose and Responsibilities
69///
70/// The transaction layer in a SIP stack has several key responsibilities:
71///
72/// 1. **Message Reliability**: Ensuring messages are delivered reliably over unreliable transports (e.g., UDP)
73/// by handling retransmissions and timeouts.
74///
75/// 2. **State Management**: Implementing the state machines defined in RFC 3261 for handling different types
76/// of SIP transactions (INVITE client/server, non-INVITE client/server).
77///
78/// 3. **Transaction Matching**: Correctly matching requests and responses to their corresponding transactions
79/// based on SIP headers (Via, CSeq, etc.) according to the rules defined in RFC 3261 Section 17.1.3 and 17.2.3.
80///
81/// 4. **Timer Management**: Managing various timers required by the SIP protocol for retransmissions,
82/// transaction timeouts, and cleanup operations.
83///
84/// 5. **ACK Handling**: Special processing for ACK messages, which are treated differently depending on
85/// whether they acknowledge 2xx or non-2xx responses.
86///
87/// 6. **Transaction User Interface**: Providing a clean API for the Transaction User (TU) layer to
88/// send requests, receive responses, and handle events.
89///
90/// ## Architecture
91///
92/// The transaction layer sits between the transport layer and the transaction user (TU) layer in the SIP stack:
93///
94/// ```text
95/// +--------------------------------------+
96/// | Transaction User (TU) |
97/// | (dialog management, call control) |
98/// +--------------------------------------+
99/// |
100/// v
101/// +--------------------------------------+
102/// | Transaction Layer |
103/// | (this crate: transaction-core) |
104/// +--------------------------------------+
105/// |
106/// v
107/// +--------------------------------------+
108/// | Transport Layer |
109/// | (UDP, TCP, TLS, WebSocket) |
110/// +--------------------------------------+
111/// |
112/// v
113/// +--------------------------------------+
114/// | Network |
115/// +--------------------------------------+
116/// ```
117///
118/// ### Transaction vs. Dialog Layer
119///
120/// It's important to understand the separation between the transaction layer and dialog layer in SIP:
121///
122/// - **Transaction Layer** (this library): Handles individual request-response exchanges and ensures
123/// reliable message delivery. Transactions are short-lived with well-defined lifecycles.
124///
125/// - **Dialog Layer** (implemented in session-core): Maintains long-lived application state across
126/// multiple transactions. Dialogs track the relationship between endpoints using Call-ID, tags,
127/// and sequence numbers.
128///
129/// This separation allows the transaction layer to focus solely on message reliability and state management,
130/// while the dialog layer handles higher-level application logic.
131///
132/// ### Relationship to Other Libraries
133///
134/// In the RVOIP project, transaction-core interacts with:
135///
136/// - **sip-core**: Provides SIP message parsing, construction, and basic types
137/// - **sip-transport**: Handles the actual sending and receiving of SIP messages
138/// - **session-core**: Consumer of transaction services, implementing dialog management
139///
140/// The transaction layer isolates the transport details from higher layers while providing
141/// transaction state management that higher layers don't need to implement.
142///
143/// #### Transaction Core and Session Core Relationship
144///
145/// The `transaction-core` and `session-core` libraries are designed to work together while maintaining
146/// clear separation of concerns:
147///
148/// - **transaction-core** handles individual message exchanges with reliability and retransmission
149/// according to RFC 3261 Section 17, ensuring messages are delivered and properly tracked.
150///
151/// - **session-core** builds on top of transaction-core to implement dialog management (RFC 3261 Section 12)
152/// and higher-level session concepts like calls and registrations.
153///
154/// Typically, an application would:
155///
156/// 1. Create a `TransactionManager` from transaction-core
157/// 2. Pass it to a `SessionManager` from session-core
158/// 3. Work primarily with the SessionManager's higher-level API
159/// 4. Receive events from both layers (transaction events and session events)
160///
161/// This layered architecture allows each component to focus on its specific responsibilities
162/// while providing clean integration points between layers.
163///
164/// ## Library Organization
165///
166/// The codebase is organized into several modules:
167///
168/// - **manager**: Contains the `TransactionManager`, the main entry point for the library
169/// - **client**: Implements client transaction types (INVITE and non-INVITE)
170/// - **server**: Implements server transaction types (INVITE and non-INVITE)
171/// - **transaction**: Defines common transaction traits, states, and events
172/// - **timer**: Implements timer management for retransmissions and timeouts
173/// - **method**: Handles special method-specific behavior (CANCEL, ACK, etc.)
174/// - **utils**: Utility functions for transaction processing
175/// - **error**: Error types and results for the library
176///
177/// Most users will primarily interact with the `TransactionManager` class, which provides
178/// the public API for creating and managing transactions.
179///
180/// ## Key Components
181///
182/// * [`TransactionManager`]: Central coordinator for all transactions. Responsible for:
183/// - Creating and tracking transactions
184/// - Routing incoming messages to the right transaction
185/// - Handling "stray" messages that don't match any transaction
186/// - Providing a unified interface to the Transaction User
187///
188/// * Transaction Types:
189/// * [`ClientInviteTransaction`]: Implements RFC 3261 section 17.1.1 state machine
190/// * [`ClientNonInviteTransaction`]: Implements RFC 3261 section 17.1.2 state machine
191/// * [`ServerInviteTransaction`]: Implements RFC 3261 section 17.2.1 state machine
192/// * [`ServerNonInviteTransaction`]: Implements RFC 3261 section 17.2.2 state machine
193///
194/// * Timer Management:
195/// * [`TimerManager`]: Manages transaction timers as per RFC 3261
196/// * [`TimerFactory`]: Creates appropriate timers for different transaction types
197/// * [`TimerSettings`]: Configures timer durations (T1, T2, etc.)
198///
199/// ## Transaction Matching
200///
201/// The transaction layer needs to match incoming messages to the right transaction. According to RFC 3261:
202///
203/// - **For Responses**: Matched using the branch parameter, sent-by value in the top Via header,
204/// and CSeq method.
205///
206/// - **For ACK to non-2xx**: Matched to the original INVITE transaction. The branch parameter and other
207/// identifiers remain the same as the INVITE.
208///
209/// - **For ACK to 2xx**: Not matched to any transaction - handled by the TU (dialog layer).
210///
211/// - **For CANCEL**: Creates a new transaction but matches to an existing INVITE transaction
212/// with the same identifiers (except method).
213///
214/// The `TransactionManager` implements these matching rules to route messages to the appropriate
215/// transaction instance.
216///
217/// ## Usage Examples
218///
219/// ### 1. Basic Client Transaction
220///
221/// ```
222/// # mod doctest_helpers {
223/// # use rvoip_sip_core::{Method, Message as SipMessage, Request as SipCoreRequest, Response as SipCoreResponse, Uri};
224/// # use rvoip_sip_core::builder::{SimpleRequestBuilder, SimpleResponseBuilder};
225/// # use rvoip_sip_core::types::{
226/// # header::TypedHeader,
227/// # content_length::ContentLength as ContentLengthHeaderType,
228/// # status::StatusCode,
229/// # param::Param,
230/// # via::Via,
231/// # };
232/// # use rvoip_sip_transport::{Transport, Error as TransportError, TransportEvent as TransportLayerEvent};
233/// # use std::net::SocketAddr;
234/// # use std::sync::Arc;
235/// # use tokio::sync::{Mutex, mpsc};
236/// # use std::collections::VecDeque;
237/// # use async_trait::async_trait;
238/// # use std::str::FromStr;
239/// # use uuid::Uuid;
240///
241/// # #[derive(Debug, Clone)]
242/// # pub struct DocMockTransport {
243/// # pub sent_messages: Arc<Mutex<VecDeque<(SipMessage, SocketAddr)>>>,
244/// # pub event_injector: mpsc::Sender<TransportLayerEvent>,
245/// # local_socket_addr: SocketAddr,
246/// # is_transport_closed: Arc<Mutex<bool>>,
247/// # }
248///
249/// # impl DocMockTransport {
250/// # pub fn new(event_injector: mpsc::Sender<TransportLayerEvent>, local_addr_str: &str) -> Self {
251/// # DocMockTransport {
252/// # sent_messages: Arc::new(Mutex::new(VecDeque::new())),
253/// # event_injector,
254/// # local_socket_addr: local_addr_str.parse().expect("Invalid local_addr_str for DocMockTransport"),
255/// # is_transport_closed: Arc::new(Mutex::new(false)),
256/// # }
257/// # }
258/// # pub async fn inject_event(&self, event: TransportLayerEvent) -> std::result::Result<(), String> {
259/// # self.event_injector.send(event).await.map_err(|e| e.to_string())
260/// # }
261/// # }
262///
263/// # #[async_trait]
264/// # impl Transport for DocMockTransport {
265/// # fn local_addr(&self) -> std::result::Result<SocketAddr, TransportError> {
266/// # Ok(self.local_socket_addr)
267/// # }
268/// #
269/// # async fn send_message(&self, message: SipMessage, destination: SocketAddr) -> std::result::Result<(), TransportError> {
270/// # if *self.is_transport_closed.lock().await {
271/// # return Err(TransportError::TransportClosed);
272/// # }
273/// # let mut sent = self.sent_messages.lock().await;
274/// # sent.push_back((message, destination));
275/// # Ok(())
276/// # }
277/// #
278/// # async fn close(&self) -> std::result::Result<(), TransportError> {
279/// # let mut closed_status = self.is_transport_closed.lock().await;
280/// # *closed_status = true;
281/// # Ok(())
282/// # }
283/// #
284/// # fn is_closed(&self) -> bool {
285/// # self.is_transport_closed.try_lock().map_or(false, |guard| *guard)
286/// # }
287/// # }
288///
289/// # pub fn build_invite_request(from_domain: &str, to_domain: &str, local_contact_host: &str) -> std::result::Result<SipCoreRequest, Box<dyn std::error::Error>> {
290/// # let from_uri_str = format!("sip:alice@{}", from_domain);
291/// # let to_uri_str = format!("sip:bob@{}", to_domain);
292/// # let contact_uri_str = format!("sip:alice@{}", local_contact_host);
293/// #
294/// # let request = SimpleRequestBuilder::new(Method::Invite, &to_uri_str)?
295/// # .from("Alice", &from_uri_str, Some("fromtagClient1"))
296/// # .to("Bob", &to_uri_str, None)
297/// # .call_id(&format!("callclient1-{}", Uuid::new_v4()))
298/// # .cseq(1)
299/// # .contact(&contact_uri_str, Some("Alice Contact"))
300/// # .via(&format!("{}:5060", local_contact_host), "UDP", Some(&format!("z9hG4bK{}", Uuid::new_v4().simple())))
301/// # .header(TypedHeader::ContentLength(ContentLengthHeaderType::new(0)))
302/// # .build();
303/// # Ok(request)
304/// # }
305/// #
306/// # pub fn build_trying_response(invite_req: &SipCoreRequest) -> std::result::Result<SipCoreResponse, Box<dyn std::error::Error>> {
307/// # let response = SimpleResponseBuilder::response_from_request(invite_req, StatusCode::Trying, Some("Trying"))
308/// # .header(TypedHeader::ContentLength(ContentLengthHeaderType::new(0)))
309/// # .build();
310/// # Ok(response)
311/// # }
312/// # }
313/// # use doctest_helpers::*;
314/// use rvoip_transaction_core::{TransactionManager, TransactionEvent, TransactionKey};
315/// use rvoip_sip_core::{Method, Message as SipMessage, Request as SipCoreRequest, Response as SipCoreResponse, types::status::StatusCode};
316/// use rvoip_sip_transport::{TransportEvent as TransportLayerEvent, Transport};
317/// use std::net::SocketAddr;
318/// use std::sync::Arc;
319/// use tokio::sync::mpsc;
320/// use std::time::Duration;
321///
322/// #[tokio::main]
323/// async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
324/// let (event_injector_tx, event_injector_rx_for_manager) = mpsc::channel(100);
325/// let mock_client_addr = "127.0.0.1:5080";
326/// let mock_transport = Arc::new(DocMockTransport::new(event_injector_tx, mock_client_addr));
327///
328/// let (manager, mut client_events_rx) = TransactionManager::new(
329/// mock_transport.clone() as Arc<dyn Transport>,
330/// event_injector_rx_for_manager,
331/// Some(10)
332/// ).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
333///
334/// let invite_req = build_invite_request("client.com", "server.com", mock_client_addr)?;
335/// let destination_server_addr: SocketAddr = "127.0.0.1:5090".parse()?;
336///
337/// let tx_id = manager.create_client_transaction(invite_req.clone(), destination_server_addr)
338/// .await.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
339/// println!("Client transaction created with ID: {}", tx_id);
340///
341/// // Send the request after creating the transaction
342/// manager.send_request(&tx_id).await.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
343///
344/// // Allow more time for the transaction manager to send the message
345/// tokio::time::sleep(Duration::from_millis(100)).await;
346///
347/// // Process any state change events before checking messages
348/// tokio::select! {
349/// Some(event) = client_events_rx.recv() => {
350/// match event {
351/// TransactionEvent::StateChanged { transaction_id, previous_state, new_state }
352/// if transaction_id == tx_id => {
353/// println!("Transaction state changed from {:?} to {:?}", previous_state, new_state);
354/// // This is the expected StateChanged event, continue with the test
355/// },
356/// _ => {} // Ignore other events
357/// }
358/// },
359/// _ = tokio::time::sleep(Duration::from_millis(50)) => {
360/// // Timeout waiting for event, continue anyway
361/// }
362/// }
363///
364/// let sent_messages = mock_transport.sent_messages.lock().await;
365/// assert_eq!(sent_messages.len(), 1, "INVITE should have been sent");
366/// if let Some((msg, dest)) = sent_messages.front() {
367/// assert!(msg.is_request(), "Sent message should be a request");
368/// assert_eq!(msg.method(), Some(Method::Invite));
369/// assert_eq!(*dest, destination_server_addr);
370/// } else {
371/// panic!("No message found in sent_messages");
372/// }
373/// drop(sent_messages);
374///
375/// let trying_response_msg = build_trying_response(&invite_req)?;
376/// mock_transport.inject_event(TransportLayerEvent::MessageReceived {
377/// message: SipMessage::Response(trying_response_msg.clone()),
378/// source: destination_server_addr,
379/// destination: mock_transport.local_addr()?,
380/// }).await?;
381///
382/// tokio::select! {
383/// Some(event) = client_events_rx.recv() => {
384/// match event {
385/// TransactionEvent::ProvisionalResponse { transaction_id, response, .. } if transaction_id == tx_id => {
386/// println!("Received Provisional Response: {} {}", response.status_code(), response.reason_phrase());
387/// assert_eq!(response.status_code(), StatusCode::Trying.as_u16());
388/// },
389/// TransactionEvent::TransportError { transaction_id, .. } if transaction_id == tx_id => {
390/// eprintln!("Transport error for transaction {}", transaction_id);
391/// return Err("Transport error".into());
392/// },
393/// TransactionEvent::TransactionTimeout { transaction_id, .. } if transaction_id == tx_id => {
394/// eprintln!("Transaction {} timed out", transaction_id);
395/// return Err("Transaction timeout".into());
396/// },
397/// other_event => {
398/// eprintln!("Received unexpected event: {:?}", other_event);
399/// return Err("Unexpected event".into());
400/// }
401/// }
402/// },
403/// _ = tokio::time::sleep(Duration::from_secs(2)) => {
404/// eprintln!("Timeout waiting for transaction event");
405/// return Err("Timeout waiting for event".into());
406/// }
407/// }
408///
409/// manager.shutdown().await;
410/// Ok(())
411/// }
412/// ```
413///
414/// ### 2. Basic Server Transaction
415///
416/// ```
417/// use rvoip_transaction_core::{TransactionManager, TransactionEvent};
418/// use rvoip_transaction_core::builders::{client_quick, server_quick};
419/// use rvoip_transaction_core::transport::{TransportManager, TransportManagerConfig};
420/// use rvoip_sip_core::{Method, StatusCode};
421/// use std::net::SocketAddr;
422/// use std::time::Duration;
423///
424/// #[tokio::main]
425/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
426/// // Create server transport
427/// let server_config = TransportManagerConfig {
428/// enable_udp: true,
429/// bind_addresses: vec!["127.0.0.1:5060".parse()?],
430/// ..Default::default()
431/// };
432///
433/// let (mut server_transport, server_transport_rx) = TransportManager::new(server_config).await?;
434/// server_transport.initialize().await?;
435/// let server_addr = server_transport.default_transport().await
436/// .ok_or("No default transport")?.local_addr()?;
437///
438/// // Create transaction manager
439/// let (server_tm, mut server_events) = TransactionManager::with_transport_manager(
440/// server_transport,
441/// server_transport_rx,
442/// Some(10),
443/// ).await?;
444///
445/// // Clone for use in spawn (to avoid ownership issues)
446/// let server_tm_clone = server_tm.clone();
447///
448/// // Handle incoming requests
449/// tokio::spawn(async move {
450/// while let Some(event) = server_events.recv().await {
451/// match event {
452/// TransactionEvent::NonInviteRequest { transaction_id, request, source, .. } => {
453/// println!("Received {} from {}", request.method(), source);
454///
455/// // Send appropriate response using builders
456/// match request.method() {
457/// Method::Register => {
458/// let ok = server_quick::ok_register(
459/// &request,
460/// 3600,
461/// vec![format!("sip:user@{}", source.ip())]
462/// ).expect("Failed to create REGISTER response");
463///
464/// let _ = server_tm_clone.send_response(&transaction_id, ok).await;
465/// },
466/// Method::Options => {
467/// let ok = server_quick::ok_options(
468/// &request,
469/// vec![Method::Invite, Method::Register, Method::Options]
470/// ).expect("Failed to create OPTIONS response");
471///
472/// let _ = server_tm_clone.send_response(&transaction_id, ok).await;
473/// },
474/// _ => {
475/// let ok = server_quick::ok_bye(&request)
476/// .expect("Failed to create OK response");
477/// let _ = server_tm_clone.send_response(&transaction_id, ok).await;
478/// }
479/// }
480/// },
481/// TransactionEvent::InviteRequest { transaction_id, request, source, .. } => {
482/// println!("Received INVITE from {}", source);
483///
484/// // Send INVITE response
485/// let ok = server_quick::ok_invite(
486/// &request,
487/// Some("v=0\r\no=server 456 789 IN IP4 127.0.0.1\r\n...".to_string()),
488/// format!("sip:server@{}", source.ip())
489/// ).expect("Failed to create INVITE response");
490///
491/// let _ = server_tm_clone.send_response(&transaction_id, ok).await;
492/// },
493/// _ => {}
494/// }
495/// }
496/// });
497///
498/// // Keep server running for a bit
499/// tokio::time::sleep(Duration::from_millis(100)).await;
500/// server_tm.shutdown().await;
501/// Ok(())
502/// }
503/// ```
504///
505/// ### 3. Client INVITE with ACK Handling
506///
507/// This example demonstrates a client INVITE transaction, receiving a 2xx response,
508/// and then the Transaction User (TU) constructing and sending an ACK.
509///
510/// ```
511/// use rvoip_transaction_core::{TransactionManager, TransactionEvent};
512/// use rvoip_transaction_core::builders::client_quick;
513/// use rvoip_transaction_core::transport::{TransportManager, TransportManagerConfig};
514/// use rvoip_sip_core::{Method, StatusCode};
515/// use std::net::SocketAddr;
516/// use std::time::Duration;
517///
518/// #[tokio::main]
519/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
520/// // Create client transport
521/// let client_config = TransportManagerConfig {
522/// enable_udp: true,
523/// bind_addresses: vec!["127.0.0.1:0".parse()?], // Ephemeral port
524/// ..Default::default()
525/// };
526///
527/// let (mut client_transport, client_transport_rx) = TransportManager::new(client_config).await?;
528/// client_transport.initialize().await?;
529/// let client_addr = client_transport.default_transport().await
530/// .ok_or("No default transport")?.local_addr()?;
531///
532/// // Create transaction manager
533/// let (client_tm, mut client_events) = TransactionManager::with_transport_manager(
534/// client_transport,
535/// client_transport_rx,
536/// Some(10),
537/// ).await?;
538///
539/// // Create INVITE using builders
540/// let server_addr: SocketAddr = "127.0.0.1:5060".parse()?;
541/// let invite_request = client_quick::invite(
542/// "sip:alice@example.com",
543/// "sip:bob@example.com",
544/// client_addr,
545/// Some("v=0\r\no=alice 123 456 IN IP4 127.0.0.1\r\n")
546/// ).expect("Failed to create INVITE");
547///
548/// // Create client transaction
549/// let tx_id = client_tm.create_client_transaction(invite_request, server_addr).await?;
550/// println!("Created INVITE transaction: {}", tx_id);
551///
552/// // Send the INVITE
553/// client_tm.send_request(&tx_id).await?;
554/// println!("Sent INVITE request");
555///
556/// // Handle events
557/// tokio::spawn(async move {
558/// while let Some(event) = client_events.recv().await {
559/// match event {
560/// TransactionEvent::SuccessResponse { transaction_id, response, .. } => {
561/// println!("Received {} {}", response.status_code(), response.reason_phrase());
562///
563/// // For 2xx responses to INVITE, the TU must send ACK
564/// // (This would normally be done by the dialog layer)
565/// if response.status_code() >= 200 && response.status_code() < 300 {
566/// println!("Would send ACK for 2xx response (handled by TU/Dialog layer)");
567/// }
568/// },
569/// TransactionEvent::FailureResponse { transaction_id, response } => {
570/// println!("Received error: {} {}", response.status_code(), response.reason_phrase());
571/// // ACK for non-2xx responses is handled automatically by transaction layer
572/// },
573/// TransactionEvent::StateChanged { transaction_id, new_state, .. } => {
574/// println!("Transaction {} state: {:?}", transaction_id, new_state);
575/// },
576/// _ => {}
577/// }
578/// }
579/// });
580///
581/// // Keep client running for a bit
582/// tokio::time::sleep(Duration::from_millis(100)).await;
583/// client_tm.shutdown().await;
584/// Ok(())
585/// }
586/// ```
587///
588/// ## Transactional vs. Non-Transactional Behavior
589///
590/// SIP distinguishes between:
591///
592/// 1. **Transactional Messages**: Initial requests and their responses
593/// - Handled by this transaction layer
594/// - Examples: REGISTER, INVITE, BYE, etc.
595///
596/// 2. **Non-Transactional Messages**: Messages that don't create a transaction
597/// - ACK for 2xx responses (separate from the INVITE transaction)
598/// - CANCEL (creates its own transaction but refers to another)
599/// - In-dialog requests (handled at the dialog layer, but still use transactions)
600///
601/// ## RFC 3261 Compliance
602///
603/// This implementation follows the transaction state machines defined in RFC 3261:
604///
605/// * Section 17.1.1: INVITE client transactions
606/// * Section 17.1.2: Non-INVITE client transactions
607/// * Section 17.2.1: INVITE server transactions
608/// * Section 17.2.2: Non-INVITE server transactions
609///
610/// ## Error Handling
611///
612/// The library provides a comprehensive error system via the [`Error`] type, enabling
613/// detailed error information propagation for various failure scenarios. The transaction
614/// layer focuses on handling protocol-level errors such as timeouts, transport failures,
615/// and improper message sequences.
616/// # Example
617///
618/// ```
619/// use rvoip_transaction_core::prelude::*;
620/// use rvoip_transaction_core::transaction::AtomicTransactionState;
621/// use std::time::Duration;
622/// ```
623pub mod prelude {
624 pub use crate::transaction::{TransactionKey, TransactionEvent, TransactionState, TransactionKind};
625 pub use crate::manager::TransactionManager;
626 pub use rvoip_sip_transport::transport::TransportType;
627 pub use crate::transport::{
628 TransportCapabilities, TransportInfo,
629 NetworkInfoForSdp, WebSocketStatus, TransportCapabilitiesExt
630 };
631 pub use crate::error::{Error, Result};
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637 use crate::transaction::AtomicTransactionState;
638 use std::net::SocketAddr;
639 use std::str::FromStr;
640 use std::sync::Arc;
641 use tokio::sync::mpsc;
642 use std::time::Duration;
643
644 /// Mock Transport implementation for testing
645 #[derive(Debug)]
646 struct MockTransport {
647 local_addr: SocketAddr,
648 }
649
650 impl MockTransport {
651 fn new(addr: &str) -> Self {
652 Self {
653 local_addr: SocketAddr::from_str(addr).unwrap(),
654 }
655 }
656 }
657
658 #[async_trait::async_trait]
659 impl rvoip_sip_transport::Transport for MockTransport {
660 async fn send_message(
661 &self,
662 _message: rvoip_sip_core::Message,
663 _destination: SocketAddr,
664 ) -> std::result::Result<(), rvoip_sip_transport::error::Error> {
665 Ok(()) // Just pretend we sent it
666 }
667
668 fn local_addr(&self) -> std::result::Result<SocketAddr, rvoip_sip_transport::error::Error> {
669 Ok(self.local_addr)
670 }
671
672 async fn close(&self) -> std::result::Result<(), rvoip_sip_transport::error::Error> {
673 Ok(())
674 }
675
676 fn is_closed(&self) -> bool {
677 false
678 }
679 }
680
681 #[tokio::test]
682 async fn test_transaction_manager_creation() {
683 let transport = Arc::new(MockTransport::new("127.0.0.1:5060"));
684 let (_, transport_rx) = mpsc::channel(10);
685
686 let result = TransactionManager::new(
687 transport,
688 transport_rx,
689 Some(100)
690 ).await;
691
692 assert!(result.is_ok(), "Should create TransactionManager without error");
693 }
694
695 #[tokio::test]
696 async fn test_transaction_key_creation() {
697 let key = TransactionKey::new(
698 "z9hG4bK1234".to_string(),
699 rvoip_sip_core::Method::Invite,
700 false
701 );
702
703 assert_eq!(key.branch, "z9hG4bK1234");
704 assert_eq!(key.method, rvoip_sip_core::Method::Invite);
705 assert_eq!(key.is_server, false);
706 }
707
708 #[tokio::test]
709 async fn test_transaction_state_transitions() {
710 // Instead of comparing with <, verify the state machine flow by checking
711 // that states are different and represent the correct sequence
712 let initial = TransactionState::Initial;
713 let calling = TransactionState::Calling;
714 let proceeding = TransactionState::Proceeding;
715 let completed = TransactionState::Completed;
716 let terminated = TransactionState::Terminated;
717
718 // Verify states are different
719 assert_ne!(initial, calling);
720 assert_ne!(calling, proceeding);
721 assert_ne!(proceeding, completed);
722 assert_ne!(completed, terminated);
723
724 // Verify some valid transitions for InviteClient
725 assert!(AtomicTransactionState::validate_transition(TransactionKind::InviteClient, initial, calling).is_ok());
726 assert!(AtomicTransactionState::validate_transition(TransactionKind::InviteClient, calling, proceeding).is_ok());
727 assert!(AtomicTransactionState::validate_transition(TransactionKind::InviteClient, proceeding, completed).is_ok());
728 assert!(AtomicTransactionState::validate_transition(TransactionKind::InviteClient, completed, terminated).is_ok());
729 }
730
731 #[tokio::test]
732 async fn test_timer_settings() {
733 // Test the timer settings defaults
734 let settings = TimerSettings::default();
735
736 assert_eq!(settings.t1, Duration::from_millis(500), "T1 should be 500ms");
737 assert_eq!(settings.t2, Duration::from_secs(4), "T2 should be 4s");
738
739 // Create a custom timer settings
740 let custom_settings = TimerSettings {
741 t1: Duration::from_millis(200),
742 t2: Duration::from_secs(2),
743 t4: Duration::from_secs(5),
744 timer_100_interval: Duration::from_millis(200),
745 transaction_timeout: Duration::from_secs(16),
746 wait_time_d: Duration::from_secs(16),
747 wait_time_h: Duration::from_secs(16),
748 wait_time_i: Duration::from_secs(2),
749 wait_time_j: Duration::from_secs(16),
750 wait_time_k: Duration::from_secs(2),
751 };
752
753 assert_eq!(custom_settings.t1, Duration::from_millis(200));
754 assert_eq!(custom_settings.transaction_timeout, Duration::from_secs(16));
755 }
756}