Skip to main content

rvoip_sip/server/
b2bua.rs

1//! Optional B2BUA convenience: wires the canonical incoming-INVITE →
2//! originate-outbound → bridge pattern entirely through
3//! [`UnifiedCoordinator`].
4//!
5//! Per CARVE_PLAN §5: validates that `server::*` stands on its own — a
6//! SIP-only consumer can use rvoip-sip without `rvoip-core` involvement by
7//! composing api/ calls.
8//!
9//! ```rust,no_run
10//! use rvoip_sip::server::b2bua::SipB2bua;
11//! use rvoip_sip::SessionId;
12//!
13//! # async fn example(
14//! #     coordinator: std::sync::Arc<rvoip_sip::UnifiedCoordinator>,
15//! #     incoming_session_id: SessionId,
16//! # ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
17//! let b2bua = SipB2bua::new(coordinator);
18//! let _bridge = b2bua
19//!     .handle_inbound("sip:gw@example.com", &incoming_session_id, "sip:bob@upstream.example.net")
20//!     .await?;
21//! # Ok(())
22//! # }
23//! ```
24
25use crate::api::unified::{BridgeError, BridgeHandle, UnifiedCoordinator};
26use crate::server::bridge::sip_bridge;
27use crate::SessionId;
28use std::sync::Arc;
29use std::time::Duration;
30
31const OUTBOUND_ANSWER_TIMEOUT: Duration = Duration::from_secs(30);
32
33/// Error returned by [`SipB2bua`] operations.
34#[derive(Debug, thiserror::Error)]
35pub enum B2buaError {
36    /// A session/signalling operation (accept or originate) failed.
37    #[error("session error: {0}")]
38    Session(#[from] crate::errors::SessionError),
39    /// Bridging the two legs failed.
40    #[error("bridge error: {0}")]
41    Bridge(BridgeError),
42}
43
44impl From<BridgeError> for B2buaError {
45    fn from(err: BridgeError) -> Self {
46        B2buaError::Bridge(err)
47    }
48}
49
50/// Convenience B2BUA that wires incoming-INVITE → originate-outbound →
51/// bridge entirely through [`UnifiedCoordinator`].
52#[derive(Clone)]
53pub struct SipB2bua {
54    coordinator: Arc<UnifiedCoordinator>,
55}
56
57impl SipB2bua {
58    /// Create a B2BUA over the given [`UnifiedCoordinator`].
59    pub fn new(coordinator: Arc<UnifiedCoordinator>) -> Self {
60        Self { coordinator }
61    }
62
63    /// Accept the inbound INVITE on `incoming`, originate an outbound leg to
64    /// `target_uri` from `from_uri`, then bridge the two. Returns the
65    /// resulting [`BridgeHandle`] (drop to tear down).
66    pub async fn handle_inbound(
67        &self,
68        from_uri: &str,
69        incoming: &SessionId,
70        target_uri: &str,
71    ) -> Result<BridgeHandle, B2buaError> {
72        self.coordinator.accept_call(incoming).await?;
73        let outbound = self
74            .coordinator
75            .invite(Some(from_uri.to_string()), target_uri.to_string())
76            .send()
77            .await?;
78        self.coordinator
79            .session(&outbound)
80            .wait_for_answered(Some(OUTBOUND_ANSWER_TIMEOUT))
81            .await?;
82        let handle = sip_bridge(&self.coordinator, incoming, &outbound).await?;
83        Ok(handle)
84    }
85}