mod pcap;
use std::fs::OpenOptions;
use std::io::Write;
use std::os::raw::c_void;
use std::panic::{self, AssertUnwindSafe};
use std::path::Path;
use std::sync::{Mutex, OnceLock};
use std::time::SystemTime;
use super::bindings::*;
const AF_INET: i32 = 2;
enum Sink {
Text(Box<dyn Write + Send>),
Pcap(Box<dyn Write + Send>),
}
static SINK: OnceLock<Mutex<Sink>> = OnceLock::new();
pub fn init_file(path: impl AsRef<Path>) {
let path = path.as_ref();
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let Ok(mut f) = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
else {
return;
};
let pcap = path
.extension()
.is_some_and(|e| e.eq_ignore_ascii_case("pcap"));
let sink = if pcap {
if f.write_all(&pcap::global_header()).is_err() {
return;
}
Sink::Pcap(Box::new(f))
} else {
Sink::Text(Box::new(f))
};
let _ = SINK.set(Mutex::new(sink));
}
pub fn init_stderr() {
let _ = SINK.set(Mutex::new(Sink::Text(Box::new(std::io::stderr()))));
}
unsafe extern "C" fn trace_cb(
tx: bool,
tp: sip_transp,
src: *const sa,
dst: *const sa,
pkt: *const u8,
len: usize,
_arg: *mut c_void,
) {
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let Some(mtx) = SINK.get() else {
return;
};
if pkt.is_null() || len == 0 {
return;
}
let bytes = unsafe { std::slice::from_raw_parts(pkt, len) };
let mut sink = mtx.lock().unwrap_or_else(|e| e.into_inner());
match &mut *sink {
Sink::Text(w) => {
let msg = String::from_utf8_lossy(bytes);
let dir = if tx { "TX →" } else { "RX ←" };
let transp = transp_name(tp);
let ts = chrono::Local::now().format("%H:%M:%S%.3f");
let _ = writeln!(w, "[{ts}] SIP {dir} {transp}\n{}\n", msg.trim_end());
}
Sink::Pcap(w) => {
if let Some(rec) = unsafe { pcap_record(src, dst, bytes) } {
let _ = w.write_all(&rec);
}
}
}
}));
}
fn transp_name(tp: sip_transp) -> &'static str {
let p = unsafe { sip_transp_name(tp) };
if p.is_null() {
return "?";
}
unsafe { std::ffi::CStr::from_ptr(p) }
.to_str()
.unwrap_or("?")
}
unsafe fn pcap_record(src: *const sa, dst: *const sa, payload: &[u8]) -> Option<Vec<u8>> {
if src.is_null() || dst.is_null() {
return None;
}
let sport = unsafe { sa_port(src) };
let dport = unsafe { sa_port(dst) };
let (s, d): (Vec<u8>, Vec<u8>) = if unsafe { sa_af(src) } == AF_INET {
(
unsafe { sa_in(src) }.to_be_bytes().to_vec(),
unsafe { sa_in(dst) }.to_be_bytes().to_vec(),
)
} else {
let (mut s, mut d) = ([0u8; 16], [0u8; 16]);
unsafe {
sa_in6(src, s.as_mut_ptr());
sa_in6(dst, d.as_mut_ptr());
}
(s.to_vec(), d.to_vec())
};
Some(pcap::record(
&s,
&d,
sport,
dport,
payload,
SystemTime::now(),
))
}
pub(super) fn install_if_requested() {
if SINK.get().is_none() {
return;
}
unsafe {
let sip = uag_sip();
if !sip.is_null() {
sip_set_trace_handler(sip, Some(trace_cb));
}
}
}