rscamper 0.2.2

Rust interface to scamper network measurement tool
Documentation
// rscamper - Rust wrapper for scamper packet capture results
//
// Copyright (C) 2026 Dimitrios Giakatos
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.

use std::ffi::CStr;
use std::time::{Duration, UNIX_EPOCH, SystemTime};

use crate::addr::ScamperAddr;
use crate::list::{ScamperList, ScamperCycle};
use crate::ffi::scamper_sniff::{self, ScamperSniffT, ScamperSniffPktT,
    SCAMPER_SNIFF_STOP_NONE, SCAMPER_SNIFF_STOP_ERROR,
    SCAMPER_SNIFF_STOP_LIMIT_TIME, SCAMPER_SNIFF_STOP_LIMIT_PKTC, SCAMPER_SNIFF_STOP_HALTED};

fn timeval_to_duration(tv: *const libc::timeval) -> Option<Duration> {
    if tv.is_null() { return None; }
    let tv = unsafe { &*tv };
    Some(Duration::new(tv.tv_sec as u64, tv.tv_usec as u32 * 1000))
}

fn timeval_to_systemtime(tv: *const libc::timeval) -> Option<SystemTime> {
    timeval_to_duration(tv).map(|d| UNIX_EPOCH + d)
}

/// Sniff stop reasons.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SniffStopReason {
    None, Error, LimitTime, LimitPktc, Halted, Unknown(u8),
}

impl From<u8> for SniffStopReason {
    fn from(v: u8) -> Self {
        match v {
            SCAMPER_SNIFF_STOP_NONE       => Self::None,
            SCAMPER_SNIFF_STOP_ERROR      => Self::Error,
            SCAMPER_SNIFF_STOP_LIMIT_TIME => Self::LimitTime,
            SCAMPER_SNIFF_STOP_LIMIT_PKTC => Self::LimitPktc,
            SCAMPER_SNIFF_STOP_HALTED     => Self::Halted,
            other                         => Self::Unknown(other),
        }
    }
}

/// A single captured packet.
pub struct ScamperSniffPkt { inner: *mut ScamperSniffPktT }

impl ScamperSniffPkt {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperSniffPktT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_sniff::scamper_sniff_pkt_use(ptr) };
        Some(ScamperSniffPkt { inner: ptr })
    }

    pub fn tv(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_sniff::scamper_sniff_pkt_tv_get(self.inner) })
    }

    pub fn data(&self) -> &[u8] {
        let len = unsafe { scamper_sniff::scamper_sniff_pkt_len_get(self.inner) } as usize;
        let ptr = unsafe { scamper_sniff::scamper_sniff_pkt_data_get(self.inner) };
        if ptr.is_null() || len == 0 { &[] } else { unsafe { std::slice::from_raw_parts(ptr, len) } }
    }

    pub fn len(&self) -> u16 { unsafe { scamper_sniff::scamper_sniff_pkt_len_get(self.inner) } }
}

impl Drop for ScamperSniffPkt {
    fn drop(&mut self) { unsafe { scamper_sniff::scamper_sniff_pkt_free(self.inner) }; }
}
unsafe impl Send for ScamperSniffPkt {}
unsafe impl Sync for ScamperSniffPkt {}

/// A scamper packet sniff measurement result.
pub struct ScamperSniff { pub(crate) inner: *mut ScamperSniffT }

impl ScamperSniff {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperSniffT) -> Option<Self> {
        if ptr.is_null() { return None; }
        Some(ScamperSniff { inner: ptr })
    }

    pub fn src(&self) -> Option<ScamperAddr> {
        unsafe { ScamperAddr::from_ptr(scamper_sniff::scamper_sniff_src_get(self.inner)) }
    }
    pub fn userid(&self) -> u32 { unsafe { scamper_sniff::scamper_sniff_userid_get(self.inner) } }
    pub fn icmpid(&self) -> u16 { unsafe { scamper_sniff::scamper_sniff_icmpid_get(self.inner) } }

    pub fn start(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_sniff::scamper_sniff_start_get(self.inner) })
    }
    pub fn finish(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_sniff::scamper_sniff_finish_get(self.inner) })
    }

    pub fn stop_reason(&self) -> SniffStopReason {
        SniffStopReason::from(unsafe { scamper_sniff::scamper_sniff_stop_reason_get(self.inner) })
    }

    pub fn limit_pktc(&self) -> u32 { unsafe { scamper_sniff::scamper_sniff_limit_pktc_get(self.inner) } }
    pub fn limit_time(&self) -> Option<Duration> {
        timeval_to_duration(unsafe { scamper_sniff::scamper_sniff_limit_time_get(self.inner) })
    }

    pub fn errmsg(&self) -> Option<&str> {
        let p = unsafe { scamper_sniff::scamper_sniff_errmsg_get(self.inner) };
        if p.is_null() { None } else { Some(unsafe { CStr::from_ptr(p) }.to_str().unwrap_or("")) }
    }

    pub fn pktc(&self) -> u32 { unsafe { scamper_sniff::scamper_sniff_pktc_get(self.inner) } }

    pub fn pkt(&self, i: u32) -> Option<ScamperSniffPkt> {
        let ptr = unsafe { scamper_sniff::scamper_sniff_pkt_get(self.inner, i) };
        unsafe { ScamperSniffPkt::from_ptr(ptr) }
    }

    pub fn list(&self) -> Option<ScamperList> {
        unsafe { ScamperList::from_ptr(scamper_sniff::scamper_sniff_list_get(self.inner)) }
    }
    pub fn cycle(&self) -> Option<ScamperCycle> {
        unsafe { ScamperCycle::from_ptr(scamper_sniff::scamper_sniff_cycle_get(self.inner)) }
    }
}

impl Drop for ScamperSniff {
    fn drop(&mut self) { unsafe { scamper_sniff::scamper_sniff_free(self.inner) }; }
}
unsafe impl Send for ScamperSniff {}
unsafe impl Sync for ScamperSniff {}