feat(routing): require explicit inbound DID routes and normalize SIP identities for provider-based number matching
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
/// (incoming calls, registration state).
|
||||
///
|
||||
/// No raw SIP ever touches TypeScript.
|
||||
|
||||
mod audio_player;
|
||||
mod call;
|
||||
mod call_manager;
|
||||
@@ -26,7 +25,7 @@ mod voicemail;
|
||||
mod webrtc_engine;
|
||||
|
||||
use crate::call_manager::CallManager;
|
||||
use crate::config::AppConfig;
|
||||
use crate::config::{normalize_routing_identity, AppConfig};
|
||||
use crate::ipc::{emit_event, respond_err, respond_ok, Command, OutTx};
|
||||
use crate::provider::ProviderManager;
|
||||
use crate::registrar::Registrar;
|
||||
@@ -266,11 +265,7 @@ async fn handle_sip_packet(
|
||||
}
|
||||
|
||||
// 2. Device REGISTER — handled by registrar.
|
||||
let is_from_provider = eng
|
||||
.provider_mgr
|
||||
.find_by_address(&from_addr)
|
||||
.await
|
||||
.is_some();
|
||||
let is_from_provider = eng.provider_mgr.find_by_address(&from_addr).await.is_some();
|
||||
|
||||
if !is_from_provider && msg.method() == Some("REGISTER") {
|
||||
if let Some(response_buf) = eng.registrar.handle_register(&msg, from_addr) {
|
||||
@@ -349,11 +344,8 @@ async fn handle_sip_packet(
|
||||
if let Some(inbound) = inbound {
|
||||
// Emit event so TypeScript knows about the call (for dashboard, IVR routing, etc).
|
||||
let from_header = msg.get_header("From").unwrap_or("");
|
||||
let from_uri = SipMessage::extract_uri(from_header).unwrap_or("Unknown");
|
||||
let called_number = msg
|
||||
.request_uri()
|
||||
.and_then(|uri| SipMessage::extract_uri(uri))
|
||||
.unwrap_or("");
|
||||
let from_uri = normalize_routing_identity(from_header);
|
||||
let called_number = normalize_routing_identity(msg.request_uri().unwrap_or(""));
|
||||
|
||||
emit_event(
|
||||
&eng.out_tx,
|
||||
@@ -373,11 +365,7 @@ async fn handle_sip_packet(
|
||||
// 5. New outbound INVITE from device.
|
||||
if !is_from_provider && msg.is_request() && msg.method() == Some("INVITE") {
|
||||
// Resolve outbound route.
|
||||
let dialed_number = msg
|
||||
.request_uri()
|
||||
.and_then(|uri| SipMessage::extract_uri(uri))
|
||||
.unwrap_or(msg.request_uri().unwrap_or(""))
|
||||
.to_string();
|
||||
let dialed_number = normalize_routing_identity(msg.request_uri().unwrap_or(""));
|
||||
|
||||
let device = eng.registrar.find_by_address(&from_addr);
|
||||
let device_id = device.map(|d| d.device_id.clone());
|
||||
@@ -395,13 +383,18 @@ async fn handle_sip_packet(
|
||||
|
||||
if let Some(route) = route_result {
|
||||
// Look up provider state by config ID (not by device address).
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) =
|
||||
eng.provider_mgr.find_by_provider_id(&route.provider.id).await
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) = eng
|
||||
.provider_mgr
|
||||
.find_by_provider_id(&route.provider.id)
|
||||
.await
|
||||
{
|
||||
let ps = ps_arc.lock().await;
|
||||
(ps.public_ip.clone(), ps.registered_aor.clone())
|
||||
} else {
|
||||
(None, format!("sip:{}@{}", route.provider.username, route.provider.domain))
|
||||
(
|
||||
None,
|
||||
format!("sip:{}@{}", route.provider.username, route.provider.domain),
|
||||
)
|
||||
};
|
||||
|
||||
let ProxyEngine {
|
||||
@@ -461,14 +454,20 @@ async fn handle_sip_packet(
|
||||
async fn handle_make_call(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let number = match cmd.params.get("number").and_then(|v| v.as_str()) {
|
||||
Some(n) => n.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing number"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing number");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let provider_id = cmd.params.get("provider_id").and_then(|v| v.as_str());
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
let config_ref = match &eng.config {
|
||||
Some(c) => c.clone(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not configured"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not configured");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve provider.
|
||||
@@ -482,49 +481,82 @@ async fn handle_make_call(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd:
|
||||
|
||||
let provider_config = match provider_config {
|
||||
Some(p) => p,
|
||||
None => { respond_err(out_tx, &cmd.id, "no provider available"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "no provider available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Get public IP and registered AOR from provider state.
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) = eng.provider_mgr.find_by_address(
|
||||
&provider_config.outbound_proxy.to_socket_addr().unwrap_or_else(|| "0.0.0.0:0".parse().unwrap())
|
||||
).await {
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) = eng
|
||||
.provider_mgr
|
||||
.find_by_address(
|
||||
&provider_config
|
||||
.outbound_proxy
|
||||
.to_socket_addr()
|
||||
.unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let ps = ps_arc.lock().await;
|
||||
(ps.public_ip.clone(), ps.registered_aor.clone())
|
||||
} else {
|
||||
// Fallback — construct AOR from config.
|
||||
(None, format!("sip:{}@{}", provider_config.username, provider_config.domain))
|
||||
(
|
||||
None,
|
||||
format!(
|
||||
"sip:{}@{}",
|
||||
provider_config.username, provider_config.domain
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let socket = match &eng.transport {
|
||||
Some(t) => t.socket(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not initialized"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let ProxyEngine { ref mut call_mgr, ref mut rtp_pool, .. } = *eng;
|
||||
let ProxyEngine {
|
||||
ref mut call_mgr,
|
||||
ref mut rtp_pool,
|
||||
..
|
||||
} = *eng;
|
||||
let rtp_pool = rtp_pool.as_mut().unwrap();
|
||||
|
||||
let call_id = call_mgr.make_outbound_call(
|
||||
&number,
|
||||
&provider_config,
|
||||
&config_ref,
|
||||
rtp_pool,
|
||||
&socket,
|
||||
public_ip.as_deref(),
|
||||
®istered_aor,
|
||||
).await;
|
||||
let call_id = call_mgr
|
||||
.make_outbound_call(
|
||||
&number,
|
||||
&provider_config,
|
||||
&config_ref,
|
||||
rtp_pool,
|
||||
&socket,
|
||||
public_ip.as_deref(),
|
||||
®istered_aor,
|
||||
)
|
||||
.await;
|
||||
|
||||
match call_id {
|
||||
Some(id) => {
|
||||
emit_event(out_tx, "outbound_call_started", serde_json::json!({
|
||||
"call_id": id,
|
||||
"number": number,
|
||||
"provider_id": provider_config.id,
|
||||
}));
|
||||
emit_event(
|
||||
out_tx,
|
||||
"outbound_call_started",
|
||||
serde_json::json!({
|
||||
"call_id": id,
|
||||
"number": number,
|
||||
"provider_id": provider_config.id,
|
||||
}),
|
||||
);
|
||||
respond_ok(out_tx, &cmd.id, serde_json::json!({ "call_id": id }));
|
||||
}
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "call origination failed — provider not registered or no ports available");
|
||||
respond_err(
|
||||
out_tx,
|
||||
&cmd.id,
|
||||
"call origination failed — provider not registered or no ports available",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,20 +592,30 @@ async fn handle_hangup(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Co
|
||||
async fn handle_webrtc_offer(webrtc: Arc<Mutex<WebRtcEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let session_id = match cmd.params.get("session_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing session_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing session_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let offer_sdp = match cmd.params.get("sdp").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing sdp"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing sdp");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut wrtc = webrtc.lock().await;
|
||||
match wrtc.handle_offer(&session_id, &offer_sdp).await {
|
||||
Ok(answer_sdp) => {
|
||||
respond_ok(out_tx, &cmd.id, serde_json::json!({
|
||||
"session_id": session_id,
|
||||
"sdp": answer_sdp,
|
||||
}));
|
||||
respond_ok(
|
||||
out_tx,
|
||||
&cmd.id,
|
||||
serde_json::json!({
|
||||
"session_id": session_id,
|
||||
"sdp": answer_sdp,
|
||||
}),
|
||||
);
|
||||
}
|
||||
Err(e) => respond_err(out_tx, &cmd.id, &e),
|
||||
}
|
||||
@@ -584,14 +626,28 @@ async fn handle_webrtc_offer(webrtc: Arc<Mutex<WebRtcEngine>>, out_tx: &OutTx, c
|
||||
async fn handle_webrtc_ice(webrtc: Arc<Mutex<WebRtcEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let session_id = match cmd.params.get("session_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing session_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing session_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let candidate = cmd.params.get("candidate").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let candidate = cmd
|
||||
.params
|
||||
.get("candidate")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
let sdp_mid = cmd.params.get("sdp_mid").and_then(|v| v.as_str());
|
||||
let sdp_mline_index = cmd.params.get("sdp_mline_index").and_then(|v| v.as_u64()).map(|v| v as u16);
|
||||
let sdp_mline_index = cmd
|
||||
.params
|
||||
.get("sdp_mline_index")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v as u16);
|
||||
|
||||
let wrtc = webrtc.lock().await;
|
||||
match wrtc.add_ice_candidate(&session_id, candidate, sdp_mid, sdp_mline_index).await {
|
||||
match wrtc
|
||||
.add_ice_candidate(&session_id, candidate, sdp_mid, sdp_mline_index)
|
||||
.await
|
||||
{
|
||||
Ok(()) => respond_ok(out_tx, &cmd.id, serde_json::json!({})),
|
||||
Err(e) => respond_err(out_tx, &cmd.id, &e),
|
||||
}
|
||||
@@ -608,11 +664,17 @@ async fn handle_webrtc_link(
|
||||
) {
|
||||
let session_id = match cmd.params.get("session_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing session_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing session_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Create channels for the WebRTC leg.
|
||||
@@ -641,7 +703,12 @@ async fn handle_webrtc_link(
|
||||
// Lock webrtc to wire the channels.
|
||||
let mut wrtc = webrtc.lock().await;
|
||||
if wrtc
|
||||
.link_to_mixer(&session_id, &call_id, channels.inbound_tx, channels.outbound_rx)
|
||||
.link_to_mixer(
|
||||
&session_id,
|
||||
&call_id,
|
||||
channels.inbound_tx,
|
||||
channels.outbound_rx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// Also store the WebRTC leg info in the call.
|
||||
@@ -670,22 +737,30 @@ async fn handle_webrtc_link(
|
||||
}
|
||||
}
|
||||
|
||||
emit_event(out_tx, "leg_added", serde_json::json!({
|
||||
"call_id": call_id,
|
||||
"leg_id": session_id,
|
||||
"kind": "webrtc",
|
||||
"state": "connected",
|
||||
"codec": "Opus",
|
||||
"rtpPort": 0,
|
||||
"remoteMedia": null,
|
||||
"metadata": {},
|
||||
}));
|
||||
emit_event(
|
||||
out_tx,
|
||||
"leg_added",
|
||||
serde_json::json!({
|
||||
"call_id": call_id,
|
||||
"leg_id": session_id,
|
||||
"kind": "webrtc",
|
||||
"state": "connected",
|
||||
"codec": "Opus",
|
||||
"rtpPort": 0,
|
||||
"remoteMedia": null,
|
||||
"metadata": {},
|
||||
}),
|
||||
);
|
||||
|
||||
respond_ok(out_tx, &cmd.id, serde_json::json!({
|
||||
"session_id": session_id,
|
||||
"call_id": call_id,
|
||||
"bridged": true,
|
||||
}));
|
||||
respond_ok(
|
||||
out_tx,
|
||||
&cmd.id,
|
||||
serde_json::json!({
|
||||
"session_id": session_id,
|
||||
"call_id": call_id,
|
||||
"bridged": true,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
respond_err(out_tx, &cmd.id, &format!("session {session_id} not found"));
|
||||
}
|
||||
@@ -695,45 +770,76 @@ async fn handle_webrtc_link(
|
||||
async fn handle_add_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let number = match cmd.params.get("number").and_then(|v| v.as_str()) {
|
||||
Some(n) => n.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing number"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing number");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let provider_id = cmd.params.get("provider_id").and_then(|v| v.as_str());
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
let config_ref = match &eng.config {
|
||||
Some(c) => c.clone(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not configured"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not configured");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve provider.
|
||||
let provider_config = if let Some(pid) = provider_id {
|
||||
config_ref.providers.iter().find(|p| p.id == pid).cloned()
|
||||
} else {
|
||||
config_ref.resolve_outbound_route(&number, None, &|_| true).map(|r| r.provider)
|
||||
config_ref
|
||||
.resolve_outbound_route(&number, None, &|_| true)
|
||||
.map(|r| r.provider)
|
||||
};
|
||||
|
||||
let provider_config = match provider_config {
|
||||
Some(p) => p,
|
||||
None => { respond_err(out_tx, &cmd.id, "no provider available"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "no provider available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Get registered AOR.
|
||||
let registered_aor = if let Some(ps_arc) = eng.provider_mgr.find_by_address(
|
||||
&provider_config.outbound_proxy.to_socket_addr().unwrap_or_else(|| "0.0.0.0:0".parse().unwrap())
|
||||
).await {
|
||||
let registered_aor = if let Some(ps_arc) = eng
|
||||
.provider_mgr
|
||||
.find_by_address(
|
||||
&provider_config
|
||||
.outbound_proxy
|
||||
.to_socket_addr()
|
||||
.unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let ps = ps_arc.lock().await;
|
||||
ps.registered_aor.clone()
|
||||
} else {
|
||||
format!("sip:{}@{}", provider_config.username, provider_config.domain)
|
||||
format!(
|
||||
"sip:{}@{}",
|
||||
provider_config.username, provider_config.domain
|
||||
)
|
||||
};
|
||||
|
||||
let public_ip = if let Some(ps_arc) = eng.provider_mgr.find_by_address(
|
||||
&provider_config.outbound_proxy.to_socket_addr().unwrap_or_else(|| "0.0.0.0:0".parse().unwrap())
|
||||
).await {
|
||||
let public_ip = if let Some(ps_arc) = eng
|
||||
.provider_mgr
|
||||
.find_by_address(
|
||||
&provider_config
|
||||
.outbound_proxy
|
||||
.to_socket_addr()
|
||||
.unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
let ps = ps_arc.lock().await;
|
||||
ps.public_ip.clone()
|
||||
} else {
|
||||
@@ -742,16 +848,31 @@ async fn handle_add_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &C
|
||||
|
||||
let socket = match &eng.transport {
|
||||
Some(t) => t.socket(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not initialized"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let ProxyEngine { ref mut call_mgr, ref mut rtp_pool, .. } = *eng;
|
||||
let ProxyEngine {
|
||||
ref mut call_mgr,
|
||||
ref mut rtp_pool,
|
||||
..
|
||||
} = *eng;
|
||||
let rtp_pool = rtp_pool.as_mut().unwrap();
|
||||
|
||||
let leg_id = call_mgr.add_external_leg(
|
||||
&call_id, &number, &provider_config, &config_ref,
|
||||
rtp_pool, &socket, public_ip.as_deref(), ®istered_aor,
|
||||
).await;
|
||||
let leg_id = call_mgr
|
||||
.add_external_leg(
|
||||
&call_id,
|
||||
&number,
|
||||
&provider_config,
|
||||
&config_ref,
|
||||
rtp_pool,
|
||||
&socket,
|
||||
public_ip.as_deref(),
|
||||
®istered_aor,
|
||||
)
|
||||
.await;
|
||||
|
||||
match leg_id {
|
||||
Some(lid) => respond_ok(out_tx, &cmd.id, serde_json::json!({ "leg_id": lid })),
|
||||
@@ -763,33 +884,61 @@ async fn handle_add_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &C
|
||||
async fn handle_add_device_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let device_id = match cmd.params.get("device_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing device_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing device_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
let config_ref = match &eng.config {
|
||||
Some(c) => c.clone(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not configured"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not configured");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let socket = match &eng.transport {
|
||||
Some(t) => t.socket(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not initialized"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let ProxyEngine { ref registrar, ref mut call_mgr, ref mut rtp_pool, .. } = *eng;
|
||||
let ProxyEngine {
|
||||
ref registrar,
|
||||
ref mut call_mgr,
|
||||
ref mut rtp_pool,
|
||||
..
|
||||
} = *eng;
|
||||
let rtp_pool = rtp_pool.as_mut().unwrap();
|
||||
|
||||
let leg_id = call_mgr.add_device_leg(
|
||||
&call_id, &device_id, registrar, &config_ref, rtp_pool, &socket,
|
||||
).await;
|
||||
let leg_id = call_mgr
|
||||
.add_device_leg(
|
||||
&call_id,
|
||||
&device_id,
|
||||
registrar,
|
||||
&config_ref,
|
||||
rtp_pool,
|
||||
&socket,
|
||||
)
|
||||
.await;
|
||||
|
||||
match leg_id {
|
||||
Some(lid) => respond_ok(out_tx, &cmd.id, serde_json::json!({ "leg_id": lid })),
|
||||
None => respond_err(out_tx, &cmd.id, "failed to add device leg — device not registered or call not found"),
|
||||
None => respond_err(
|
||||
out_tx,
|
||||
&cmd.id,
|
||||
"failed to add device leg — device not registered or call not found",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,19 +946,32 @@ async fn handle_add_device_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx,
|
||||
async fn handle_transfer_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let source_call_id = match cmd.params.get("source_call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing source_call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing source_call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let leg_id = match cmd.params.get("leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let target_call_id = match cmd.params.get("target_call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing target_call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing target_call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
if eng.call_mgr.transfer_leg(&source_call_id, &leg_id, &target_call_id).await {
|
||||
if eng
|
||||
.call_mgr
|
||||
.transfer_leg(&source_call_id, &leg_id, &target_call_id)
|
||||
.await
|
||||
{
|
||||
respond_ok(out_tx, &cmd.id, serde_json::json!({}));
|
||||
} else {
|
||||
respond_err(out_tx, &cmd.id, "transfer failed — call or leg not found");
|
||||
@@ -820,57 +982,104 @@ async fn handle_transfer_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cm
|
||||
async fn handle_replace_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let old_leg_id = match cmd.params.get("old_leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing old_leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing old_leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let number = match cmd.params.get("number").and_then(|v| v.as_str()) {
|
||||
Some(n) => n.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing number"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing number");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let provider_id = cmd.params.get("provider_id").and_then(|v| v.as_str());
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
let config_ref = match &eng.config {
|
||||
Some(c) => c.clone(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not configured"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not configured");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let socket = match &eng.transport {
|
||||
Some(t) => t.socket(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not initialized"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve provider.
|
||||
let provider_config = if let Some(pid) = provider_id {
|
||||
config_ref.providers.iter().find(|p| p.id == pid).cloned()
|
||||
} else {
|
||||
config_ref.resolve_outbound_route(&number, None, &|_| true).map(|r| r.provider)
|
||||
config_ref
|
||||
.resolve_outbound_route(&number, None, &|_| true)
|
||||
.map(|r| r.provider)
|
||||
};
|
||||
let provider_config = match provider_config {
|
||||
Some(p) => p,
|
||||
None => { respond_err(out_tx, &cmd.id, "no provider available"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "no provider available");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) = eng.provider_mgr.find_by_provider_id(&provider_config.id).await {
|
||||
let (public_ip, registered_aor) = if let Some(ps_arc) = eng
|
||||
.provider_mgr
|
||||
.find_by_provider_id(&provider_config.id)
|
||||
.await
|
||||
{
|
||||
let ps = ps_arc.lock().await;
|
||||
(ps.public_ip.clone(), ps.registered_aor.clone())
|
||||
} else {
|
||||
(None, format!("sip:{}@{}", provider_config.username, provider_config.domain))
|
||||
(
|
||||
None,
|
||||
format!(
|
||||
"sip:{}@{}",
|
||||
provider_config.username, provider_config.domain
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let ProxyEngine { ref mut call_mgr, ref mut rtp_pool, .. } = *eng;
|
||||
let ProxyEngine {
|
||||
ref mut call_mgr,
|
||||
ref mut rtp_pool,
|
||||
..
|
||||
} = *eng;
|
||||
let rtp_pool = rtp_pool.as_mut().unwrap();
|
||||
|
||||
let new_leg_id = call_mgr.replace_leg(
|
||||
&call_id, &old_leg_id, &number, &provider_config, &config_ref,
|
||||
rtp_pool, &socket, public_ip.as_deref(), ®istered_aor,
|
||||
).await;
|
||||
let new_leg_id = call_mgr
|
||||
.replace_leg(
|
||||
&call_id,
|
||||
&old_leg_id,
|
||||
&number,
|
||||
&provider_config,
|
||||
&config_ref,
|
||||
rtp_pool,
|
||||
&socket,
|
||||
public_ip.as_deref(),
|
||||
®istered_aor,
|
||||
)
|
||||
.await;
|
||||
|
||||
match new_leg_id {
|
||||
Some(lid) => respond_ok(out_tx, &cmd.id, serde_json::json!({ "new_leg_id": lid })),
|
||||
None => respond_err(out_tx, &cmd.id, "replace failed — call ended or dial failed"),
|
||||
None => respond_err(
|
||||
out_tx,
|
||||
&cmd.id,
|
||||
"replace failed — call ended or dial failed",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,17 +1087,26 @@ async fn handle_replace_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd
|
||||
async fn handle_remove_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let leg_id = match cmd.params.get("leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
let socket = match &eng.transport {
|
||||
Some(t) => t.socket(),
|
||||
None => { respond_err(out_tx, &cmd.id, "not initialized"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if eng.call_mgr.remove_leg(&call_id, &leg_id, &socket).await {
|
||||
@@ -903,7 +1121,10 @@ async fn handle_remove_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd:
|
||||
async fn handle_webrtc_close(webrtc: Arc<Mutex<WebRtcEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let session_id = match cmd.params.get("session_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing session_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing session_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut wrtc = webrtc.lock().await;
|
||||
@@ -919,22 +1140,27 @@ async fn handle_webrtc_close(webrtc: Arc<Mutex<WebRtcEngine>>, out_tx: &OutTx, c
|
||||
|
||||
/// Handle `start_interaction` — isolate a leg, play a prompt, collect DTMF.
|
||||
/// This command blocks until the interaction completes (digit, timeout, or cancel).
|
||||
async fn handle_start_interaction(
|
||||
engine: Arc<Mutex<ProxyEngine>>,
|
||||
out_tx: &OutTx,
|
||||
cmd: &Command,
|
||||
) {
|
||||
async fn handle_start_interaction(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let leg_id = match cmd.params.get("leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let prompt_wav = match cmd.params.get("prompt_wav").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing prompt_wav"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing prompt_wav");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let expected_digits: Vec<char> = cmd
|
||||
.params
|
||||
@@ -1007,10 +1233,8 @@ async fn handle_start_interaction(
|
||||
serde_json::json!(result_str),
|
||||
);
|
||||
if let Some(ref d) = digit_str {
|
||||
leg.metadata.insert(
|
||||
"last_interaction_digit".to_string(),
|
||||
serde_json::json!(d),
|
||||
);
|
||||
leg.metadata
|
||||
.insert("last_interaction_digit".to_string(), serde_json::json!(d));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1024,18 +1248,20 @@ async fn handle_start_interaction(
|
||||
}
|
||||
|
||||
/// Handle `add_tool_leg` — add a recording or transcription tool leg to a call.
|
||||
async fn handle_add_tool_leg(
|
||||
engine: Arc<Mutex<ProxyEngine>>,
|
||||
out_tx: &OutTx,
|
||||
cmd: &Command,
|
||||
) {
|
||||
async fn handle_add_tool_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let tool_type_str = match cmd.params.get("tool_type").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing tool_type"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing tool_type");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let tool_type = match tool_type_str.as_str() {
|
||||
@@ -1066,13 +1292,11 @@ async fn handle_add_tool_leg(
|
||||
out_tx.clone(),
|
||||
)
|
||||
}
|
||||
crate::mixer::ToolType::Transcription => {
|
||||
crate::tool_leg::spawn_transcription_tool(
|
||||
tool_leg_id.clone(),
|
||||
call_id.clone(),
|
||||
out_tx.clone(),
|
||||
)
|
||||
}
|
||||
crate::mixer::ToolType::Transcription => crate::tool_leg::spawn_transcription_tool(
|
||||
tool_leg_id.clone(),
|
||||
call_id.clone(),
|
||||
out_tx.clone(),
|
||||
),
|
||||
};
|
||||
|
||||
// Send AddToolLeg to the mixer and register in call.
|
||||
@@ -1097,10 +1321,7 @@ async fn handle_add_tool_leg(
|
||||
|
||||
// Register tool leg in the call's leg map.
|
||||
let mut metadata = std::collections::HashMap::new();
|
||||
metadata.insert(
|
||||
"tool_type".to_string(),
|
||||
serde_json::json!(tool_type_str),
|
||||
);
|
||||
metadata.insert("tool_type".to_string(), serde_json::json!(tool_type_str));
|
||||
call.legs.insert(
|
||||
tool_leg_id.clone(),
|
||||
crate::call::LegInfo {
|
||||
@@ -1144,18 +1365,20 @@ async fn handle_add_tool_leg(
|
||||
}
|
||||
|
||||
/// Handle `remove_tool_leg` — remove a tool leg from a call.
|
||||
async fn handle_remove_tool_leg(
|
||||
engine: Arc<Mutex<ProxyEngine>>,
|
||||
out_tx: &OutTx,
|
||||
cmd: &Command,
|
||||
) {
|
||||
async fn handle_remove_tool_leg(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let tool_leg_id = match cmd.params.get("tool_leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing tool_leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing tool_leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
@@ -1191,26 +1414,34 @@ async fn handle_remove_tool_leg(
|
||||
}
|
||||
|
||||
/// Handle `set_leg_metadata` — set a metadata key on a leg.
|
||||
async fn handle_set_leg_metadata(
|
||||
engine: Arc<Mutex<ProxyEngine>>,
|
||||
out_tx: &OutTx,
|
||||
cmd: &Command,
|
||||
) {
|
||||
async fn handle_set_leg_metadata(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let call_id = match cmd.params.get("call_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing call_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing call_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let leg_id = match cmd.params.get("leg_id").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing leg_id"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing leg_id");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let key = match cmd.params.get("key").and_then(|v| v.as_str()) {
|
||||
Some(s) => s.to_string(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing key"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing key");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let value = match cmd.params.get("value") {
|
||||
Some(v) => v.clone(),
|
||||
None => { respond_err(out_tx, &cmd.id, "missing value"); return; }
|
||||
None => {
|
||||
respond_err(out_tx, &cmd.id, "missing value");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut eng = engine.lock().await;
|
||||
@@ -1234,11 +1465,7 @@ async fn handle_set_leg_metadata(
|
||||
}
|
||||
|
||||
/// Handle `generate_tts` — synthesize text to a WAV file using Kokoro TTS.
|
||||
async fn handle_generate_tts(
|
||||
engine: Arc<Mutex<ProxyEngine>>,
|
||||
out_tx: &OutTx,
|
||||
cmd: &Command,
|
||||
) {
|
||||
async fn handle_generate_tts(engine: Arc<Mutex<ProxyEngine>>, out_tx: &OutTx, cmd: &Command) {
|
||||
let tts_engine = engine.lock().await.tts_engine.clone();
|
||||
let mut tts = tts_engine.lock().await;
|
||||
match tts.generate(&cmd.params).await {
|
||||
|
||||
Reference in New Issue
Block a user