rscamper 0.2.2

Rust interface to scamper network measurement tool
Documentation
// rscamper - Rust wrapper for scamper alias resolution 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_dealias::{self, ScamperDealiasT, ScamperDealiasProbeT,
    ScamperDealiasReplyT, ScamperDealiasProbedefT};

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

/// A dealias probe definition.
pub struct ScamperDealiasProbedef {
    inner: *mut ScamperDealiasProbedefT,
}

impl ScamperDealiasProbedef {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperDealiasProbedefT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_dealias::scamper_dealias_probedef_use(ptr) };
        Some(ScamperDealiasProbedef { inner: ptr })
    }

    pub fn src(&self) -> Option<ScamperAddr> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_probedef_src_get(self.inner) };
        unsafe { ScamperAddr::from_ptr(ptr) }
    }

    pub fn dst(&self) -> Option<ScamperAddr> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_probedef_dst_get(self.inner) };
        unsafe { ScamperAddr::from_ptr(ptr) }
    }

    pub fn id(&self) -> u32 {
        unsafe { scamper_dealias::scamper_dealias_probedef_id_get(self.inner) }
    }

    pub fn method(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_probedef_method_get(self.inner) }
    }

    pub fn is_udp(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_probedef_is_udp(self.inner) != 0 }
    }

    pub fn is_icmp(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_probedef_is_icmp(self.inner) != 0 }
    }

    pub fn is_tcp(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_probedef_is_tcp(self.inner) != 0 }
    }

    pub fn ttl(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_probedef_ttl_get(self.inner) }
    }

    pub fn tos(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_probedef_tos_get(self.inner) }
    }

    pub fn size(&self) -> u16 {
        unsafe { scamper_dealias::scamper_dealias_probedef_size_get(self.inner) }
    }
}

impl Drop for ScamperDealiasProbedef {
    fn drop(&mut self) {
        unsafe { scamper_dealias::scamper_dealias_probedef_free(self.inner) };
    }
}

unsafe impl Send for ScamperDealiasProbedef {}
unsafe impl Sync for ScamperDealiasProbedef {}

/// A reply to a dealias probe.
pub struct ScamperDealiasReply {
    inner: *mut ScamperDealiasReplyT,
}

impl ScamperDealiasReply {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperDealiasReplyT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_dealias::scamper_dealias_reply_use(ptr) };
        Some(ScamperDealiasReply { inner: ptr })
    }

    pub fn src(&self) -> Option<ScamperAddr> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_reply_src_get(self.inner) };
        unsafe { ScamperAddr::from_ptr(ptr) }
    }

    pub fn rx(&self) -> Option<SystemTime> {
        let tv = unsafe { scamper_dealias::scamper_dealias_reply_rx_get(self.inner) };
        timeval_to_systemtime(tv)
    }

    pub fn proto(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_reply_proto_get(self.inner) }
    }

    pub fn ttl(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_reply_ttl_get(self.inner) }
    }

    pub fn size(&self) -> u16 {
        unsafe { scamper_dealias::scamper_dealias_reply_size_get(self.inner) }
    }

    pub fn ipid(&self) -> u16 {
        unsafe { scamper_dealias::scamper_dealias_reply_ipid_get(self.inner) }
    }

    pub fn ipid32(&self) -> u32 {
        unsafe { scamper_dealias::scamper_dealias_reply_ipid32_get(self.inner) }
    }

    pub fn is_icmp(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_reply_is_icmp(self.inner) != 0 }
    }

    pub fn is_tcp(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_reply_is_tcp(self.inner) != 0 }
    }

    pub fn icmp_type(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_reply_icmp_type_get(self.inner) }
    }

    pub fn icmp_code(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_reply_icmp_code_get(self.inner) }
    }

    pub fn tcp_flags(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_reply_tcp_flags_get(self.inner) }
    }
}

impl Drop for ScamperDealiasReply {
    fn drop(&mut self) {
        unsafe { scamper_dealias::scamper_dealias_reply_free(self.inner) };
    }
}

unsafe impl Send for ScamperDealiasReply {}
unsafe impl Sync for ScamperDealiasReply {}

/// A dealias probe with its associated replies.
pub struct ScamperDealiasProbe {
    inner: *mut ScamperDealiasProbeT,
}

impl ScamperDealiasProbe {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperDealiasProbeT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_dealias::scamper_dealias_probe_use(ptr) };
        Some(ScamperDealiasProbe { inner: ptr })
    }

    pub fn def(&self) -> Option<ScamperDealiasProbedef> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_probe_def_get(self.inner) };
        unsafe { ScamperDealiasProbedef::from_ptr(ptr) }
    }

    pub fn seq(&self) -> u32 {
        unsafe { scamper_dealias::scamper_dealias_probe_seq_get(self.inner) }
    }

    pub fn tx(&self) -> Option<SystemTime> {
        let tv = unsafe { scamper_dealias::scamper_dealias_probe_tx_get(self.inner) };
        timeval_to_systemtime(tv)
    }

    pub fn ipid(&self) -> u16 {
        unsafe { scamper_dealias::scamper_dealias_probe_ipid_get(self.inner) }
    }

    pub fn replyc(&self) -> u16 {
        unsafe { scamper_dealias::scamper_dealias_probe_replyc_get(self.inner) }
    }

    pub fn reply(&self, i: u16) -> Option<ScamperDealiasReply> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_probe_reply_get(self.inner, i) };
        unsafe { ScamperDealiasReply::from_ptr(ptr) }
    }

    pub fn reply_is_from_target(&self, reply: &ScamperDealiasReply) -> bool {
        unsafe {
            scamper_dealias::scamper_dealias_reply_from_target(self.inner, reply.inner) != 0
        }
    }
}

impl Drop for ScamperDealiasProbe {
    fn drop(&mut self) {
        unsafe { scamper_dealias::scamper_dealias_probe_free(self.inner) };
    }
}

unsafe impl Send for ScamperDealiasProbe {}
unsafe impl Sync for ScamperDealiasProbe {}

/// A scamper alias resolution measurement result.
pub struct ScamperDealias {
    pub(crate) inner: *mut ScamperDealiasT,
}

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

    pub fn userid(&self) -> u32 {
        unsafe { scamper_dealias::scamper_dealias_userid_get(self.inner) }
    }

    pub fn start(&self) -> Option<SystemTime> {
        let tv = unsafe { scamper_dealias::scamper_dealias_start_get(self.inner) };
        timeval_to_systemtime(tv)
    }

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

    pub fn result(&self) -> u8 {
        unsafe { scamper_dealias::scamper_dealias_result_get(self.inner) }
    }

    pub fn result_is_aliases(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_result_is_aliases(self.inner) != 0 }
    }

    pub fn is_method_mercator(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_mercator(self.inner) != 0 }
    }

    pub fn is_method_ally(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_ally(self.inner) != 0 }
    }

    pub fn is_method_radargun(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_radargun(self.inner) != 0 }
    }

    pub fn is_method_prefixscan(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_prefixscan(self.inner) != 0 }
    }

    pub fn is_method_bump(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_bump(self.inner) != 0 }
    }

    pub fn is_method_midarest(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_midarest(self.inner) != 0 }
    }

    pub fn is_method_midardisc(&self) -> bool {
        unsafe { scamper_dealias::scamper_dealias_method_is_midardisc(self.inner) != 0 }
    }

    pub fn probec(&self) -> u32 {
        unsafe { scamper_dealias::scamper_dealias_probec_get(self.inner) }
    }

    pub fn probe(&self, i: u32) -> Option<ScamperDealiasProbe> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_probe_get(self.inner, i) };
        unsafe { ScamperDealiasProbe::from_ptr(ptr) }
    }

    pub fn list(&self) -> Option<ScamperList> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_list_get(self.inner) };
        unsafe { ScamperList::from_ptr(ptr) }
    }

    pub fn cycle(&self) -> Option<ScamperCycle> {
        let ptr = unsafe { scamper_dealias::scamper_dealias_cycle_get(self.inner) };
        unsafe { ScamperCycle::from_ptr(ptr) }
    }

    /// Convert to JSON string.
    pub fn to_json(&self) -> Option<String> {
        let mut len = 0usize;
        let ptr = unsafe { scamper_dealias::scamper_dealias_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 ScamperDealias {
    fn drop(&mut self) {
        unsafe { scamper_dealias::scamper_dealias_free(self.inner) };
    }
}

unsafe impl Send for ScamperDealias {}
unsafe impl Sync for ScamperDealias {}