use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use std::panic::{self, AssertUnwindSafe};
use std::sync::{Mutex, OnceLock};
use crate::event::AppEvent;
use super::bindings::*;
use super::re_thread::EVENT_TX;
type InboundHeaderMap = std::collections::HashMap<(usize, String), Vec<(String, String)>>;
static INBOUND_HEADERS: OnceLock<Mutex<InboundHeaderMap>> = OnceLock::new();
pub fn inbound_headers_store() -> &'static Mutex<InboundHeaderMap> {
INBOUND_HEADERS.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
}
#[derive(Clone)]
struct ArmedResponse {
scode: u16,
reason: String,
headers: Vec<String>,
}
static ARMED_RESPONSES: OnceLock<Mutex<std::collections::HashMap<usize, ArmedResponse>>> =
OnceLock::new();
fn armed_responses() -> &'static Mutex<std::collections::HashMap<usize, ArmedResponse>> {
ARMED_RESPONSES.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
}
pub(crate) fn arm_invite_response(ua: usize, scode: u16, reason: String, headers: Vec<String>) {
armed_responses()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(
ua,
ArmedResponse {
scode,
reason,
headers,
},
);
}
pub(crate) fn disarm_invite_response(ua: usize) {
armed_responses()
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&ua);
}
fn respond_to_invite(msg: *const SipMsg, scode: u16, reason: &str, headers: &[String]) -> bool {
let mut body = String::new();
for h in headers {
body.push_str(h);
body.push_str("\r\n");
}
body.push_str("Content-Length: 0\r\n\r\n");
let (Ok(reason_c), Ok(body_c)) = (CString::new(reason), CString::new(body)) else {
return false;
};
let rc = unsafe {
sip_treplyf(
std::ptr::null_mut(),
std::ptr::null_mut(),
uag_sip(),
msg,
false,
scode,
reason_c.as_ptr(),
c"%s".as_ptr(),
body_c.as_ptr(),
)
};
rc == 0
}
unsafe extern "C" fn collect_hdr_cb(le: *mut Le, _arg: *mut c_void) -> bool {
if le.is_null() {
return false;
}
let hdr = unsafe { (*le).data as *const SipHdr };
if hdr.is_null() {
return false;
}
let collector = unsafe { &mut *((_arg) as *mut HeaderVec) };
let name = pl_to_string(unsafe { &(*hdr).name as *const Pl });
let val = pl_to_string(unsafe { &(*hdr).val as *const Pl });
collector.0.push((name, val));
false }
struct HeaderVec(Vec<(String, String)>);
pub unsafe extern "C" fn bevent_handler(ev: BeventEv, event: *mut Bevent, _arg: *mut c_void) {
let result = panic::catch_unwind(AssertUnwindSafe(|| bevent_handler_inner(ev, event)));
if let Err(_panic) = result {
crate::rlog!(Error, "panic in bevent_handler — suppressed");
}
}
fn bevent_handler_inner(ev: BeventEv, event: *mut Bevent) {
let bevent_ev = ev as i32;
let app_event = match bevent_ev {
x if x == bevent_ev::BEVENT_SIPSESS_CONN as i32 => {
let msg = unsafe { bevent_get_msg(event) };
let ua = if !msg.is_null() {
unsafe { uag_find_msg(msg) }
} else {
std::ptr::null_mut()
};
if !msg.is_null() && !ua.is_null() {
let call_id = pl_to_string(unsafe { &(*msg).callid as *const Pl });
let mut collector = HeaderVec(Vec::new());
unsafe {
list_apply(
&(*msg).hdrl as *const List,
true,
Some(collect_hdr_cb),
&mut collector as *mut HeaderVec as *mut c_void,
);
}
inbound_headers_store()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert((ua as usize, call_id), collector.0);
let armed = armed_responses()
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&(ua as usize))
.cloned();
if let Some(r) = armed {
if respond_to_invite(msg, r.scode, &r.reason, &r.headers) {
crate::rlog!(
Info,
"answered inbound INVITE with {} {}",
r.scode,
r.reason
);
} else {
crate::rlog!(Warn, "failed to send armed {} response", r.scode);
}
unsafe { bevent_stop(event) };
return;
}
let rc = unsafe { ua_accept(ua, msg) };
if rc != 0 {
crate::rlog!(Warn, "ua_accept() failed (rc={rc})");
}
}
return;
}
x if x == bevent_ev::BEVENT_REGISTERING as i32 => {
let ua = unsafe { bevent_get_ua(event) };
let account = ua_aor(ua);
AppEvent::Registering { account }
}
x if x == bevent_ev::BEVENT_REGISTER_OK as i32
|| x == bevent_ev::BEVENT_FALLBACK_OK as i32 =>
{
let ua = unsafe { bevent_get_ua(event) };
let account = ua_aor(ua);
AppEvent::RegisterOk { account }
}
x if x == bevent_ev::BEVENT_REGISTER_FAIL as i32
|| x == bevent_ev::BEVENT_FALLBACK_FAIL as i32 =>
{
let text = bevent_text(event);
AppEvent::RegisterFailed { reason: text }
}
x if x == bevent_ev::BEVENT_UNREGISTERING as i32 => {
let ua = unsafe { bevent_get_ua(event) };
let account = ua_aor(ua);
AppEvent::Unregistered { account }
}
x if x == bevent_ev::BEVENT_CALL_INCOMING as i32 => {
let call = unsafe { bevent_get_call(event) };
super::sounds::play_alert("ring.wav");
let (call_id, number, display_name) = call_info(call);
AppEvent::CallIncoming {
call_id,
number,
display_name,
}
}
x if x == bevent_ev::BEVENT_CALL_OUTGOING as i32 => {
let call = unsafe { bevent_get_call(event) };
let (call_id, number, _) = call_info(call);
AppEvent::CallOutgoing { call_id, number }
}
x if x == bevent_ev::BEVENT_CALL_RINGING as i32 => {
let call = unsafe { bevent_get_call(event) };
super::sounds::play_alert("ringback.wav");
let call_id = call_id_str(call);
AppEvent::CallRinging { call_id }
}
x if x == bevent_ev::BEVENT_CALL_ESTABLISHED as i32 => {
super::sounds::stop_alert();
let call = unsafe { bevent_get_call(event) };
let call_id = call_id_str(call);
AppEvent::CallEstablished { call_id }
}
x if x == bevent_ev::BEVENT_CALL_CLOSED as i32 => {
super::sounds::stop_alert();
let call = unsafe { bevent_get_call(event) };
let call_id = call_id_str(call);
let text = bevent_text(event);
let scode = if !call.is_null() {
unsafe { call_scode(call) }
} else {
0
};
let reason = if scode >= 100 && !text.starts_with(|c: char| c.is_ascii_digit()) {
format!("{scode} {text}")
} else {
text
};
let error = crate::event::is_error_reason(&reason);
AppEvent::CallClosed {
call_id,
reason,
error,
}
}
x if x == bevent_ev::BEVENT_MWI_NOTIFY as i32 => {
let text = bevent_text(event);
parse_mwi(&text)
}
_ => AppEvent::Unknown {
class: "bevent".into(),
type_: format!("{bevent_ev}"),
},
};
let ua = unsafe { bevent_get_ua(event) };
if !ua.is_null() {
if let Some(mtx) = EVENT_TX.get() {
if let Some(tx) = mtx
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(&(ua as usize))
{
let _ = tx.send(app_event);
}
}
}
}
pub fn ua_aor(ua: *mut Ua) -> String {
if ua.is_null() {
return String::new();
}
let acc = unsafe { ua_account(ua) };
if acc.is_null() {
return String::new();
}
cstr_to_string(unsafe { account_aor(acc) })
}
pub fn call_info(call: *mut Call) -> (String, String, Option<String>) {
if call.is_null() {
return (String::new(), String::new(), None);
}
let id = call_id_str(call);
let number = cstr_to_string(unsafe { call_peeruri(call) });
let display_name = {
let n = cstr_to_string(unsafe { call_peername(call) });
if n.is_empty() { None } else { Some(n) }
};
(id, number, display_name)
}
pub fn call_id_str(call: *mut Call) -> String {
if call.is_null() {
return String::new();
}
cstr_to_string(unsafe { call_id(call) })
}
fn bevent_text(event: *mut Bevent) -> String {
cstr_to_string(unsafe { bevent_get_text(event) })
}
pub fn cstr_to_string(ptr: *const c_char) -> String {
if ptr.is_null() {
return String::new();
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.unwrap_or("")
.to_string()
}
fn parse_mwi(param: &str) -> AppEvent {
let mut waiting = false;
let mut new_count = 0u32;
for line in param.lines() {
if let Some(val) = line.strip_prefix("Messages-Waiting:") {
waiting = val.trim().eq_ignore_ascii_case("yes");
}
if let Some(val) = line.strip_prefix("Voice-Message:") {
if let Some(new) = val.trim().split('/').next() {
new_count = new.trim().parse().unwrap_or(0);
}
}
}
AppEvent::VoicemailStatus { waiting, new_count }
}
fn pl_to_string(pl: *const Pl) -> String {
if pl.is_null() {
return String::new();
}
unsafe {
let pl = &*pl;
if pl.l == 0 {
return String::new();
}
let slice = std::slice::from_raw_parts(pl.p as *const u8, pl.l);
String::from_utf8_lossy(slice).to_string()
}
}