netinfo-ffi 0.5.0

C FFI for netinfo, which groups network usage by process
Documentation
//! This is the C FFI for the [netinfo](https://github.com/ChangSpivey/netinfo) library. See
//! [documentation](https://docs.rs/netinfo) of the main library for more information.

#![allow(non_camel_case_types)]
#[macro_use] extern crate enum_primitive;
extern crate netinfo;

use std::panic;
use std::ffi::CString;
use std::os::raw::*;
use std::mem;
use std::ptr::null_mut;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::time::Duration;
use netinfo::*;
use netinfo::error::Error as LibError;
use enum_primitive::FromPrimitive;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper macros

/// This macro catches a panic and returns ReturnCodes::UnknownError in that case. Otherwise it will
/// just return the given error code as `ni_error_code`.
macro_rules! safe {
    ($($tty:tt)*) => (
        {
            let panic_result: Result<WrapperResult<()>, _> = std::panic::catch_unwind(|| {
                $($tty)*
            });


            match panic_result {
                Ok(closure_result) => ReturnCodes::from(closure_result) as ni_error_code,
                Err(_) => ReturnCodes::UnknownError as ni_error_code,
            }
        }
    );
}


/// This macro checks pointers whether they are 0 pointers or not. If they are, it will return
/// ReturnCodes::NullPointerArgument.
macro_rules! check_ptr {
    ($($i:ident),+) => {
        $(
            if $i == null_mut() { return From::from(ReturnCodes::NullPointerArgument); }
        )*
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// C <-> Rust memory/pointer helpers

fn vec_to_ptr_len<T>(v: Vec<T>) -> (*mut T, usize) {
    let mut b = v.into_boxed_slice();
    let ptr = b.as_mut_ptr();
    let len = b.len();
    std::mem::forget(b);
    (ptr, len)
}

unsafe fn ptr_len_to_vec<T>(ptr: *mut T, len: usize) -> Vec<T> {
    Box::from_raw(std::slice::from_raw_parts_mut(ptr, len)).into_vec()
}

/// Will interpret a pointer as a box but will not destroy that box. This ensures we will never "forget" to write `mem::forget()`.
unsafe fn temp_box<T, A, F: FnOnce(&mut Box<T>) -> WrapperResult<A>>(ptr: *mut c_void, f: F) -> WrapperResult<A> {
    let mut b = Box::from_raw(ptr as *mut T);
    let res = f(&mut b);
    mem::forget(b);
    res
}

/// Will interpret a pointer as a vector but will not destroy that vector. This ensures we will never "forget" to write `mem::forget()`.
unsafe fn temp_vec<T, A, F: FnOnce(&mut Vec<T>) -> WrapperResult<A>>(ptr: *mut T, len: u32, f: F) -> WrapperResult<A> {
    let mut v = ptr_len_to_vec::<T>(ptr, len as usize);
    let res = f(&mut v);
    mem::forget(v);
    res
}

/// This reinterprets the `*mut *mut R` as `Vec<Box<T>>` and creates a new `Vec<T>` containing
/// clones of all elements in the original vector.
unsafe fn temp_vec_of_box_cloned<T: Clone, R>(vec_ptr: *mut *mut R, vec_len: u32) -> WrapperResult<Vec<T>> {
    let mut vec: Vec<T> = Vec::new();

    temp_vec(vec_ptr, vec_len, |vec_of_ptr_elem: &mut Vec<*mut R>| {
        for &mut ptr_elem in vec_of_ptr_elem {
            temp_box(ptr_elem as *mut c_void, |elem_box: &mut Box<T>| {
                vec.push((**elem_box).clone());
                Ok(())
            })?;
        };
        Ok(())
    })?;

    Ok(vec)
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// Types that are shared between netinfo.h and lib.rs

type ni_netinfo = *mut c_void;
type ni_net_interface = *mut c_void;
type ni_net_statistics = *mut c_void;
type ni_error_code = i32;
type ni_boolean = u8;
type ni_pid = u64;
type ni_inout = u32;
type ni_transport_type  = u32;

#[repr(C)]
#[derive(Debug)]
pub struct ni_mac_addr {
  d: [u8; 6],
}

#[repr(C)]
#[derive(Debug)]
pub struct ni_ipv4_addr {
  v4: [u8; 4],
}

#[repr(C)]
#[derive(Debug)]
pub struct ni_ipv6_addr {
  v6: [u8; 16], // generated by https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.octets
}


enum_from_primitive!{
#[repr(C)]
#[derive(Clone, Copy)]
enum ni_inout_enum {
  NETINFO_IOT_INCOMING = 0,
  NETINFO_IOT_OUTGOING = 1,
}
}

enum_from_primitive!{
#[repr(C)]
#[derive(Clone, Copy)]
enum ni_transport_type_enum {
  NETINFO_TT_TCP = 0,
  NETINFO_TT_UDP = 1,
}
}

impl ni_transport_type_enum {
    fn import(int: ni_transport_type) -> WrapperResult<ni_transport_type_enum> {
        ni_transport_type_enum::from_u32(int).ok_or(WrapperError::InvalidTransportType)
    }

    fn to_netinfo_type(&self) -> netinfo::TransportType {
        match *self {
            ni_transport_type_enum::NETINFO_TT_TCP => { netinfo::TransportType::Tcp }
            ni_transport_type_enum::NETINFO_TT_UDP => { netinfo::TransportType::Udp }
        }
    }
}


impl ni_inout_enum {
    fn import(int: ni_inout) -> WrapperResult<ni_inout_enum> {
        ni_inout_enum::from_u32(int).ok_or(WrapperError::InvalidInoutType)
    }

    fn to_netinfo_type(&self) -> netinfo::InoutType {
        match *self {
            ni_inout_enum::NETINFO_IOT_INCOMING => { netinfo::InoutType::Incoming }
            ni_inout_enum::NETINFO_IOT_OUTGOING => { netinfo::InoutType::Outgoing }
        }
    }
}

impl From<MacAddr> for ni_mac_addr {
    fn from(f: MacAddr) -> ni_mac_addr { let m = ni_mac_addr { d: f.into() }; m }
}

impl From<Ipv4Addr> for ni_ipv4_addr {
    fn from(e: Ipv4Addr) -> ni_ipv4_addr { ni_ipv4_addr { v4: e.octets() } }
}

impl From<Ipv6Addr> for ni_ipv6_addr {
    fn from(e: Ipv6Addr) -> ni_ipv6_addr { ni_ipv6_addr { v6: e.octets() } }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Wrapper internal data structures

enum_from_primitive! {
enum ReturnCodes {
    Ok = 0,
    UnknownError = -1,
    UnknownTypeFree = -2,
    NullPointerArgument = -3,
    ChannelCreationError = -4,
    InvalidInoutType = -5,
    InvalidTransportType = -6,
}
}

impl ReturnCodes {
    fn desc(&self) -> &'static str {
        match *self {
            ReturnCodes::Ok => "return value does not indicate an error",
            ReturnCodes::UnknownError => "unkown error",
            ReturnCodes::UnknownTypeFree => "tried to free an unkown type",
            ReturnCodes::NullPointerArgument => "invalid argument: null pointer",
            ReturnCodes::ChannelCreationError => "could not create pcap channel... does program have permission to read traffic?",
            ReturnCodes::InvalidInoutType => "invalid argument: unknown inout type",
            ReturnCodes::InvalidTransportType => "invalid argument: unknown transport type",
        }
    }
}

impl From<ReturnCodes> for ni_error_code {
    fn from(e: ReturnCodes) -> ni_error_code { e as ni_error_code }
}


/// Errors that are not from the main netinfo library but from this wrapper/FFI
#[derive(Debug)]
enum WrapperError {
    NetinfoError(LibError),
    InvalidTransportType,
    InvalidInoutType,
}

type WrapperResult<T> = Result<T, WrapperError>;

impl From<LibError> for WrapperError {
    fn from(e: LibError) -> WrapperError { WrapperError::NetinfoError(e) }
}

impl From<LibError> for ReturnCodes {
    fn from(e: LibError) -> ReturnCodes {
        use netinfo::error::ErrorKind as E;
        match *e.kind() {
            E::ChannelCreationError => ReturnCodes::ChannelCreationError,
            _ => ReturnCodes::UnknownError // TODO
        }
    }
}

impl From<WrapperError> for ReturnCodes {
    fn from(e: WrapperError) -> ReturnCodes {
        match e {
            WrapperError::InvalidInoutType => ReturnCodes::InvalidInoutType,
            WrapperError::InvalidTransportType => ReturnCodes::InvalidTransportType,
            WrapperError::NetinfoError(netinfo_error) => ReturnCodes::from(netinfo_error),
        }
    }
}

impl From<WrapperResult<()>> for ReturnCodes {
    fn from(e: WrapperResult<()>) -> ReturnCodes {
        match e {
            Ok(()) => ReturnCodes::Ok,
            Err(wrapper_error) => ReturnCodes::from(wrapper_error),
        }
    }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Functions to be exported in library

#[no_mangle]
pub unsafe extern fn netinfo_list_net_interface(net_interfaces_array_out: *mut *mut ni_net_interface, num_net_interfaces_out: *mut u32) -> ni_error_code {
    check_ptr!(net_interfaces_array_out, num_net_interfaces_out);
    safe! {
        let net_interfaces: Vec<ni_net_interface> = Netinfo::list_net_interfaces()?
                                                        .into_iter()
                                                        .map(|i| Box::into_raw(Box::new(i)) as ni_net_interface)
                                                        .collect();
        write_vec_to_ptr_len32(net_interfaces, net_interfaces_array_out, num_net_interfaces_out);
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_int_get_name(i: ni_net_interface, name_out: *mut *mut u8, name_len_out: *mut u32) -> ni_error_code {
    check_ptr!(name_out, name_len_out);
    safe! {
        temp_box(i, |i: &mut Box<NetworkInterface>| {
            let name_chars: Vec<u8> = CString::new(i.as_ref().get_name().clone()).unwrap().into_bytes_with_nul();
            write_vec_to_ptr_len32(name_chars, name_out, name_len_out);
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free_string(s: *mut u8, len: u32) -> ni_error_code {
    check_ptr!(s);
    safe! {
        mem::drop(ptr_len_to_vec::<u8>(s, len as usize));
        Ok(())
    }
}


#[no_mangle]
pub unsafe extern fn netinfo_int_get_index(i: ni_net_interface, index_out: *mut u32) -> ni_error_code {
    check_ptr!(index_out);
    safe! {
        temp_box(i, |i: &mut Box<NetworkInterface>| {
            *index_out = i.get_index();
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_int_get_mac(i: ni_net_interface, has_mac_out: *mut ni_boolean, mac_out: *mut ni_mac_addr) -> ni_error_code {
    check_ptr!(has_mac_out, mac_out);
    safe! {
        temp_box(i, |i: &mut Box<NetworkInterface>| {
            match i.get_mac() {
                Some(mac) => {
                    *has_mac_out = 1;
                    *mac_out = From::from(mac);
                }
                None => { *has_mac_out = 0; }
            }
            Ok(())
        })
    }
}

unsafe fn write_vec_to_ptr_len32<T>(v: Vec<T>, ptr_out: *mut *mut T, len_out: *mut u32) {
    let (ptr, len) = vec_to_ptr_len(v);
    *ptr_out = ptr;
    *len_out = len as u32;
}

#[no_mangle]
pub unsafe extern fn netinfo_int_get_ips(i: ni_net_interface, ip4_array_out: *mut *mut ni_ipv4_addr, num_ip4_out: *mut u32, ip6_array_out: *mut *mut ni_ipv6_addr, num_ip6_out: *mut u32) -> ni_error_code {
    check_ptr!(ip4_array_out, num_ip4_out, ip6_array_out, num_ip6_out);
    safe! {
        temp_box(i, |i: &mut Box<NetworkInterface>| {
            let mut ipv4_vec: Vec<ni_ipv4_addr> = Vec::new();
            let mut ipv6_vec: Vec<ni_ipv6_addr> = Vec::new();

            for ip in i.get_ips() {
                match ip {
                    IpAddr::V4(ipv4) => { ipv4_vec.push(ipv4.into()); }
                    IpAddr::V6(ipv6) => { ipv6_vec.push(ipv6.into()); }
                }
            }

            write_vec_to_ptr_len32(ipv4_vec, ip4_array_out, num_ip4_out);
            write_vec_to_ptr_len32(ipv6_vec, ip6_array_out, num_ip6_out);

            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free_ip_arrays(ip4_array: *mut ni_ipv4_addr, len_ipv4: u32, ip6_array: *mut ni_ipv6_addr, len_ipv6: u32) -> ni_error_code {
    safe! {
        ptr_len_to_vec::<ni_ipv4_addr>(ip4_array, len_ipv4 as usize);
        ptr_len_to_vec::<ni_ipv6_addr>(ip6_array, len_ipv6 as usize);
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_int_get_flags(i: ni_net_interface, flags_out: *mut u32) -> ni_error_code {
    check_ptr!(flags_out);
    safe! {
        temp_box(i, |i: &mut Box<NetworkInterface>| {
            *flags_out = i.get_flags();
            Ok(())
        })
    }
}


#[no_mangle]
pub unsafe extern fn netinfo_free_net_interface_array(net_interfaces_array: *mut ni_net_interface, len: u32) -> ni_error_code {
    safe! {
        let vec1: Vec<ni_net_interface> = ptr_len_to_vec::<ni_net_interface>(net_interfaces_array, len as usize);
        let vec2: Vec<Box<NetworkInterface>> = vec1.into_iter().map(|i| Box::from_raw(i as *mut NetworkInterface)).collect();

        mem::drop(vec2);

        Ok(())
    }
}


#[no_mangle]
pub unsafe extern fn netinfo_init(interfaces: *mut ni_net_interface, num_net_interfaces: u32, n_out: *mut ni_netinfo) -> ni_error_code {
    check_ptr!(interfaces, n_out);
    safe! {
        let real_interfaces: Vec<NetworkInterface> = temp_vec_of_box_cloned(interfaces, num_net_interfaces)?;
        let netinfo_object: Netinfo = Netinfo::new(&real_interfaces[..])?;
        *n_out = Box::into_raw(Box::new(netinfo_object)) as ni_netinfo;
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_get_net_statistics(n: ni_netinfo, ns: *mut ni_net_statistics) -> ni_error_code {
    check_ptr!(n);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            let net_stat: NetStatistics = netinfo.get_net_statistics()?;
            *ns = Box::into_raw(Box::new(net_stat)) as ni_net_statistics;
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_clear(n: ni_netinfo) -> ni_error_code {
    check_ptr!(n);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            netinfo.clear()?;
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_start(n: ni_netinfo) -> ni_error_code {
    check_ptr!(n);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            netinfo.start()?;
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stop(n: ni_netinfo) -> ni_error_code {
    check_ptr!(n);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            netinfo.stop()?;
            Ok(())
        })
    }
}

// Please see `https://docs.rs/netinfo` for more information.
#[no_mangle]
pub unsafe extern fn netinfo_set_min_refresh_interval(n: ni_netinfo, activate_min_refresh_interval: ni_boolean, milliseconds: u32) -> ni_error_code {
    check_ptr!(n);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            if activate_min_refresh_interval != 0 {
                netinfo.set_min_refresh_interval(Some(Duration::from_millis(milliseconds as u64)))?;
            } else {
                netinfo.set_min_refresh_interval(None)?;
            }
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_pop_thread_errors(n: ni_netinfo, worker_thread_errors: *mut *mut ni_error_code, num_errors: *mut u32) -> ni_error_code {
    check_ptr!(n, worker_thread_errors, num_errors);
    safe! {
        temp_box(n, |netinfo: &mut Box<Netinfo>| {
            let netinfo_errors = netinfo.pop_thread_errors()?;
            let error_codes: Vec<ni_error_code> = netinfo_errors.into_iter().map(|e: LibError| ni_error_code::from(ReturnCodes::from(e))).collect();
            write_vec_to_ptr_len32(error_codes, worker_thread_errors, num_errors);
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free_thread_error_array(worker_thread_errors: *mut ni_error_code, num_errors: u32) -> ni_error_code {
    safe! {
        ptr_len_to_vec(worker_thread_errors, num_errors as usize);
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free(n: ni_netinfo) -> ni_error_code {
    check_ptr!(n);
    safe! {
        mem::drop(Box::from_raw(n as *mut Netinfo));
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_total_bytes(s: ni_net_statistics, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            *bytes_out = stat.get_total();
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_bytes_per_pid(s: ni_net_statistics, pid: ni_pid, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            *bytes_out = stat.get_bytes_by_pid(pid);
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_unassigned_bytes(s: ni_net_statistics, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            *bytes_out = stat.get_unassigned_bytes();
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_bytes_by_transport_type(s: ni_net_statistics, t: ni_transport_type, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            *bytes_out = stat.get_bytes_by_transport_type(ni_transport_type_enum::import(t)?.to_netinfo_type());
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_bytes_by_inout_type(s: ni_net_statistics, t: ni_inout, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            *bytes_out = stat.get_bytes_by_inout_type(ni_inout_enum::import(t)?.to_netinfo_type());
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_all_pids(s: ni_net_statistics, pid_array_out: *mut *mut ni_pid, num_pids_out: *mut u32) -> ni_error_code {
    check_ptr!(s, pid_array_out, num_pids_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            let vec: Vec<ni_pid> = stat.get_all_pids().into_iter().map(|pid| pid as ni_pid).collect();
            write_vec_to_ptr_len32(vec, pid_array_out, num_pids_out);
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free_pid_array(pid_array: *mut ni_pid, len: u32) -> ni_error_code {
    safe! {
        ptr_len_to_vec(pid_array, len as usize);
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_stat_get_bytes_per_attr(s: ni_net_statistics, pid_opt: *mut ni_pid, inout_opt: *mut ni_inout, tt_opt: *mut ni_transport_type, bytes_out: *mut u64) -> ni_error_code {
    check_ptr!(s, bytes_out);
    safe! {
        temp_box(s, |stat: &mut Box<NetStatistics>| {
            let pid_opt2: Option<u64> = if pid_opt == null_mut() { None } else { Some(*pid_opt) };
            let inout_opt2: Option<InoutType> = if inout_opt == null_mut() { None } else { Some(ni_inout_enum::import(*inout_opt)?.to_netinfo_type()) };
            let tt_opt2: Option<TransportType> = if tt_opt == null_mut() { None } else { Some(ni_transport_type_enum::import(*tt_opt)?.to_netinfo_type()) };
            *bytes_out = stat.get_bytes_by_attr(pid_opt2, inout_opt2, tt_opt2);
            Ok(())
        })
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_free_stat(s: ni_net_statistics) -> ni_error_code {
    check_ptr!(s);
    safe! {
        mem::drop(Box::from_raw(s as *mut NetStatistics));
        Ok(())
    }
}

#[no_mangle]
pub unsafe extern fn netinfo_get_error_description(error_code: ni_error_code) -> *const u8 {
    let res = panic::catch_unwind(|| {
        ReturnCodes::from_i32(error_code)
            .map(|return_code| {
                return_code.desc().as_ptr()
            }).unwrap_or("unknown error code".as_ptr())
    });

    match res {
        Ok(static_ptr) => static_ptr,
        Err(_) => null_mut(),
    }
}