rscamper 0.2.2

Rust interface to scamper network measurement tool
Documentation
// rscamper - Rust wrapper for scamper OWAMP measurement 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_owamp::{self, ScamperOwampT, ScamperOwampTxT, ScamperOwampRxT,
    ScamperOwampSchedT,
    SCAMPER_OWAMP_RESULT_NONE, SCAMPER_OWAMP_RESULT_DONE, SCAMPER_OWAMP_RESULT_HALTED,
    SCAMPER_OWAMP_RESULT_ERROR, SCAMPER_OWAMP_RESULT_NOCONN,
    SCAMPER_OWAMP_RESULT_NOTACCEPTED, SCAMPER_OWAMP_RESULT_NOMODE,
    SCAMPER_OWAMP_RESULT_TIMEOUT,
    SCAMPER_OWAMP_SCHED_TYPE_FIXED, SCAMPER_OWAMP_SCHED_TYPE_EXP};

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)
}

/// OWAMP result codes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwampResult {
    None, Done, Halted, Error, NoConn, NotAccepted, NoMode, Timeout, Unknown(u8),
}

impl From<u8> for OwampResult {
    fn from(v: u8) -> Self {
        match v {
            SCAMPER_OWAMP_RESULT_NONE        => Self::None,
            SCAMPER_OWAMP_RESULT_DONE        => Self::Done,
            SCAMPER_OWAMP_RESULT_HALTED      => Self::Halted,
            SCAMPER_OWAMP_RESULT_ERROR       => Self::Error,
            SCAMPER_OWAMP_RESULT_NOCONN      => Self::NoConn,
            SCAMPER_OWAMP_RESULT_NOTACCEPTED => Self::NotAccepted,
            SCAMPER_OWAMP_RESULT_NOMODE      => Self::NoMode,
            SCAMPER_OWAMP_RESULT_TIMEOUT     => Self::Timeout,
            other                            => Self::Unknown(other),
        }
    }
}

/// OWAMP schedule types.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwampSchedType {
    Fixed, Exp, Unknown(u8),
}

impl From<u8> for OwampSchedType {
    fn from(v: u8) -> Self {
        match v {
            SCAMPER_OWAMP_SCHED_TYPE_FIXED => Self::Fixed,
            SCAMPER_OWAMP_SCHED_TYPE_EXP   => Self::Exp,
            other                          => Self::Unknown(other),
        }
    }
}

/// An OWAMP reception record.
pub struct ScamperOwampRx { inner: *mut ScamperOwampRxT }
impl ScamperOwampRx {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperOwampRxT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_owamp::scamper_owamp_rx_use(ptr) };
        Some(ScamperOwampRx { inner: ptr })
    }
    pub fn stamp(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_owamp::scamper_owamp_rx_stamp_get(self.inner) })
    }
    pub fn errest(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_rx_errest_get(self.inner) } }
    pub fn flags(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_rx_flags_get(self.inner) } }
    pub fn dscp(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_rx_dscp_get(self.inner) } }
    pub fn ttl(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_rx_ttl_get(self.inner) } }
}
impl Drop for ScamperOwampRx {
    fn drop(&mut self) { unsafe { scamper_owamp::scamper_owamp_rx_free(self.inner) }; }
}
unsafe impl Send for ScamperOwampRx {}

/// An OWAMP transmission record.
pub struct ScamperOwampTx { inner: *mut ScamperOwampTxT }
impl ScamperOwampTx {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperOwampTxT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_owamp::scamper_owamp_tx_use(ptr) };
        Some(ScamperOwampTx { inner: ptr })
    }
    pub fn sched(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_owamp::scamper_owamp_tx_sched_get(self.inner) })
    }
    pub fn stamp(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_owamp::scamper_owamp_tx_stamp_get(self.inner) })
    }
    pub fn seq(&self) -> u32 { unsafe { scamper_owamp::scamper_owamp_tx_seq_get(self.inner) } }
    pub fn errest(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_tx_errest_get(self.inner) } }
    pub fn flags(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_tx_flags_get(self.inner) } }
    pub fn rxc(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_tx_rxc_get(self.inner) } }
    pub fn rx(&self, i: u8) -> Option<ScamperOwampRx> {
        let ptr = unsafe { scamper_owamp::scamper_owamp_tx_rx_get(self.inner, i) };
        unsafe { ScamperOwampRx::from_ptr(ptr) }
    }
}
impl Drop for ScamperOwampTx {
    fn drop(&mut self) { unsafe { scamper_owamp::scamper_owamp_tx_free(self.inner) }; }
}
unsafe impl Send for ScamperOwampTx {}

/// An OWAMP schedule entry.
pub struct ScamperOwampSched { inner: *mut ScamperOwampSchedT }
impl ScamperOwampSched {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperOwampSchedT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_owamp::scamper_owamp_sched_use(ptr) };
        Some(ScamperOwampSched { inner: ptr })
    }
    pub fn sched_type(&self) -> OwampSchedType {
        OwampSchedType::from(unsafe { scamper_owamp::scamper_owamp_sched_type_get(self.inner) })
    }
    pub fn tv(&self) -> Option<Duration> {
        timeval_to_duration(unsafe { scamper_owamp::scamper_owamp_sched_tv_get(self.inner) })
    }
}
impl Drop for ScamperOwampSched {
    fn drop(&mut self) { unsafe { scamper_owamp::scamper_owamp_sched_free(self.inner) }; }
}
unsafe impl Send for ScamperOwampSched {}

/// A scamper OWAMP measurement result.
pub struct ScamperOwamp { pub(crate) inner: *mut ScamperOwampT }

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

    pub fn src(&self) -> Option<ScamperAddr> {
        unsafe { ScamperAddr::from_ptr(scamper_owamp::scamper_owamp_src_get(self.inner)) }
    }
    pub fn dst(&self) -> Option<ScamperAddr> {
        unsafe { ScamperAddr::from_ptr(scamper_owamp::scamper_owamp_dst_get(self.inner)) }
    }
    pub fn userid(&self) -> u32 { unsafe { scamper_owamp::scamper_owamp_userid_get(self.inner) } }
    pub fn dport(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_dport_get(self.inner) } }
    pub fn flags(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_flags_get(self.inner) } }
    pub fn dir(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_dir_get(self.inner) } }
    pub fn dscp(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_dscp_get(self.inner) } }
    pub fn ttl(&self) -> u8 { unsafe { scamper_owamp::scamper_owamp_ttl_get(self.inner) } }
    pub fn pktsize(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_pktsize_get(self.inner) } }
    pub fn attempts(&self) -> u32 { unsafe { scamper_owamp::scamper_owamp_attempts_get(self.inner) } }

    pub fn start(&self) -> Option<SystemTime> {
        timeval_to_systemtime(unsafe { scamper_owamp::scamper_owamp_start_get(self.inner) })
    }

    pub fn result(&self) -> OwampResult {
        OwampResult::from(unsafe { scamper_owamp::scamper_owamp_result_get(self.inner) })
    }

    pub fn errmsg(&self) -> Option<String> {
        let p = unsafe { scamper_owamp::scamper_owamp_errmsg_get(self.inner) };
        if p.is_null() { None } else {
            let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned();
            unsafe { libc::free(p as *mut libc::c_void) };
            Some(s)
        }
    }

    pub fn udp_sport(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_udp_sport_get(self.inner) } }
    pub fn udp_dport(&self) -> u16 { unsafe { scamper_owamp::scamper_owamp_udp_dport_get(self.inner) } }

    pub fn txc(&self) -> u32 { unsafe { scamper_owamp::scamper_owamp_txc_get(self.inner) } }
    pub fn tx(&self, i: u32) -> Option<ScamperOwampTx> {
        let ptr = unsafe { scamper_owamp::scamper_owamp_tx_get(self.inner, i) };
        unsafe { ScamperOwampTx::from_ptr(ptr) }
    }

    pub fn schedc(&self) -> u32 { unsafe { scamper_owamp::scamper_owamp_schedc_get(self.inner) } }
    pub fn sched(&self, i: u32) -> Option<ScamperOwampSched> {
        let ptr = unsafe { scamper_owamp::scamper_owamp_sched_get(self.inner, i) };
        unsafe { ScamperOwampSched::from_ptr(ptr) }
    }

    pub fn list(&self) -> Option<ScamperList> {
        unsafe { ScamperList::from_ptr(scamper_owamp::scamper_owamp_list_get(self.inner)) }
    }
    pub fn cycle(&self) -> Option<ScamperCycle> {
        unsafe { ScamperCycle::from_ptr(scamper_owamp::scamper_owamp_cycle_get(self.inner)) }
    }

    pub fn to_json(&self) -> Option<String> {
        let mut len = 0usize;
        let ptr = unsafe { scamper_owamp::scamper_owamp_tojson(self.inner, &mut len) };
        if ptr.is_null() { return None; }
        let s = unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned();
        unsafe { libc::free(ptr as *mut libc::c_void) };
        Some(s)
    }
}

impl Drop for ScamperOwamp {
    fn drop(&mut self) { unsafe { scamper_owamp::scamper_owamp_free(self.inner) }; }
}
unsafe impl Send for ScamperOwamp {}
unsafe impl Sync for ScamperOwamp {}