use std::io::Write;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Instant;
use chrono::Local;
#[derive(Clone)]
pub struct DebugLog {
inner: Option<Arc<Mutex<std::fs::File>>>,
}
impl DebugLog {
pub fn new(enabled: bool, path: &str) -> Self {
if enabled {
match std::fs::File::create(path) {
Ok(f) => {
eprintln!("[+] Debug logging to {}", path);
Self { inner: Some(Arc::new(Mutex::new(f))) }
}
Err(e) => {
eprintln!("[-] Failed to create debug log: {}", e);
Self { inner: None }
}
}
} else {
Self { inner: None }
}
}
pub fn log(&self, msg: &str) {
if let Some(ref f) = self.inner {
if let Ok(mut file) = f.lock() {
let ts = Local::now().format("%H:%M:%S%.3f");
let _ = writeln!(file, "[{}] {}", ts, msg);
}
}
}
pub fn log_hex(&self, prefix: &str, data: &[u8], max_bytes: usize) {
if self.inner.is_none() { return; }
let len = data.len().min(max_bytes);
let hex: String = data[..len].iter().map(|b| format!("{:02x}", b)).collect::<Vec<_>>().join(" ");
let suffix = if data.len() > max_bytes { format!("... ({} bytes total)", data.len()) } else { String::new() };
self.log(&format!("{}: {}{}", prefix, hex, suffix));
}
pub fn is_enabled(&self) -> bool {
self.inner.is_some()
}
}
#[derive(Clone)]
pub struct PcapDumper {
inner: Option<Arc<Mutex<std::fs::File>>>,
start: Instant,
}
impl PcapDumper {
pub fn new(path: Option<&PathBuf>) -> Self {
match path {
Some(p) => {
match std::fs::File::create(p) {
Ok(mut f) => {
let hdr: [u8; 24] = [
0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
];
if f.write_all(&hdr).is_ok() {
eprintln!("[+] Dumping all packets to {}", p.display());
Self { inner: Some(Arc::new(Mutex::new(f))), start: Instant::now() }
} else {
Self { inner: None, start: Instant::now() }
}
}
Err(e) => {
eprintln!("[-] Failed to create pcap dump: {}", e);
Self { inner: None, start: Instant::now() }
}
}
}
None => Self { inner: None, start: Instant::now() },
}
}
pub fn write_packet(&self, raw: &[u8]) {
if let Some(ref f) = self.inner {
if let Ok(mut file) = f.lock() {
let elapsed = self.start.elapsed();
let ts_sec = elapsed.as_secs() as u32;
let ts_usec = elapsed.subsec_micros();
let len = raw.len() as u32;
let mut hdr = [0u8; 16];
hdr[0..4].copy_from_slice(&ts_sec.to_le_bytes());
hdr[4..8].copy_from_slice(&ts_usec.to_le_bytes());
hdr[8..12].copy_from_slice(&len.to_le_bytes());
hdr[12..16].copy_from_slice(&len.to_le_bytes());
let _ = file.write_all(&hdr);
let _ = file.write_all(raw);
}
}
}
}
static DEBUG_LOG: OnceLock<DebugLog> = OnceLock::new();
static PCAP_DUMP: OnceLock<PcapDumper> = OnceLock::new();
pub fn init_debug(enabled: bool, path: &str) {
let _ = DEBUG_LOG.set(DebugLog::new(enabled, path));
}
pub fn init_pcap_dump(path: Option<&PathBuf>) {
let _ = PCAP_DUMP.set(PcapDumper::new(path));
}
pub fn dbg_log(msg: &str) {
if let Some(log) = DEBUG_LOG.get() {
log.log(msg);
}
}
pub fn dbg_hex(prefix: &str, data: &[u8], max_bytes: usize) {
if let Some(log) = DEBUG_LOG.get() {
log.log_hex(prefix, data, max_bytes);
}
}
pub fn dbg_enabled() -> bool {
DEBUG_LOG.get().map(|l| l.is_enabled()).unwrap_or(false)
}
pub fn pcap_dump(raw: &[u8]) {
if let Some(d) = PCAP_DUMP.get() {
d.write_packet(raw);
}
}