104 lines
2.6 KiB
Rust
104 lines
2.6 KiB
Rust
|
|
//! Call hub — owns legs and bridges media.
|
||
|
|
//!
|
||
|
|
//! Each Call has a unique ID and tracks its state, direction, and associated
|
||
|
|
//! SIP Call-IDs for message routing.
|
||
|
|
|
||
|
|
use std::net::SocketAddr;
|
||
|
|
use std::sync::Arc;
|
||
|
|
use std::time::Instant;
|
||
|
|
use tokio::net::UdpSocket;
|
||
|
|
|
||
|
|
/// Call state machine.
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum CallState {
|
||
|
|
SettingUp,
|
||
|
|
Ringing,
|
||
|
|
Connected,
|
||
|
|
Voicemail,
|
||
|
|
Ivr,
|
||
|
|
Terminating,
|
||
|
|
Terminated,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl CallState {
|
||
|
|
pub fn as_str(&self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
Self::SettingUp => "setting-up",
|
||
|
|
Self::Ringing => "ringing",
|
||
|
|
Self::Connected => "connected",
|
||
|
|
Self::Voicemail => "voicemail",
|
||
|
|
Self::Ivr => "ivr",
|
||
|
|
Self::Terminating => "terminating",
|
||
|
|
Self::Terminated => "terminated",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
|
pub enum CallDirection {
|
||
|
|
Inbound,
|
||
|
|
Outbound,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl CallDirection {
|
||
|
|
pub fn as_str(&self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
Self::Inbound => "inbound",
|
||
|
|
Self::Outbound => "outbound",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A passthrough call — both sides share the same SIP Call-ID.
|
||
|
|
/// The proxy rewrites SDP/Contact/Request-URI and relays RTP.
|
||
|
|
pub struct PassthroughCall {
|
||
|
|
pub id: String,
|
||
|
|
pub sip_call_id: String,
|
||
|
|
pub state: CallState,
|
||
|
|
pub direction: CallDirection,
|
||
|
|
pub created_at: Instant,
|
||
|
|
|
||
|
|
// Call metadata.
|
||
|
|
pub caller_number: Option<String>,
|
||
|
|
pub callee_number: Option<String>,
|
||
|
|
pub provider_id: String,
|
||
|
|
|
||
|
|
// Provider side.
|
||
|
|
pub provider_addr: SocketAddr,
|
||
|
|
pub provider_media: Option<SocketAddr>,
|
||
|
|
|
||
|
|
// Device side.
|
||
|
|
pub device_addr: SocketAddr,
|
||
|
|
pub device_media: Option<SocketAddr>,
|
||
|
|
|
||
|
|
// RTP relay.
|
||
|
|
pub rtp_port: u16,
|
||
|
|
pub rtp_socket: Arc<UdpSocket>,
|
||
|
|
|
||
|
|
// Packet counters.
|
||
|
|
pub pkt_from_device: u64,
|
||
|
|
pub pkt_from_provider: u64,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl PassthroughCall {
|
||
|
|
pub fn duration_secs(&self) -> u64 {
|
||
|
|
self.created_at.elapsed().as_secs()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn to_status_json(&self) -> serde_json::Value {
|
||
|
|
serde_json::json!({
|
||
|
|
"id": self.id,
|
||
|
|
"state": self.state.as_str(),
|
||
|
|
"direction": self.direction.as_str(),
|
||
|
|
"callerNumber": self.caller_number,
|
||
|
|
"calleeNumber": self.callee_number,
|
||
|
|
"providerUsed": self.provider_id,
|
||
|
|
"createdAt": self.created_at.elapsed().as_millis(),
|
||
|
|
"duration": self.duration_secs(),
|
||
|
|
"rtpPort": self.rtp_port,
|
||
|
|
"pktFromDevice": self.pkt_from_device,
|
||
|
|
"pktFromProvider": self.pkt_from_provider,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|