mod ausrc;
mod bindings;
mod config;
mod events;
mod phone;
mod re_thread;
mod siptrace;
mod sounds;
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::{Mutex, OnceLock};
use anyhow::{Result, bail};
use crate::account::{Account, BackendOptions};
use crate::backend::{Backend, Session};
use crate::event::AppEvent;
use self::bindings::*;
use self::config::{build_config_string, configure_account};
use self::events::bevent_handler;
use self::phone::{BaresipPhone, BaresipSessionHandle, make_header_poll};
use self::re_thread::{EVENT_TX, enter_re_thread, on_re_thread, redirect_logging, start_re_thread};
pub use self::re_thread::stop_re_thread;
pub fn sip_trace_file(path: impl AsRef<std::path::Path>) {
siptrace::init_file(path);
}
pub fn sip_trace_stderr() {
siptrace::init_stderr();
}
pub fn is_registered() -> bool {
use self::bindings::{Ua, ua_isregistered, uag_list};
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
on_re_thread(move || {
let result = unsafe {
let list = uag_list();
if list.is_null() {
false
} else {
let mut le = (*list).head;
let mut found = false;
while !le.is_null() {
let ua = (*le).data as *const Ua;
if !ua.is_null() && ua_isregistered(ua) {
found = true;
break;
}
le = (*le).next;
}
found
}
};
let _ = tx.send(result);
});
rx.recv().unwrap_or(false)
}
pub fn received_audio(key: &str) -> Option<(Vec<i16>, u32)> {
ausrc::received_window(key)
}
pub fn sent_audio(key: &str) -> Option<(Vec<i16>, u32)> {
ausrc::sent_window(key)
}
pub fn call_count() -> u32 {
use self::bindings::uag_call_count;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
on_re_thread(move || {
let count = unsafe { uag_call_count() };
let _ = tx.send(count);
});
rx.recv().unwrap_or(0)
}
pub struct BaresipBackend;
impl Backend for BaresipBackend {
fn spawn_session(
&self,
_rt: &tokio::runtime::Handle,
_name: &str,
account: &Account,
options: &BackendOptions,
) -> Result<Session> {
let _ = EVENT_TX.set(Mutex::new(HashMap::new()));
redirect_logging();
if let Err(e) = start_re_thread() {
bail!("{e}");
}
let mut ua_ptr: *mut Ua = std::ptr::null_mut();
let msg_rx;
unsafe {
let _guard = enter_re_thread();
let dir = format!("/tmp/ringo-baresip-{}", std::process::id());
{
use std::os::unix::fs::DirBuilderExt;
let _ = std::fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(&dir);
}
let tmp = CString::new(dir).unwrap();
let _ = conf_path_set(tmp.as_ptr());
let config_str = build_config_string(account, options);
crate::rlog!(Info, "baresip config:\n{}", config_str);
let config_c = match CString::new(config_str) {
Ok(s) => s,
Err(_) => bail!("generated baresip config contains an interior NUL byte"),
};
let rc = conf_configure_buf(config_c.as_ptr() as *const u8, config_c.to_bytes().len());
if rc != 0 {
bail!("conf_configure_buf() failed (rc={rc})");
}
static BARESIP_INIT_DONE: OnceLock<bool> = OnceLock::new();
if !BARESIP_INIT_DONE.get().copied().unwrap_or(false) {
let cfg = conf_config();
if cfg.is_null() {
bail!("conf_config() returned null");
}
let rc = baresip_init(cfg);
if rc != 0 {
bail!("baresip_init() failed (rc={rc})");
}
const DEFAULT_UA: &str = concat!("ringo-core/", env!("CARGO_PKG_VERSION"));
let sw_str = options.user_agent.as_deref().unwrap_or(DEFAULT_UA);
let sw = CString::new(sw_str).unwrap_or_else(|_| CString::new(DEFAULT_UA).unwrap());
let rc = ua_init(sw.as_ptr(), true, true, true);
if rc != 0 {
bail!("ua_init() failed (rc={rc})");
}
bevent_register(Some(bevent_handler), std::ptr::null_mut());
if let Err(e) = ausrc::register_module() {
bail!("{e}");
}
siptrace::install_if_requested();
let _ = BARESIP_INIT_DONE.set(true);
}
let rc = conf_modules();
if rc != 0 {
crate::rlog!(Warn, "conf_modules() returned {rc}");
}
let audio_params = if options.audio_driver.as_deref() == Some("aubridge") {
ausrc::set_full_capture(options.record_audio);
ausrc::init_generator(&account.username);
format!(
";audio_source=ringo,{};audio_player=ringo,{}",
account.username, account.username
)
} else {
String::new()
};
let aor = match CString::new(format!(
"{}<sip:{}@{}>{}{}",
account
.display_name
.as_deref()
.filter(|s| !s.is_empty())
.map(|s| format!("{s} "))
.unwrap_or_default(),
account.username,
account.domain,
account
.transport
.as_deref()
.filter(|s| !s.is_empty())
.map(|s| format!(";transport={s}"))
.unwrap_or_default(),
audio_params,
)) {
Ok(s) => s,
Err(_) => bail!("account fields contain an interior NUL byte"),
};
let rc = ua_alloc(&mut ua_ptr, aor.as_ptr());
if rc != 0 {
bail!("ua_alloc() failed (rc={rc})");
}
let acc = ua_account(ua_ptr);
if !acc.is_null() {
if let Err(e) = configure_account(acc, account) {
bail!("configure_account() failed: {e}");
}
}
let ua_usize = ua_ptr as usize;
let (msg_tx, rx) = std::sync::mpsc::channel::<AppEvent>();
msg_rx = rx;
if let Some(mtx) = EVENT_TX.get() {
mtx.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(ua_usize, msg_tx);
}
let rc = ua_register(ua_ptr);
if rc != 0 {
crate::rlog!(Warn, "ua_register() failed (rc={rc})");
}
}
let ua_usize = ua_ptr as usize;
let audio_key = if options.audio_driver.as_deref() == Some("aubridge") {
Some(account.username.clone())
} else {
None
};
let phone = Box::new(BaresipPhone::new(ua_usize, audio_key.clone()));
let handle = Box::new(BaresipSessionHandle::new(ua_usize, audio_key));
let header_poll = Some(make_header_poll(ua_usize));
Ok(Session::new(msg_rx, phone, header_poll, handle))
}
}