rscamper 0.2.2

Rust interface to scamper network measurement tool
Documentation
// rscamper - Rust wrapper for scamper HTTP 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, CString};
use std::time::{Duration, UNIX_EPOCH, SystemTime};

use crate::addr::ScamperAddr;
use crate::list::{ScamperList, ScamperCycle};
use crate::ffi::scamper_http::{self, ScamperHttpT, ScamperHttpBufT,
    SCAMPER_HTTP_STOP_NONE, SCAMPER_HTTP_STOP_HALTED, SCAMPER_HTTP_STOP_NOCONN,
    SCAMPER_HTTP_STOP_DONE, SCAMPER_HTTP_STOP_ERROR, SCAMPER_HTTP_STOP_TIMEOUT,
    SCAMPER_HTTP_STOP_INSECURE};

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

/// HTTP stop reasons.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpStopReason {
    None, Halted, NoConn, Done, Error, Timeout, Insecure, Unknown(u8),
}

impl From<u8> for HttpStopReason {
    fn from(v: u8) -> Self {
        match v {
            SCAMPER_HTTP_STOP_NONE     => Self::None,
            SCAMPER_HTTP_STOP_HALTED   => Self::Halted,
            SCAMPER_HTTP_STOP_NOCONN   => Self::NoConn,
            SCAMPER_HTTP_STOP_DONE     => Self::Done,
            SCAMPER_HTTP_STOP_ERROR    => Self::Error,
            SCAMPER_HTTP_STOP_TIMEOUT  => Self::Timeout,
            SCAMPER_HTTP_STOP_INSECURE => Self::Insecure,
            other                      => Self::Unknown(other),
        }
    }
}

/// A single HTTP buffer (request or response data segment).
pub struct ScamperHttpBuf { inner: *mut ScamperHttpBufT }

impl ScamperHttpBuf {
    pub(crate) unsafe fn from_ptr(ptr: *mut ScamperHttpBufT) -> Option<Self> {
        if ptr.is_null() { return None; }
        let ptr = unsafe { scamper_http::scamper_http_buf_use(ptr) };
        Some(ScamperHttpBuf { inner: ptr })
    }

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

    pub fn data(&self) -> &[u8] {
        let len = unsafe { scamper_http::scamper_http_buf_len_get(self.inner) } as usize;
        let ptr = unsafe { scamper_http::scamper_http_buf_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_http::scamper_http_buf_len_get(self.inner) } }
    pub fn is_tx(&self) -> bool { unsafe { scamper_http::scamper_http_buf_is_tx(self.inner) != 0 } }
    pub fn is_rx(&self) -> bool { unsafe { scamper_http::scamper_http_buf_is_rx(self.inner) != 0 } }
    pub fn is_tls(&self) -> bool { unsafe { scamper_http::scamper_http_buf_is_tls(self.inner) != 0 } }
    pub fn is_hdr(&self) -> bool { unsafe { scamper_http::scamper_http_buf_is_hdr(self.inner) != 0 } }
    pub fn is_data(&self) -> bool { unsafe { scamper_http::scamper_http_buf_is_data(self.inner) != 0 } }
}

impl Drop for ScamperHttpBuf {
    fn drop(&mut self) { unsafe { scamper_http::scamper_http_buf_free(self.inner) }; }
}
unsafe impl Send for ScamperHttpBuf {}
unsafe impl Sync for ScamperHttpBuf {}

/// A scamper HTTP measurement result.
pub struct ScamperHttp {
    pub(crate) inner: *mut ScamperHttpT,
}

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

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

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

    pub fn userid(&self) -> u32 { unsafe { scamper_http::scamper_http_userid_get(self.inner) } }
    pub fn sport(&self) -> u16 { unsafe { scamper_http::scamper_http_sport_get(self.inner) } }
    pub fn dport(&self) -> u16 { unsafe { scamper_http::scamper_http_dport_get(self.inner) } }

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

    pub fn stop(&self) -> HttpStopReason {
        HttpStopReason::from(unsafe { scamper_http::scamper_http_stop_get(self.inner) })
    }

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

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

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

    pub fn bufc(&self) -> u32 { unsafe { scamper_http::scamper_http_bufc_get(self.inner) } }

    pub fn buf(&self, i: u32) -> Option<ScamperHttpBuf> {
        let ptr = unsafe { scamper_http::scamper_http_buf_get(self.inner, i) };
        unsafe { ScamperHttpBuf::from_ptr(ptr) }
    }

    pub fn status_code(&self) -> Option<u16> {
        let mut status = 0u16;
        let r = unsafe { scamper_http::scamper_http_status_code_get(self.inner, &mut status) };
        if r == 0 { Some(status) } else { None }
    }

    pub fn url(&self) -> Option<String> {
        let mut len = 0usize;
        let r = unsafe { scamper_http::scamper_http_url_len_get(self.inner, &mut len) };
        if r != 0 || len == 0 { return None; }
        let mut buf = vec![0u8; len + 1];
        unsafe { scamper_http::scamper_http_url_get(self.inner, buf.as_mut_ptr() as *mut libc::c_char, buf.len()) };
        CStr::from_bytes_until_nul(&buf).ok()
            .and_then(|s| s.to_str().ok())
            .map(|s| s.to_owned())
    }

    pub fn rx_hdr(&self) -> Option<Vec<u8>> {
        let mut len = 0usize;
        let r = unsafe { scamper_http::scamper_http_rx_hdr_len_get(self.inner, &mut len) };
        if r != 0 || len == 0 { return None; }
        let mut buf = vec![0u8; len];
        unsafe { scamper_http::scamper_http_rx_hdr_get(self.inner, buf.as_mut_ptr(), buf.len()) };
        Some(buf)
    }

    pub fn rx_data(&self) -> Option<Vec<u8>> {
        let mut len = 0usize;
        let r = unsafe { scamper_http::scamper_http_rx_data_len_get(self.inner, &mut len) };
        if r != 0 || len == 0 { return None; }
        let mut buf = vec![0u8; len];
        unsafe { scamper_http::scamper_http_rx_data_get(self.inner, buf.as_mut_ptr(), buf.len()) };
        Some(buf)
    }

    pub fn rx_hdr_name(&self, name: &str) -> Option<String> {
        let cname = CString::new(name).ok()?;
        let mut value: *mut libc::c_char = std::ptr::null_mut();
        let r = unsafe {
            scamper_http::scamper_http_rx_hdr_name_get(self.inner, cname.as_ptr(), &mut value)
        };
        if r != 0 || value.is_null() { return None; }
        let s = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
        unsafe { libc::free(value as *mut libc::c_void) };
        Some(s)
    }

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

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

impl Drop for ScamperHttp {
    fn drop(&mut self) {
        unsafe { scamper_http::scamper_http_free(self.inner) };
    }
}

unsafe impl Send for ScamperHttp {}
unsafe impl Sync for ScamperHttp {}