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;
29
30/// Error returned by [`SipB2bua`] operations.
31#[derive(Debug, thiserror::Error)]
32pub enum B2buaError {
33    /// A session/signalling operation (accept or originate) failed.
34    #[error("session error: {0}")]
35    Session(#[from] crate::errors::SessionError),
36    /// Bridging the two legs failed.
37    #[error("bridge error: {0}")]
38    Bridge(BridgeError),
39}
40
41impl From<BridgeError> for B2buaError {
42    fn from(err: BridgeError) -> Self {
43        B2buaError::Bridge(err)
44    }
45}
46
47/// Convenience B2BUA that wires incoming-INVITE → originate-outbound →
48/// bridge entirely through [`UnifiedCoordinator`].
49#[derive(Clone)]
50pub struct SipB2bua {
51    coordinator: Arc<UnifiedCoordinator>,
52}
53
54impl SipB2bua {
55    /// Create a B2BUA over the given [`UnifiedCoordinator`].
56    pub fn new(coordinator: Arc<UnifiedCoordinator>) -> Self {
57        Self { coordinator }
58    }
59
60    /// Accept the inbound INVITE on `incoming`, originate an outbound leg to
61    /// `target_uri` from `from_uri`, then bridge the two. Returns the
62    /// resulting [`BridgeHandle`] (drop to tear down).
63    pub async fn handle_inbound(
64        &self,
65        from_uri: &str,
66        incoming: &SessionId,
67        target_uri: &str,
68    ) -> Result<BridgeHandle, B2buaError> {
69        self.coordinator.accept_call(incoming).await?;
70        let outbound = self
71            .coordinator
72            .invite(Some(from_uri.to_string()), target_uri.to_string())
73            .send()
74            .await?;
75        let handle = sip_bridge(&self.coordinator, incoming, &outbound).await?;
76        Ok(handle)
77    }
78}