feat(fax): add fax routing, job tracking, inbox management, and T.38/UDPTL media support
This commit is contained in:
@@ -11,7 +11,7 @@ use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::{mpsc, watch};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub type LegId = String;
|
||||
@@ -114,6 +114,13 @@ pub struct LegInfo {
|
||||
pub kind: LegKind,
|
||||
pub state: LegState,
|
||||
pub codec_pt: u8,
|
||||
/// Media transport currently negotiated for this leg.
|
||||
///
|
||||
/// `rtp` covers classic SIP audio media, `t38-udptl` covers T.38 fax,
|
||||
/// `webrtc` is used for browser legs, and `internal` for proxy-local media/tool paths.
|
||||
pub media_protocol: &'static str,
|
||||
/// Whether this leg is currently wired into an active media bridge.
|
||||
pub media_io_active: bool,
|
||||
|
||||
/// For SIP legs: the SIP dialog manager (handles 407 auth, BYE, etc).
|
||||
pub sip_leg: Option<SipLeg>,
|
||||
@@ -146,6 +153,15 @@ pub struct LegInfo {
|
||||
pub metadata: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PendingDialogBridge {
|
||||
pub source_leg_id: LegId,
|
||||
pub target_leg_id: LegId,
|
||||
pub source_request: SipMessage,
|
||||
pub target_request: SipMessage,
|
||||
pub method: String,
|
||||
}
|
||||
|
||||
/// A multiparty call with N legs and a central mixer.
|
||||
pub struct Call {
|
||||
// Duplicated from the HashMap key in CallManager. Kept for future
|
||||
@@ -169,12 +185,21 @@ pub struct Call {
|
||||
/// Used to construct proper 180/200/error responses back to the device.
|
||||
pub device_invite: Option<SipMessage>,
|
||||
|
||||
/// Pending in-dialog B2BUA transaction bridged across two different SIP dialogs.
|
||||
pub pending_dialog_bridge: Option<PendingDialogBridge>,
|
||||
|
||||
/// All legs in this call, keyed by leg ID.
|
||||
pub legs: HashMap<LegId, LegInfo>,
|
||||
|
||||
/// Channel to send commands to the mixer task.
|
||||
pub mixer_cmd_tx: mpsc::Sender<MixerCommand>,
|
||||
|
||||
/// Active passthrough media bridge mode, if any.
|
||||
pub media_bridge_mode: Option<String>,
|
||||
|
||||
/// Cancellation handles for non-mixer passthrough media tasks.
|
||||
media_bridge_cancel_txs: Vec<watch::Sender<bool>>,
|
||||
|
||||
/// Handle to the mixer task (aborted on call teardown).
|
||||
mixer_task: Option<JoinHandle<()>>,
|
||||
}
|
||||
@@ -196,8 +221,11 @@ impl Call {
|
||||
callee_number: None,
|
||||
provider_id,
|
||||
device_invite: None,
|
||||
pending_dialog_bridge: None,
|
||||
legs: HashMap::new(),
|
||||
mixer_cmd_tx,
|
||||
media_bridge_mode: None,
|
||||
media_bridge_cancel_txs: Vec::new(),
|
||||
mixer_task: Some(mixer_task),
|
||||
}
|
||||
}
|
||||
@@ -235,8 +263,31 @@ impl Call {
|
||||
self.created_at.elapsed().as_secs()
|
||||
}
|
||||
|
||||
pub fn clear_media_bridge(&mut self) {
|
||||
for cancel_tx in self.media_bridge_cancel_txs.drain(..) {
|
||||
let _ = cancel_tx.send(true);
|
||||
}
|
||||
self.media_bridge_mode = None;
|
||||
}
|
||||
|
||||
pub fn install_media_bridge(
|
||||
&mut self,
|
||||
mode: &str,
|
||||
cancel_txs: Vec<watch::Sender<bool>>,
|
||||
) {
|
||||
self.clear_media_bridge();
|
||||
self.media_bridge_mode = Some(mode.to_string());
|
||||
self.media_bridge_cancel_txs = cancel_txs;
|
||||
}
|
||||
|
||||
pub fn note_mixer_bridge(&mut self, mode: &str) {
|
||||
self.clear_media_bridge();
|
||||
self.media_bridge_mode = Some(mode.to_string());
|
||||
}
|
||||
|
||||
/// Shut down the mixer and abort its task.
|
||||
pub async fn shutdown_mixer(&mut self) {
|
||||
self.clear_media_bridge();
|
||||
let _ = self.mixer_cmd_tx.send(MixerCommand::Shutdown).await;
|
||||
if let Some(handle) = self.mixer_task.take() {
|
||||
handle.abort();
|
||||
|
||||
Reference in New Issue
Block a user