knet-bindings 2.0.0-alpha1+1.90.647-0fc83

Rust bindings for Kronosnet libraries
Documentation
// libknet interface for Rust
// Copyright (c) 2021 Red Hat, Inc.
//
// All rights reserved.
//
// Author: Christine Caulfield (ccaulfi@redhat.com)
//
#![allow(clippy::too_many_arguments)]
#![allow(clippy::collapsible_else_if)]

// For the code generated by bindgen
use crate::sys::libknet as ffi;

use std::ffi::{CString, CStr};
use std::sync::mpsc::*;
use std::ptr::{copy_nonoverlapping, null, null_mut};
use std::sync::Mutex;
use std::collections::HashMap;
use std::io::{Result, Error, ErrorKind};
use std::os::raw::{c_void, c_char, c_uchar, c_uint};
use std::mem::size_of;
use std::net::SocketAddr;
use std::fmt;
use std::thread::spawn;
use std::time::{Duration, SystemTime};
use os_socketaddr::OsSocketAddr;

#[derive(Copy, Clone, PartialEq)]
/// The ID of a host known to knet.
pub struct HostId {
    host_id: u16,
}
impl HostId {
    pub fn new(id: u16) -> HostId
    {
	HostId{host_id: id}
    }
    pub fn to_u16(self: HostId) -> u16
    {
	self.host_id
    }
}
impl fmt::Display for HostId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	write!(f,"{}", self.host_id)?;
	Ok(())
    }
}

pub enum TxRx {
    Tx = 0,
    Rx = 1
}

impl TxRx {
    pub fn new (tx_rx: u8) -> TxRx
    {
	match tx_rx {
	    1 => TxRx::Rx,
	    _ => TxRx::Tx
	}
    }
}
impl fmt::Display for TxRx {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	match self {
	    TxRx::Tx => write!(f, "Tx"),
	    TxRx::Rx => write!(f, "Rx"),
	}
    }
}

bitflags! {
/// Flags passed into [handle_new]
    pub struct HandleFlags: u64
    {
        const PRIVILEGED = 1;
	const NONE = 0;
    }
}

bitflags! {
/// Flags passed into [link_set_config]
    pub struct LinkFlags: u64
    {
        const TRAFFICHIPRIO = 1;
	const NONE = 0;
    }
}


/// for passing to [handle_crypto_set_config]
pub struct CryptoConfig<'a> {
    pub crypto_model: String,
    pub crypto_cipher_type: String,
    pub crypto_hash_type: String,
    pub private_key: &'a [u8],
}

/// for passing to [handle_compress]
pub struct CompressConfig {
    pub compress_model: String,
    pub compress_threshold: u32,
    pub compress_level: i32,
}

/// Return value from packet filter
pub enum FilterDecision {
    Discard,
    Unicast,
    Multicast
}
impl FilterDecision {
    pub fn to_i32(self: &FilterDecision) -> i32
    {
	match self {
	    FilterDecision::Discard => -1,
	    FilterDecision::Unicast => 0,
	    FilterDecision::Multicast => 1,
	}
    }
}
impl fmt::Display for FilterDecision {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	match self {
	    FilterDecision::Discard => write!(f, "Discard"),
	    FilterDecision::Unicast => write!(f, "Unicast"),
	    FilterDecision::Multicast => write!(f, "Multicast"),
	}
    }
}

// Used to convert a knet_handle_t into one of ours
lazy_static! {
    static ref HANDLE_HASH: Mutex<HashMap<u64, PrivHandle>> = Mutex::new(HashMap::new());
}

fn get_errno() -> i32
{
    match Error::last_os_error().raw_os_error() {
	Some(e) => e,
	None => libc::EINVAL,
    }
}


/// Callback from [handle_enable_sock_notify]
pub type SockNotifyFn = fn(private_data: u64,
			   datafd: i32,
			   channel: i8,
			   txrx: TxRx,
			   Result<()>);

/// Callback called when packets arrive/are sent [handle_enable_filter]
pub type FilterFn = fn(private_data: u64,
		       outdata: &[u8],
		       txrx: TxRx,
		       this_host_id: HostId,
		       src_host_id: HostId,
		       channel: &mut i8,
		       dst_host_ids: &mut Vec<HostId>) -> FilterDecision;

/// Callback called when PMTU changes, see [handle_enable_pmtud_notify]
pub type PmtudNotifyFn = fn(private_data: u64,
			    data_mtu: u32);


/// Called when the onwire version number for a node changes, see [handle_enable_onwire_ver_notify]
pub type OnwireNotifyFn = fn(private_data: u64,
			     onwire_min_ver: u8,
			     onwire_max_ver: u8,
			     onwire_ver: u8);

/// Called when a host status changes, see [host_enable_status_change_notify]
pub type HostStatusChangeNotifyFn = fn(private_data: u64,
				       host_id: HostId,
				       reachable: bool,
				       remote: bool,
				       external: bool);

/// Called when a link status changes, see [link_enable_status_change_notify]
pub type LinkStatusChangeNotifyFn = fn(private_data: u64,
				       host_id: HostId,
				       link_id: u8,
				       connected: bool,
				       remote: bool,
				       external: bool);


// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_sock_notify_fn(
    private_data: *mut c_void,
    datafd: i32,
    channel: i8,
    tx_rx: u8,
    error: i32,
    errorno: i32)
{
    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	let res = if error == 0 {
	    Ok(())
	} else {
	    Err(Error::from_raw_os_error(errorno))
	};
	// Call user fn
	if let Some(f) = h.sock_notify_fn {
	    f(h.sock_notify_private_data,
	      datafd,
	      channel,
	      TxRx::new(tx_rx),
	      res);
	}
    }
}

// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_filter_fn(
    private_data: *mut c_void,
    outdata: *const  c_uchar,
    outdata_len: isize,
    tx_rx: u8,
    this_host_id: u16,
    src_host_id: u16,
    channel: *mut i8,
    dst_host_ids: *mut u16,
    dst_host_ids_entries: *mut usize) -> i32
{
    let mut ret : i32 = -1;

    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	// Is there is filter fn?
	if let Some(f) = h.filter_fn {
	    let data : &[u8] = unsafe {
		std::slice::from_raw_parts(outdata as *const u8, outdata_len as usize)
	    };
	    let mut r_channel = unsafe {*channel};
	    let mut hosts_vec = Vec::<HostId>::new();

	    // Call Rust callback
	    ret = f(h.filter_private_data,
		    data,
		    TxRx::new(tx_rx),
		    HostId{host_id: this_host_id},
		    HostId{host_id: src_host_id},
		    &mut r_channel,
		    &mut hosts_vec).to_i32();

	    // Pass back mutable params dst_hosts
	    unsafe {
		*channel = r_channel;
		*dst_host_ids_entries = hosts_vec.len();
		let mut retvec = dst_host_ids;
		for i in &hosts_vec {
		    *retvec = i.host_id;
		    retvec = retvec.offset(1); // next entry
		}
	    }
	}
    }
    ret
}

// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_pmtud_notify_fn(
    private_data: *mut c_void,
    data_mtu: u32)
{
    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	// Call user fn
	if let Some(f) = h.pmtud_notify_fn {
	    f(h.pmtud_notify_private_data, data_mtu);
	}
    }
}


// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_onwire_notify_fn(
    private_data: *mut c_void,
    onwire_min_ver: u8,
    onwire_max_ver: u8,
    onwire_ver: u8)
{
    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	// Call user fn
	if let Some(f) = h.onwire_notify_fn {
	    f(h.onwire_notify_private_data,
	      onwire_min_ver,
	      onwire_max_ver,
	      onwire_ver);
	}
    }
}

// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_host_status_change_notify_fn(
    private_data: *mut c_void,
    host_id: u16,
    reachable: u8,
    remote: u8,
    external: u8)
{
    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	// Call user fn
	if let Some(f) = h.host_status_change_notify_fn {
	    f(h.host_status_change_notify_private_data,
	      HostId{host_id},
	      crate::u8_to_bool(reachable),
	      crate::u8_to_bool(remote),
	      crate::u8_to_bool(external));
	}
    }
}

// Called from knet, we work out where to route it to and convert params
extern "C" fn rust_link_status_change_notify_fn(
    private_data: *mut c_void,
    host_id: u16,
    link_id: u8,
    connected: u8,
    remote: u8,
    external: u8)
{
    if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) {
	// Call user fn
	if let Some(f) = h.link_status_change_notify_fn {
	    f(h.link_status_change_notify_private_data,
	      HostId{host_id},
	      link_id,
	      crate::u8_to_bool(connected),
	      crate::u8_to_bool(remote),
	      crate::u8_to_bool(external));
	}
    }
}

// Logging thread
fn logging_thread(knet_pipe: i32, sender: Sender<LogMsg>)
{
    let mut logbuf = ffi::knet_log_msg {msg: [0; 254],
					subsystem: 0,
					msglevel: 0,
					knet_h: 0 as ffi::knet_handle_t};
    // Make it blocking
    unsafe { libc::fcntl(knet_pipe, libc::F_SETFL,
			 libc::fcntl(knet_pipe, libc::F_GETFL, 0) & !libc::O_NONBLOCK)};


    loop {
	let msglen = unsafe {libc::read(knet_pipe, &mut logbuf as *mut _ as *mut c_void,
					size_of::<ffi::knet_log_msg>())};
	if msglen < 1 {
	    unsafe { libc::close(knet_pipe); }
	    // EOF on pipe, handle is closed.
	    return;
	}
	if msglen == size_of::<ffi::knet_log_msg>() as isize {
	    let rmsg = LogMsg {
		msg: crate::string_from_bytes_safe(logbuf.msg.as_ptr(), 254),
		subsystem: SubSystem::new(logbuf.subsystem),
		level: LogLevel::new(logbuf.msglevel),
		handle: Handle{knet_handle: logbuf.knet_h as u64}};

	    if let Err(e) = sender.send(rmsg) {
		println!("Error sending log message: {}", e);
	    }
	}
    }
}


#[derive(Copy, Clone, PartialEq)]
#[repr(transparent)]
/// a handle into the knet library, returned from [handle_new]
pub struct Handle {
    knet_handle: u64,
}

// Private version of knet handle, contains all the callback data so
// we only need to access it in the calback functions, making the rest
// a bit quicker & neater
struct PrivHandle {
    log_fd: i32,
    sock_notify_fn: Option<SockNotifyFn>,
    sock_notify_private_data: u64,
    filter_fn: Option<FilterFn>,
    filter_private_data: u64,
    pmtud_notify_fn: Option<PmtudNotifyFn>,
    pmtud_notify_private_data: u64,
    onwire_notify_fn: Option<OnwireNotifyFn>,
    onwire_notify_private_data: u64,
    host_status_change_notify_fn: Option<HostStatusChangeNotifyFn>,
    host_status_change_notify_private_data: u64,
    link_status_change_notify_fn: Option<LinkStatusChangeNotifyFn>,
    link_status_change_notify_private_data: u64,
}

/// A knet logging message returned down the log_sender channel set in [handle_new]
pub struct LogMsg {
    pub msg: String,
    pub subsystem: SubSystem,
    pub level: LogLevel,
    pub handle: Handle,
}

/// Initialise the knet library, returns a handle for use with the other API calls
pub fn handle_new(host_id: &HostId,
		  log_sender: Option<Sender<LogMsg>>,
		  default_log_level: LogLevel,
		  flags: HandleFlags) -> Result<Handle>
{
    // If a log sender was passed, make an FD & thread for knet
    let log_fd = match log_sender {
	Some(s) => {
	    let mut pipes = [0i32; 2];
	    if unsafe {libc::pipe(pipes.as_mut_ptr())} != 0 {
		return Err(Error::last_os_error());
	    }
	    spawn(move || logging_thread(pipes[0], s));
	    pipes[1]
	},
	None => 0
    };

    let res = unsafe {
	ffi::knet_handle_new(host_id.host_id,
			     log_fd,
			     default_log_level.to_u8(),
			     flags.bits)
    };

    if res.is_null() {
	Err(Error::last_os_error())
    } else {
	let rhandle = PrivHandle{log_fd,
				 sock_notify_fn: None,
				 sock_notify_private_data: 0u64,
				 filter_fn: None,
				 filter_private_data: 0u64,
				 pmtud_notify_fn: None,
				 pmtud_notify_private_data: 0u64,
				 onwire_notify_fn: None,
				 onwire_notify_private_data: 0u64,
				 host_status_change_notify_fn: None,
				 host_status_change_notify_private_data: 0u64,
				 link_status_change_notify_fn: None,
				 link_status_change_notify_private_data: 0u64,

	};
	HANDLE_HASH.lock().unwrap().insert(res as u64, rhandle);
	Ok(Handle{knet_handle: res as u64})
    }
}

/// Finish with knet, frees the handle returned by [handle_new]
pub fn handle_free(handle: Handle) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_free(handle.knet_handle as ffi::knet_handle_t)
    };

    if res == 0 {
	// Close the log fd as knet doesn't "do ownership" and this will shut down
	// our logging thread.
	if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	    unsafe {
		libc::close(h.log_fd);
	    };
	}

	HANDLE_HASH.lock().unwrap().remove(&handle.knet_handle);
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable notifications of socket state changes, set callback to 'None' to disable
pub fn handle_enable_sock_notify(handle: Handle,
				 private_data: u64,
				 sock_notify_fn: Option<SockNotifyFn>) -> Result<()>
{
    let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.sock_notify_private_data = private_data;
	    h.sock_notify_fn = sock_notify_fn;
	    match sock_notify_fn {
		Some(_f) =>
		    unsafe {
			ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t,
							    handle.knet_handle as *mut c_void,
							    Some(rust_sock_notify_fn))
		    },
		None =>
		    unsafe {
			ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t,
							    handle.knet_handle as *mut c_void,
							    None)
		    },
	    }
	},
	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Add a data FD to knet. if datafd is 0 then knet will allocate one for you.
pub fn handle_add_datafd(handle: Handle, datafd: i32, channel: i8) -> Result<(i32, i8)>
{
    let mut c_datafd = datafd;
    let mut c_channel = channel;
    let res = unsafe {
	ffi::knet_handle_add_datafd(handle.knet_handle as ffi::knet_handle_t,
				    &mut c_datafd,
				    &mut c_channel)
    };
    if res == 0 {
	Ok((c_datafd, c_channel))
    } else {
	Err(Error::last_os_error())
    }
}

/// Remove a datafd from knet
pub fn handle_remove_datafd(handle: Handle, datafd: i32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_remove_datafd(handle.knet_handle as ffi::knet_handle_t,
				       datafd)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Returns the channel associated with data fd
pub fn handle_get_channel(handle: Handle, datafd: i32) -> Result<i8>
{
    let mut c_channel = 0i8;
    let res = unsafe {
	ffi::knet_handle_get_channel(handle.knet_handle as ffi::knet_handle_t,
				     datafd, &mut c_channel)
    };
    if res == 0 {
	Ok(c_channel)
    } else {
	Err(Error::last_os_error())
    }
}

/// Returns the data FD associated with a channel
pub fn handle_get_datafd(handle: Handle, channel: i8) -> Result<i32>
{
    let mut c_datafd = 0i32;
    let res = unsafe {
	ffi::knet_handle_get_datafd(handle.knet_handle as ffi::knet_handle_t,
				     channel, &mut c_datafd)
    };
    if res == 0 {
	Ok(c_datafd)
    } else {
	Err(Error::last_os_error())
    }
}

#[derive(Copy, Clone, PartialEq)]
pub enum DefragReclaimPolicy {
    Average = 0,
    Absolute = 1
}

impl DefragReclaimPolicy {
    pub fn new (policy: u32) -> DefragReclaimPolicy {
	{
	    match policy {
		1 => DefragReclaimPolicy::Absolute,
		_ => DefragReclaimPolicy::Average,
	    }
	}
    }
    pub fn to_u32 (&self) -> u32 {
	{
	    match self {
		DefragReclaimPolicy::Absolute => 1,
		DefragReclaimPolicy::Average => 0,
	    }
	}
    }
}

impl fmt::Display for DefragReclaimPolicy {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	match self {
	    DefragReclaimPolicy::Absolute => write!(f, "Absolute"),
	    DefragReclaimPolicy::Average => write!(f, "Average"),
	}
    }
}

/// Configure the defrag buffer parameters - applies to all hosts
pub fn handle_set_host_defrag_bufs(handle: Handle, min_defrag_bufs: u16, max_defrag_bufs: u16,
				   shrink_threshold: u8,
				   reclaim_policy: DefragReclaimPolicy) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_set_host_defrag_bufs(handle.knet_handle as ffi::knet_handle_t,
					      min_defrag_bufs, max_defrag_bufs,
					      shrink_threshold,
					      reclaim_policy.to_u32())
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Get the defrag buffer parameters.
/// Returns (min_defrag_bufs, max_defrag_bufs, shrink_threshold, usage_samples, usage_samples_timespan, reclaim_policy)
pub fn handle_get_host_defrag_bufs(handle: Handle) -> Result<(u16, u16, u8, DefragReclaimPolicy)>
{
    let mut min_defrag_bufs: u16 = 0;
    let mut max_defrag_bufs: u16 = 0;
    let mut shrink_threshold: u8 = 0;
    let mut reclaim_policy: u32 = 0;
    let res = unsafe {
	ffi::knet_handle_get_host_defrag_bufs(handle.knet_handle as ffi::knet_handle_t,
					      &mut min_defrag_bufs, &mut max_defrag_bufs,
					      &mut shrink_threshold,
					      &mut reclaim_policy)
    };
    if res == 0 {
	Ok((min_defrag_bufs, max_defrag_bufs,
	    shrink_threshold,
	    DefragReclaimPolicy::new(reclaim_policy)))
    } else {
	Err(Error::last_os_error())
    }
}

/// Receive messages from knet
pub fn recv(handle: Handle, buf: &[u8], channel: i8) -> Result<isize>
{
    let res = unsafe {
	ffi::knet_recv(handle.knet_handle as ffi::knet_handle_t,
		       buf.as_ptr() as *mut c_char,
		       buf.len(),
		       channel)
    };
    if res >= 0 {
	Ok(res)
    } else {
	if get_errno() == libc::EAGAIN {
	    Err(Error::new(ErrorKind::WouldBlock, "Try again"))
	} else {
	    Err(Error::last_os_error())
	}
    }
}

/// Send messages knet
pub fn send(handle: Handle, buf: &[u8], channel: i8) -> Result<isize>
{
    let res = unsafe {
	ffi::knet_send(handle.knet_handle as ffi::knet_handle_t,
		       buf.as_ptr() as *const c_char,
		       buf.len(),
		       channel)
    };
    if res >= 0 {
	Ok(res)
    } else {
	if get_errno() == libc::EAGAIN {
	    Err(Error::new(ErrorKind::WouldBlock, "Try again"))
	} else {
	    Err(Error::last_os_error())
	}
    }
}

/// Send messages to knet and wait till they have gone
pub fn send_sync(handle: Handle, buf: &[u8], channel: i8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_send_sync(handle.knet_handle as ffi::knet_handle_t,
			    buf.as_ptr() as *const c_char,
			    buf.len(),
			    channel)
    };
    if res == 0 {
	Ok(())
    } else {
	if get_errno() == libc::EAGAIN {
	    Err(Error::new(ErrorKind::WouldBlock, "Try again"))
	} else {
	    Err(Error::last_os_error())
	}
    }
}

/// Enable the packet filter. pass 'None' as the callback to disable.
pub fn handle_enable_filter(handle: Handle,
			    private_data: u64,
			    filter_fn: Option<FilterFn>) -> Result<()>
{
    let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.filter_private_data = private_data;
	    h.filter_fn = filter_fn;
	    match filter_fn {
		Some(_f) =>
		    unsafe {
			ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t,
						       handle.knet_handle as *mut c_void,
						       Some(rust_filter_fn))
		    },
		None =>
		    unsafe {
			ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t,
						       handle.knet_handle as *mut c_void,
						       None)
		    },
	    }
	},

	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Set timer resolution
pub fn handle_set_threads_timer_res(handle: Handle, timeres: u32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_set_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, timeres)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get timer resolution
pub fn handle_get_threads_timer_res(handle: Handle) -> Result<u32>
{
    let mut c_timeres: u32 = 0;
    let res = unsafe {
	ffi::knet_handle_get_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, &mut c_timeres)
    };
    if res == 0 {
	Ok(c_timeres)
    } else {
	Err(Error::last_os_error())
    }
}



/// Starts traffic moving. You must call this before knet will do anything.
pub fn handle_setfwd(handle: Handle, enabled: bool) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_setfwd(handle.knet_handle as ffi::knet_handle_t,
				enabled as c_uint)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable access control lists
pub fn handle_enable_access_lists(handle: Handle, enabled: bool) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_enable_access_lists(handle.knet_handle as ffi::knet_handle_t,
					     enabled as c_uint)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Set frequency that PMTUd will check for MTU changes. value in milliseconds
pub fn handle_pmtud_setfreq(handle: Handle, interval: u32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_pmtud_setfreq(handle.knet_handle as ffi::knet_handle_t,
				       interval)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get frequency that PMTUd will check for MTU changes. value in milliseconds
pub fn handle_pmtud_getfreq(handle: Handle) -> Result<u32>
{
    let mut c_interval = 0u32;
    let res = unsafe {
	ffi::knet_handle_pmtud_getfreq(handle.knet_handle as ffi::knet_handle_t,
				       &mut c_interval)
    };
    if res == 0 {
	Ok(c_interval)
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the current MTU
pub fn handle_pmtud_get(handle: Handle) -> Result<u32>
{
    let mut c_mtu = 0u32;
    let res = unsafe {
	ffi::knet_handle_pmtud_get(handle.knet_handle as ffi::knet_handle_t,
				   &mut c_mtu)
    };
    if res == 0 {
	Ok(c_mtu)
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the interface MTU (this should not be necessary)
pub fn handle_pmtud_set(handle: Handle, iface_mtu: u32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_pmtud_set(handle.knet_handle as ffi::knet_handle_t,
				   iface_mtu)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable notification of MTU changes
pub fn handle_enable_pmtud_notify(handle: Handle,
				  private_data: u64,
				  pmtud_notify_fn: Option<PmtudNotifyFn>) -> Result<()>
{
    let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.pmtud_notify_private_data = private_data;
	    h.pmtud_notify_fn = pmtud_notify_fn;
	    match pmtud_notify_fn {
		Some(_f) =>
		    unsafe {
			ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t,
							     handle.knet_handle as *mut c_void,
							     Some(rust_pmtud_notify_fn))
		    },
		None =>
		    unsafe {
			ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t,
							     handle.knet_handle as *mut c_void,
							     None)
		    },
	    }
	},
	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Configure cryptographic seetings for packets being transmitted
pub fn handle_crypto_set_config(handle: Handle, config: &CryptoConfig, config_num: u8) -> Result<()>
{
    let mut crypto_cfg = ffi::knet_handle_crypto_cfg {
	crypto_model: [0; 16],
	crypto_cipher_type: [0; 16],
	crypto_hash_type: [0; 16],
	private_key: [0; 4096],
	private_key_len: 0,
    };

    if config.private_key.len() > 4096 {
	return Err(Error::new(ErrorKind::Other, "key too long"));
    }

    crate::string_to_bytes(&config.crypto_model, &mut crypto_cfg.crypto_model)?;
    crate::string_to_bytes(&config.crypto_cipher_type, &mut crypto_cfg.crypto_cipher_type)?;
    crate::string_to_bytes(&config.crypto_hash_type, &mut crypto_cfg.crypto_hash_type)?;
    unsafe {
	// NOTE param order is 'wrong-way round' from C
	copy_nonoverlapping(config.private_key.as_ptr(), crypto_cfg.private_key.as_mut_ptr(), config.private_key.len());
    }
    crypto_cfg.private_key_len = config.private_key.len() as u32;

    let res = unsafe {
	ffi::knet_handle_crypto_set_config(handle.knet_handle as ffi::knet_handle_t,
					   &mut crypto_cfg,
					   config_num)
    };
    if res == 0 {
	Ok(())
    } else {
	if res == -2 {
	    Err(Error::new(ErrorKind::Other, "Other cryto error"))
	} else {
	    Err(Error::last_os_error())
	}
    }
}

/// Whether to allow or disallow clear-text traffic when crypto is enabled with [handle_crypto_rx_clear_traffic]
pub enum RxClearTraffic {
    Allow = 0,
    Disallow = 1,
}

/// Enable or disable clear-text traffic when crypto is enabled
pub fn handle_crypto_rx_clear_traffic(handle: Handle, value: RxClearTraffic) -> Result<()>
{
    let c_value : u8 =
	match value {
	    RxClearTraffic::Allow => 0,
	    RxClearTraffic::Disallow => 1
	};

    let res = unsafe {
	ffi::knet_handle_crypto_rx_clear_traffic(handle.knet_handle as ffi::knet_handle_t,
						 c_value)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Tell knet which crypto settings to use
pub fn handle_crypto_use_config(handle: Handle, config_num: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_crypto_use_config(handle.knet_handle as ffi::knet_handle_t,
					   config_num)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Set up packet compression
pub fn handle_compress(handle: Handle, config: &CompressConfig) -> Result<()>
{
    let mut compress_cfg = ffi::knet_handle_compress_cfg {
	compress_model: [0; 16],
	compress_threshold : config.compress_threshold,
	compress_level : config.compress_level
    };

    if config.compress_model.len() > 16 {
	return Err(Error::new(ErrorKind::Other, "key too long"));
    }

    crate::string_to_bytes(&config.compress_model, &mut compress_cfg.compress_model)?;

    let res = unsafe {
	ffi::knet_handle_compress(handle.knet_handle as ffi::knet_handle_t,
				  &mut compress_cfg)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Stats for the knet handle
pub type HandleStats = ffi::knet_handle_stats;

impl HandleStats {
    pub fn new() -> HandleStats
    {
	HandleStats {
	    size: 0,
	    tx_uncompressed_packets: 0,
	    tx_compressed_packets: 0,
	    tx_compressed_original_bytes: 0,
	    tx_compressed_size_bytes: 0,
	    tx_compress_time_ave: 0,
	    tx_compress_time_min: 0,
	    tx_compress_time_max: 0,
	    tx_failed_to_compress: 0,
	    tx_unable_to_compress: 0,
	    rx_compressed_packets: 0,
	    rx_compressed_original_bytes: 0,
	    rx_compressed_size_bytes: 0,
	    rx_compress_time_ave: 0,
	    rx_compress_time_min: 0,
	    rx_compress_time_max: 0,
	    rx_failed_to_decompress: 0,
	    tx_crypt_packets: 0,
	    tx_crypt_byte_overhead: 0,
	    tx_crypt_time_ave: 0,
	    tx_crypt_time_min: 0,
	    tx_crypt_time_max: 0,
	    rx_crypt_packets: 0,
	    rx_crypt_time_ave: 0,
	    rx_crypt_time_min: 0,
	    rx_crypt_time_max: 0,
	}
    }
}
impl Default for ffi::knet_handle_stats {
    fn default() -> Self {
	ffi::knet_handle_stats::new()
    }
}

impl fmt::Display for HandleStats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	write!(f, "{}, ", self.tx_uncompressed_packets)?;
	write!(f, "{}, ", self.tx_compressed_packets)?;
	write!(f, "{}, ", self.tx_compressed_original_bytes)?;
	write!(f, "{}, ", self.tx_compressed_size_bytes)?;
	write!(f, "{}, ", self.tx_compress_time_ave)?;
	write!(f, "{}, ", self.tx_compress_time_min)?;
	write!(f, "{}, ", self.tx_compress_time_max)?;
	write!(f, "{}, ", self.tx_failed_to_compress)?;
	write!(f, "{}, ", self.tx_unable_to_compress)?;
	write!(f, "{}, ", self.rx_compressed_packets)?;
	write!(f, "{}, ", self.rx_compressed_original_bytes)?;
	write!(f, "{}, ", self.rx_compressed_size_bytes)?;
	write!(f, "{}, ", self.rx_compress_time_ave)?;
	write!(f, "{}, ", self.rx_compress_time_min)?;
	write!(f, "{}, ", self.rx_compress_time_max)?;
	write!(f, "{}, ", self.rx_failed_to_decompress)?;
	write!(f, "{}, ", self.tx_crypt_packets)?;
	write!(f, "{}, ", self.tx_crypt_byte_overhead)?;
	write!(f, "{}, ", self.tx_crypt_time_ave)?;
	write!(f, "{}, ", self.tx_crypt_time_min)?;
	write!(f, "{}, ", self.tx_crypt_time_max)?;
	write!(f, "{}, ", self.rx_crypt_packets)?;
	write!(f, "{}, ", self.rx_crypt_time_ave)?;
	write!(f, "{}, ", self.rx_crypt_time_min)?;
	write!(f, "{}, ", self.rx_crypt_time_max)?;
	Ok(())
    }
}

/// Return statistics for this knet handle
pub fn handle_get_stats(handle: Handle) -> Result<HandleStats>
{
    let (res, stats) = unsafe {
	let mut c_stats = HandleStats::new();
	let res = ffi::knet_handle_get_stats(handle.knet_handle as ffi::knet_handle_t,
					     &mut c_stats, size_of::<HandleStats>());
	(res, c_stats)
    };
    if res == 0 {
	Ok(stats)
    } else {
	Err(Error::last_os_error())
    }
}

/// Tell [handle_clear_stats] whether to cleat all stats or just handle stats
pub enum ClearStats {
    Handle = 1,
    HandleAndLink = 2,
}

/// Clear statistics
pub fn handle_clear_stats(handle: Handle, clear_options: ClearStats) -> Result<()>
{
    let c_value : i32 =
	match clear_options {
	    ClearStats::Handle => 1,
	    ClearStats::HandleAndLink => 2
	};

    let res = unsafe {
	ffi::knet_handle_clear_stats(handle.knet_handle as ffi::knet_handle_t,
				     c_value)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Crypto info returned from [get_crypto_list]
pub struct CryptoInfo
{
    pub name: String,
    pub properties: u8, // Unused
}
impl CryptoInfo
{
    pub fn new(c_info: ffi::knet_crypto_info) -> CryptoInfo
    {
	let cstr = unsafe {CStr::from_ptr(c_info.name) };
	let name = match cstr.to_str() {
	    Ok(s) => s.to_string(),
	    Err(e) => e.to_string(),
	};
	CryptoInfo {properties: 0,
		    name}
    }
}

/// Get a list of valid crypto options
pub fn get_crypto_list() -> Result<Vec<CryptoInfo>>
{
    let mut list_entries: usize = 256;
    let mut c_list : [ffi::knet_crypto_info; 256] =
	[ ffi::knet_crypto_info{name: null(), properties: 0u8, pad:[0; 256]}; 256];

    let res = unsafe {
	ffi::knet_get_crypto_list(&mut c_list[0],
				  &mut list_entries)
    };
    if res == 0 {
	let mut retvec = Vec::<CryptoInfo>::new();
	for i in c_list.iter().take(list_entries) {
	    retvec.push(CryptoInfo::new(*i));
	}
	Ok(retvec)
    } else {
	Err(Error::last_os_error())
    }
}


/// Compressions types returned from [get_compress_list]
pub struct CompressInfo
{
    pub name: String,
    pub properties: u8, // Unused
}
impl CompressInfo
{
    pub fn new(c_info: ffi::knet_compress_info) -> CompressInfo
    {
	let cstr = unsafe {CStr::from_ptr(c_info.name) };
	let name = match cstr.to_str() {
	    Ok(s) => s.to_string(),
	    Err(e) => e.to_string(),
	};
	CompressInfo {properties: 0,
		      name}
    }
}

/// Return a list of compression options
pub fn get_compress_list() -> Result<Vec<CompressInfo>>
{
    let mut list_entries: usize = 256;
    let mut c_list : [ffi::knet_compress_info; 256] =
	[ ffi::knet_compress_info{name: null(), properties: 0u8, pad:[0; 256]}; 256];

    let res = unsafe {
	ffi::knet_get_compress_list(&mut c_list[0],
				  &mut list_entries)
    };
    if res == 0 {
	let mut retvec = Vec::<CompressInfo>::new();
	for i in c_list.iter().take(list_entries) {
	    retvec.push(CompressInfo::new(*i));
	}
	Ok(retvec)
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable callback when the onwire version for a node changes
pub fn handle_enable_onwire_ver_notify(handle: Handle,
				       private_data: u64,
				       onwire_notify_fn: Option<OnwireNotifyFn>) -> Result<()>
{
    // This looks a bit different to the other _enable*_notify calls because knet
    // calls the calback function in the API. Which results in a deadlock with our
    // own mutex
    match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.onwire_notify_private_data = private_data;
	    h.onwire_notify_fn = onwire_notify_fn;
	},
	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    let res = match onwire_notify_fn {
	Some(_f) =>
	    unsafe {
		ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t,
							  handle.knet_handle as *mut c_void,
							  Some(rust_onwire_notify_fn))
	    },
	None =>
	    unsafe {
		ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t,
							  handle.knet_handle as *mut c_void,
							  None)
	    },
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Get the onsure version for a node
pub fn handle_get_onwire_ver(handle: Handle, host_id: &HostId) -> Result<(u8,u8,u8)>
{
    let mut onwire_min_ver = 0u8;
    let mut onwire_max_ver = 0u8;
    let mut onwire_ver = 0u8;
    let res = unsafe {
	ffi::knet_handle_get_onwire_ver(handle.knet_handle as ffi::knet_handle_t,
					host_id.host_id,
					&mut onwire_min_ver,
					&mut onwire_max_ver,
					&mut onwire_ver)
    };
    if res == 0 {
	Ok((onwire_min_ver, onwire_max_ver, onwire_ver))
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the onsire version for this node
pub fn handle_set_onwire_ver(handle: Handle, onwire_ver: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_set_onwire_ver(handle.knet_handle as ffi::knet_handle_t,
					onwire_ver)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the reconnect interval.
pub fn handle_set_transport_reconnect_interval(handle: Handle, msecs: u32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_handle_set_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t,
							  msecs)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the reconnect interval.
pub fn handle_get_transport_reconnect_interval(handle: Handle) -> Result<u32>
{
    let mut msecs = 0u32;
    let res = unsafe {
	ffi::knet_handle_get_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t,
							  &mut msecs)
    };
    if res == 0 {
	Ok(msecs)
    } else {
	Err(Error::last_os_error())
    }
}

/// Add a new host ID
pub fn host_add(handle: Handle, host_id: &HostId) -> Result<()>
{
    let res = unsafe {
	ffi::knet_host_add(handle.knet_handle as ffi::knet_handle_t,
			   host_id.host_id)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Remove a Host ID
pub fn host_remove(handle: Handle, host_id: &HostId) -> Result<()>
{
    let res = unsafe {
	ffi::knet_host_remove(handle.knet_handle as ffi::knet_handle_t,
			      host_id.host_id)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the name of a host
pub fn host_set_name(handle: Handle, host_id: &HostId, name: &str) -> Result<()>
{

    let c_name = CString::new(name)?;
    let res = unsafe {
	ffi::knet_host_set_name(handle.knet_handle as ffi::knet_handle_t,
				host_id.host_id, c_name.as_ptr())
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

const KNET_MAX_HOST_LEN:usize = 256;
const KNET_MAX_PORT_LEN:usize = 6;
/// Retrieve the name of a host given its ID
pub fn host_get_name_by_host_id(handle: Handle, host_id: &HostId) -> Result<String>
{
    let mut c_name: [c_char; KNET_MAX_HOST_LEN] = [0; KNET_MAX_HOST_LEN];
    let res = unsafe {
	ffi::knet_host_get_name_by_host_id(handle.knet_handle as ffi::knet_handle_t,
					   host_id.host_id, c_name.as_mut_ptr())
    };
    if res == 0 {
	crate::string_from_bytes(c_name.as_ptr(), KNET_MAX_HOST_LEN)
    } else {
	Err(Error::last_os_error())
    }
}


/// Return the ID of a host given its name
pub fn host_get_id_by_host_name(handle: Handle, name: &str) -> Result<HostId>
{

    let c_name = CString::new(name)?;
    let mut c_host_id = 0u16;
    let res = unsafe {
	ffi::knet_host_get_id_by_host_name(handle.knet_handle as ffi::knet_handle_t,
					   c_name.as_ptr(), &mut c_host_id)
    };
    if res == 0 {
	Ok(HostId{host_id: c_host_id})
    } else {
	Err(Error::last_os_error())
    }
}

const KNET_MAX_HOST: usize = 65536;
/// Return a list of host IDs known to this handle
pub fn host_get_host_list(handle: Handle) -> Result<Vec<HostId>>
{
    let mut c_host_ids: [u16; KNET_MAX_HOST] = [0; KNET_MAX_HOST];
    let mut c_host_ids_entries: usize = 0;
    let res = unsafe {
	ffi::knet_host_get_host_list(handle.knet_handle as ffi::knet_handle_t,
				     &mut c_host_ids[0], &mut c_host_ids_entries)
    };
    if res == 0 {
	let mut host_vec = Vec::<HostId>::new();
	for i in c_host_ids.iter().take(c_host_ids_entries) {
	    host_vec.push(HostId {host_id: *i});
	}
	Ok(host_vec)
    } else {
	Err(Error::last_os_error())
    }
}

/// Link Policies for [host_set_policy]
#[derive(Copy, Clone, PartialEq)]
pub enum LinkPolicy {
    Passive,
    Active,
    Rr,
}

impl LinkPolicy{
    pub fn new(value: u8) -> LinkPolicy
    {
	match value {
	    2 => LinkPolicy::Rr,
	    1 => LinkPolicy::Active,
	    _ => LinkPolicy::Passive,
	}
    }
    pub fn to_u8(self: LinkPolicy) -> u8
    {
	match self {
	    LinkPolicy::Passive => 0,
	    LinkPolicy::Active => 1,
	    LinkPolicy::Rr => 2,
	}
    }
}
impl fmt::Display for LinkPolicy {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	match self {
	    LinkPolicy::Passive => write!(f, "Passive"),
	    LinkPolicy::Active => write!(f, "Active"),
	    LinkPolicy::Rr => write!(f, "RR"),
	}
    }

}

/// Set the policy for this host, this only makes sense if multiple links between hosts are configured
pub fn host_set_policy(handle: Handle, host_id: &HostId, policy: LinkPolicy) -> Result<()>
{
    let c_value: u8 = policy.to_u8();

    let res = unsafe {
	ffi::knet_host_set_policy(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, c_value)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Return the current link policy for a node
pub fn host_get_policy(handle: Handle, host_id: &HostId) -> Result<LinkPolicy>
{
    let mut c_value: u8 = 0;
    let res = unsafe {
	ffi::knet_host_get_policy(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, &mut c_value)
    };
    if res == 0 {
	Ok(LinkPolicy::new(c_value))
    } else {
	Err(Error::last_os_error())
    }
}

/// Current status of a host. remote  & reachable are current not used
pub struct HostStatus
{
    pub reachable: bool,
    pub remote: bool,
    pub external: bool,
}
impl fmt::Display for HostStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	write!(f, "reachable: {}, ", self.reachable)?;
	write!(f, "remote: {}, ", self.remote)?;
	write!(f, "external: {}", self.external)?;
	Ok(())
    }
}


/// Return the current status of a host
pub fn host_get_status(handle: Handle, host_id: &HostId) -> Result<HostStatus>
{
    let mut c_value = ffi::knet_host_status { reachable:0, remote:0, external:0};
    let res = unsafe {
	ffi::knet_host_get_status(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, &mut c_value)
    };
    if res == 0 {
	Ok(HostStatus {
	    reachable: crate::u8_to_bool(c_value.reachable),
	    remote: crate::u8_to_bool(c_value.remote),
	    external: crate::u8_to_bool(c_value.external)
	})
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable callbacks when the status of a host changes
pub fn host_enable_status_change_notify(handle: Handle,
					private_data: u64,
					host_status_change_notify_fn: Option<HostStatusChangeNotifyFn>) -> Result<()>
{
    let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.host_status_change_notify_private_data = private_data;
	    h.host_status_change_notify_fn = host_status_change_notify_fn;
	    match host_status_change_notify_fn {
		Some(_f) =>
		    unsafe {
			ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t,
								   handle.knet_handle as *mut c_void,
								   Some(rust_host_status_change_notify_fn))
		    },
		None =>
		    unsafe {
			ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t,
								   handle.knet_handle as *mut c_void,
								   None)
		    },
	    }
	},
	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Transport types supported in knet
pub enum TransportId {
    Loopback,
    Udp,
    Sctp,
}
impl TransportId {
    pub fn new(id: u8) -> TransportId
    {
	match id {
	    2 => TransportId::Sctp,
	    1 => TransportId::Udp,
	    _ => TransportId::Loopback,
	}
    }
    pub fn to_u8(self: &TransportId) -> u8
    {
	match self {
	    TransportId::Loopback => 0,
	    TransportId::Udp => 1,
	    TransportId::Sctp => 2,
	}
    }

    pub fn to_string(self: &TransportId) -> String
    {
	match self {
	    TransportId::Udp => "UDP".to_string(),
	    TransportId::Sctp => "SCTP".to_string(),
	    TransportId::Loopback => "Loopback".to_string()
	}
    }
    pub fn from_string(name: String) -> TransportId
    {
	match name.as_str() {
	    "UDP" => TransportId::Udp,
	    "SCTP" => TransportId::Sctp,
	    "Loopback" => TransportId::Loopback,
	    _ => TransportId::Loopback,
	}
    }
}

/// Transport info returned from [get_transport_list]
pub struct TransportInfo
{
    pub name: String,
    pub id: TransportId,
    pub properties: u8, // currently unused
}

// Controversially implementing name_by_id and id_by_name here
impl TransportInfo
{
    pub fn new(c_info: ffi::knet_transport_info) -> TransportInfo
    {
	let cstr = unsafe {CStr::from_ptr(c_info.name) };
	let name = match cstr.to_str() {
	    Ok(s) => s.to_string(),
	    Err(e) => e.to_string(),
	};
	TransportInfo {properties: 0,
		       id: TransportId::new(c_info.id),
		       name}
    }
}

pub fn get_transport_list() -> Result<Vec<TransportInfo>>
{
    let mut list_entries: usize = 256;
    let mut c_list : [ffi::knet_transport_info; 256] =
	[ ffi::knet_transport_info{name: null(), id: 0u8, properties: 0u8, pad:[0; 256]}; 256];

    let res = unsafe {
	ffi::knet_get_transport_list(&mut c_list[0],
				     &mut list_entries)
    };
    if res == 0 {
	let mut retvec = Vec::<TransportInfo>::new();
	for i in c_list.iter().take(list_entries) {
	    retvec.push(TransportInfo::new(*i));
	}
	Ok(retvec)
    } else {
	Err(Error::last_os_error())
    }
}

/// Configure a link to a host ID. dst_addr may be None for a dynamic link.
pub fn link_set_config(handle: Handle, host_id: &HostId, link_id: u8,
		       transport: TransportId,
		       src_addr: &SocketAddr, dst_addr: Option<&SocketAddr>, flags: LinkFlags) -> Result<()>
{
    // Not really mut, but C is dumb
    let mut c_srcaddr = make_new_sockaddr_storage(src_addr);

    // dst_addr can be NULL/None if this is a dynamic link
    let res = if let Some(dst) = dst_addr {
	let mut c_dstaddr = make_new_sockaddr_storage(dst);

	unsafe {
	    ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t,
				      host_id.host_id, link_id,
				      transport.to_u8(),
				      &mut c_srcaddr,
				      &mut c_dstaddr,
				      flags.bits)
	}
    } else {
	unsafe {
	    ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t,
				      host_id.host_id, link_id,
				      transport.to_u8(),
				      &mut c_srcaddr,
				      null_mut(),
				      flags.bits)
	}
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Return a link's configuration
pub fn link_get_config(handle: Handle, host_id: &HostId, link_id: u8) ->
		       Result<(TransportId, Option<SocketAddr>, Option<SocketAddr>, LinkFlags)>
{
    let mut c_srcaddr = OsSocketAddr::new();
    let mut c_dstaddr = OsSocketAddr::new();
    let mut c_transport = 0u8;
    let mut c_flags = 0u64;
    let mut c_dynamic = 0u8;
    let res = unsafe {
	ffi::knet_link_get_config(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, link_id,
				  &mut c_transport,
				  c_srcaddr.as_mut_ptr() as *mut ffi::sockaddr_storage,
				  c_dstaddr.as_mut_ptr() as *mut ffi::sockaddr_storage,
				  &mut c_dynamic,
				  &mut c_flags)
    };
    if res == 0 {
	let r_transport = TransportId::new(c_transport);
	Ok((r_transport, c_srcaddr.into(), c_dstaddr.into(), LinkFlags{bits:c_flags}))
    } else {
	Err(Error::last_os_error())
    }
}

/// Clear a link configuration.
pub fn link_clear_config(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_clear_config(handle.knet_handle as ffi::knet_handle_t,
				    host_id.host_id, link_id)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Type of ACL
pub enum AclAcceptReject
{
    Accept,
    Reject,
}
impl AclAcceptReject {
    pub fn new(ar: u32) -> AclAcceptReject
    {
	match ar {
	    ffi::CHECK_ACCEPT => AclAcceptReject::Accept,
	    ffi::CHECK_REJECT => AclAcceptReject::Reject,
	    _ => AclAcceptReject::Reject,
	}
    }
    pub fn to_u32(self: &AclAcceptReject) -> u32
    {
	match self {
	    AclAcceptReject::Accept => ffi::CHECK_ACCEPT,
	    AclAcceptReject::Reject => ffi::CHECK_REJECT,
	}
    }
}

/// What the ACL should check
pub enum AclCheckType
{
    Address,
    Mask,
    Range,
}
impl AclCheckType {
    pub fn new(ct: u32) -> AclCheckType
    {
	match ct {
	    ffi::CHECK_TYPE_ADDRESS => AclCheckType::Address,
	    ffi::CHECK_TYPE_MASK => AclCheckType::Mask,
	    ffi::CHECK_TYPE_RANGE => AclCheckType::Range,
	    _ => AclCheckType::Address,
	}
    }
    pub fn to_u32(self: &AclCheckType) -> u32
    {
	match self {
	    AclCheckType::Address => ffi::CHECK_TYPE_ADDRESS,
	    AclCheckType::Mask => ffi::CHECK_TYPE_MASK,
	    AclCheckType::Range => ffi::CHECK_TYPE_RANGE,
	}
    }
}

// We need to have a zeroed-out stackaddr storage to pass to the ACL APIs
// as knet compares the whole sockaddr_storage when using knet_rm_acl()
fn make_new_sockaddr_storage(ss: &SocketAddr) -> ffi::sockaddr_storage
{
    // A blank one
    let mut new_ss = ffi::sockaddr_storage {
	ss_family: 0,
	__ss_padding: [0; 118],
	__ss_align: 0,
    };
    let p_new_ss : *mut ffi::sockaddr_storage = &mut new_ss;

    // Rust only fills in what it thinks is necessary
    let c_ss : OsSocketAddr = (*ss).into();

    // Copy it
    unsafe {
	// Only copy as much as is in the OsSocketAddr
	copy_nonoverlapping(c_ss.as_ptr(),
			    p_new_ss as *mut libc::sockaddr,
			    1);
    }

    new_ss
}


/// Add an ACL to a link, adds the ACL to the end of the list.
pub fn link_add_acl(handle: Handle, host_id: &HostId, link_id: u8,
		    ss1: &SocketAddr, ss2: &SocketAddr,
		    check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()>
{
    // Not really mut, but C is dumb
    let mut c_ss1 = make_new_sockaddr_storage(ss1);
    let mut c_ss2 = make_new_sockaddr_storage(ss2);
    let res = unsafe {
	ffi::knet_link_add_acl(handle.knet_handle as ffi::knet_handle_t,
			       host_id.host_id, link_id,
			       &mut c_ss1,
			       &mut c_ss2,
			       check_type.to_u32(), acceptreject.to_u32())

    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Insert an ACL anywhere in the ACL list for this host/link
pub fn link_insert_acl(handle: Handle, host_id: &HostId, link_id: u8,
		       index: i32,
		       ss1: &SocketAddr, ss2: &SocketAddr,
		       check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()>
{
    // Not really mut, but C is dumb
    let mut c_ss1 = make_new_sockaddr_storage(ss1);
    let mut c_ss2 = make_new_sockaddr_storage(ss2);
    let res = unsafe {
	ffi::knet_link_insert_acl(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, link_id,
				  index,
				  &mut c_ss1,
				  &mut c_ss2,
				  check_type.to_u32(), acceptreject.to_u32())

    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Remove an ACL for this host/link
pub fn link_rm_acl(handle: Handle, host_id: &HostId, link_id: u8,
		   ss1: &SocketAddr, ss2: &SocketAddr,
		   check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()>
{
    // Not really mut, but C is dumb
    let mut c_ss1 = make_new_sockaddr_storage(ss1);
    let mut c_ss2 = make_new_sockaddr_storage(ss2);
    let res = unsafe {
	ffi::knet_link_rm_acl(handle.knet_handle as ffi::knet_handle_t,
			      host_id.host_id, link_id,
			      &mut c_ss1,
			      &mut c_ss2,
			      check_type.to_u32(), acceptreject.to_u32())

    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Clear out all ACLs from this host/link
pub fn link_clear_acl(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_clear_acl(handle.knet_handle as ffi::knet_handle_t,
				 host_id.host_id, link_id)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}


/// Enable/disable a link (you still need to call [handle_setfwd] for traffic to flow
pub fn link_set_enable(handle: Handle, host_id: &HostId, link_id: u8, enable: bool) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_set_enable(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, link_id, enable as u32)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the 'enabled' status for a link
pub fn link_get_enable(handle: Handle, host_id: &HostId, link_id: u8) -> Result<bool>
{
    let mut c_enable = 0u32;
    let res = unsafe {
	ffi::knet_link_get_enable(handle.knet_handle as ffi::knet_handle_t,
				  host_id.host_id, link_id, &mut c_enable)
    };
    if res == 0 {
	Ok(crate::u32_to_bool(c_enable))
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the ping timers for a link
pub fn link_set_ping_timers(handle: Handle, host_id: &HostId, link_id: u8,
			    interval: i64, timeout: i64, precision: u32) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_set_ping_timers(handle.knet_handle as ffi::knet_handle_t,
				       host_id.host_id, link_id,
				       interval, timeout, precision)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the ping timers for a link
pub fn link_get_ping_timers(handle: Handle, host_id: &HostId, link_id: u8) -> Result<(i64, i64, u32)>
{
    let mut c_interval : ffi::time_t = 0;
    let mut c_timeout : ffi::time_t = 0;
    let mut c_precision = 0u32;
    let res = unsafe {
	ffi::knet_link_get_ping_timers(handle.knet_handle as ffi::knet_handle_t,
				       host_id.host_id, link_id,
				       &mut c_interval, &mut c_timeout, &mut c_precision)
    };
    if res == 0 {
	Ok((c_interval as i64, c_timeout as i64, c_precision))
    } else {
	Err(Error::last_os_error())
    }
}

/// Set the pong count for a link
pub fn link_set_pong_count(handle: Handle, host_id: &HostId, link_id: u8,
			   pong_count: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_set_pong_count(handle.knet_handle as ffi::knet_handle_t,
				      host_id.host_id, link_id,
				      pong_count)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the pong count for a link
pub fn link_get_pong_count(handle: Handle, host_id: &HostId, link_id: u8) -> Result<u8>
{
    let mut c_pong_count = 0u8;
    let res = unsafe {
	ffi::knet_link_get_pong_count(handle.knet_handle as ffi::knet_handle_t,
				       host_id.host_id, link_id,
				       &mut c_pong_count)
    };
    if res == 0 {
	Ok(c_pong_count)
    } else {
	Err(Error::last_os_error())
    }
}


/// Set the link priority (only useful with multiple links to a node)
pub fn link_set_priority(handle: Handle, host_id: &HostId, link_id: u8,
			 priority: u8) -> Result<()>
{
    let res = unsafe {
	ffi::knet_link_set_priority(handle.knet_handle as ffi::knet_handle_t,
				    host_id.host_id, link_id,
				    priority)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the link priority
pub fn link_get_priority(handle: Handle, host_id: &HostId, link_id: u8) -> Result<u8>
{
    let mut c_priority = 0u8;
    let res = unsafe {
	ffi::knet_link_get_priority(handle.knet_handle as ffi::knet_handle_t,
				    host_id.host_id, link_id,
				    &mut c_priority)
    };
    if res == 0 {
	Ok(c_priority)
    } else {
	Err(Error::last_os_error())
    }
}

const KNET_MAX_LINK: usize = 8;
/// Get a list of links for this host
pub fn link_get_link_list(handle: Handle, host_id: &HostId) -> Result<Vec<u8>>
{
    let mut c_link_ids: [u8; KNET_MAX_LINK] = [0; KNET_MAX_LINK];
    let mut c_link_ids_entries: usize = 0;
    let res = unsafe {
	ffi::knet_link_get_link_list(handle.knet_handle as ffi::knet_handle_t, host_id.host_id,
				     &mut c_link_ids[0], &mut c_link_ids_entries)
    };
    if res == 0 {
	let mut link_vec = Vec::<u8>::new();
	for i in c_link_ids.iter().take(c_link_ids_entries) {
	    link_vec.push(*i);
	}
	Ok(link_vec)
    } else {
	Err(Error::last_os_error())
    }
}

/// Enable callbacks when a link status changes
pub fn link_enable_status_change_notify(handle: Handle,
					private_data: u64,
					link_status_change_notify_fn: Option<LinkStatusChangeNotifyFn>) -> Result<()>
{
    let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) {
	Some(h) => {
	    h.link_status_change_notify_private_data = private_data;
	    h.link_status_change_notify_fn = link_status_change_notify_fn;
	    match link_status_change_notify_fn {
		Some(_f) =>
		    unsafe {
			ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t,
								   handle.knet_handle as *mut c_void,
								   Some(rust_link_status_change_notify_fn))
		    },
		None =>
		    unsafe {
			ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t,
								   handle.knet_handle as *mut c_void,
								   None)
		    },
	    }
	},
	None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")),
    };

    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Link stats
pub struct LinkStats {
    pub tx_data_packets: u64,
    pub rx_data_packets: u64,
    pub tx_data_bytes: u64,
    pub rx_data_bytes: u64,
    pub rx_ping_packets: u64,
    pub tx_ping_packets: u64,
    pub rx_ping_bytes: u64,
    pub tx_ping_bytes: u64,
    pub rx_pong_packets: u64,
    pub tx_pong_packets: u64,
    pub rx_pong_bytes: u64,
    pub tx_pong_bytes: u64,
    pub rx_pmtu_packets: u64,
    pub tx_pmtu_packets: u64,
    pub rx_pmtu_bytes: u64,
    pub tx_pmtu_bytes: u64,
    pub tx_total_packets: u64,
    pub rx_total_packets: u64,
    pub tx_total_bytes: u64,
    pub rx_total_bytes: u64,
    pub tx_total_errors: u64,
    pub tx_total_retries: u64,
    pub tx_pmtu_errors: u32,
    pub tx_pmtu_retries: u32,
    pub tx_ping_errors: u32,
    pub tx_ping_retries: u32,
    pub tx_pong_errors: u32,
    pub tx_pong_retries: u32,
    pub tx_data_errors: u32,
    pub tx_data_retries: u32,
    pub latency_min: u32,
    pub latency_max: u32,
    pub latency_ave: u32,
    pub latency_samples: u32,
    pub down_count: u32,
    pub up_count: u32,
    pub last_up_times: Vec<SystemTime>,
    pub last_down_times: Vec<SystemTime>,
}
// Quick & Dirty printing
impl fmt::Display for LinkStats {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	write!(f, "{}, ", self.tx_data_packets)?;
	write!(f, "{}, ", self.rx_data_packets)?;
	write!(f, "{}, ", self.tx_data_bytes)?;
	write!(f, "{}, ", self.rx_data_bytes)?;
	write!(f, "{}, ", self.rx_ping_packets)?;
	write!(f, "{}, ", self.tx_ping_packets)?;
	write!(f, "{}, ", self.rx_ping_bytes)?;
	write!(f, "{}, ", self.tx_ping_bytes)?;
	write!(f, "{}, ", self.rx_pong_packets)?;
	write!(f, "{}, ", self.tx_pong_packets)?;
	write!(f, "{}, ", self.rx_pong_bytes)?;
	write!(f, "{}, ", self.tx_pong_bytes)?;
	write!(f, "{}, ", self.rx_pmtu_packets)?;
	write!(f, "{}, ", self.tx_pmtu_packets)?;
	write!(f, "{}, ", self.rx_pmtu_bytes)?;
	write!(f, "{}, ", self.tx_pmtu_bytes)?;
	write!(f, "{}, ", self.tx_total_packets)?;
	write!(f, "{}, ", self.rx_total_packets)?;
	write!(f, "{}, ", self.tx_total_bytes)?;
	write!(f, "{}, ", self.rx_total_bytes)?;
	write!(f, "{}, ", self.tx_total_errors)?;
	write!(f, "{}, ", self.tx_total_retries)?;
	write!(f, "{}, ", self.tx_pmtu_errors)?;
	write!(f, "{}, ", self.tx_pmtu_retries)?;
	write!(f, "{}, ", self.tx_ping_errors)?;
	write!(f, "{}, ", self.tx_ping_retries)?;
	write!(f, "{}, ", self.tx_pong_errors)?;
	write!(f, "{}, ", self.tx_pong_retries)?;
	write!(f, "{}, ", self.tx_data_errors)?;
	write!(f, "{}, ", self.tx_data_retries)?;
	write!(f, "{}, ", self.latency_min)?;
	write!(f, "{}, ", self.latency_max)?;
	write!(f, "{}, ", self.latency_ave)?;
	write!(f, "{}, ", self.latency_samples)?;
	write!(f, "{}, ", self.down_count)?;
	write!(f, "{}, ", self.up_count)?;
	write!(f, "Last up times: ")?;
	// There's no sensible print for SystemTime in the std library
	// and I don't want to add dependancies here for printing as it
	// mostly going to be the client's responsibility, so use the Debug option
	for i in &self.last_up_times {
	    write!(f, "{:?}", i)?;
	}
	write!(f, " Last down times: ")?;
	for i in &self.last_down_times {
	    write!(f, "{:?}", i)?;
	}
	Ok(())
    }
}

// I wish this all wasn't necessary!
impl ffi::knet_link_stats {
    pub fn new() -> ffi::knet_link_stats {
	ffi::knet_link_stats {
	    tx_data_packets: 0,
	    rx_data_packets: 0,
	    tx_data_bytes: 0,
	    rx_data_bytes: 0,
	    rx_ping_packets: 0,
	    tx_ping_packets: 0,
	    rx_ping_bytes: 0,
	    tx_ping_bytes: 0,
	    rx_pong_packets: 0,
	    tx_pong_packets: 0,
	    rx_pong_bytes: 0,
	    tx_pong_bytes: 0,
	    rx_pmtu_packets: 0,
	    tx_pmtu_packets: 0,
	    rx_pmtu_bytes: 0,
	    tx_pmtu_bytes: 0,
	    tx_total_packets: 0,
	    rx_total_packets: 0,
	    tx_total_bytes: 0,
	    rx_total_bytes: 0,
	    tx_total_errors: 0,
	    tx_total_retries: 0,
	    tx_pmtu_errors: 0,
	    tx_pmtu_retries: 0,
	    tx_ping_errors: 0,
	    tx_ping_retries: 0,
	    tx_pong_errors: 0,
	    tx_pong_retries: 0,
	    tx_data_errors: 0,
	    tx_data_retries: 0,
	    latency_min: 0,
	    latency_max: 0,
	    latency_ave: 0,
	    latency_samples: 0,
	    down_count: 0,
	    up_count: 0,
	    last_up_times: [0; 16],
	    last_down_times: [0; 16],
	    last_up_time_index: 0,
	    last_down_time_index: 0,
	}
    }
}
impl Default for ffi::knet_link_stats {
    fn default() -> Self {
	ffi::knet_link_stats::new()
    }
}
impl ffi::knet_link_status {
    pub fn new()-> ffi::knet_link_status
    {
	ffi::knet_link_status {
	    size: 0,
	    src_ipaddr : [0; KNET_MAX_HOST_LEN],
	    dst_ipaddr : [0; KNET_MAX_HOST_LEN],
	    src_port : [0; KNET_MAX_PORT_LEN],
	    dst_port : [0; KNET_MAX_PORT_LEN],
	    enabled: 0,
	    connected: 0,
	    dynconnected: 0,
	    pong_last: ffi::timespec{ tv_sec: 0, tv_nsec: 0},
	    mtu: 0,
	    proto_overhead: 0,
	    stats: ffi::knet_link_stats::new(),
	}
    }
}
impl Default for ffi::knet_link_status {
    fn default() -> Self {
	ffi::knet_link_status::new()
    }
}

/// Link status (includes a [LinkStats])
pub struct LinkStatus
{
    pub src_ipaddr: String,
    pub dst_ipaddr: String,
    pub src_port: String,
    pub dst_port: String,
    pub enabled: bool,
    pub connected: bool,
    pub dynconnected: bool,
    pub pong_last: SystemTime,
    pub mtu: u32,
    pub proto_overhead: u32,
    pub stats: LinkStats,
}
impl LinkStatus {
    pub fn new(c_stats: ffi::knet_link_status) -> LinkStatus
    {
	LinkStatus {
	    src_ipaddr : crate::string_from_bytes_safe(c_stats.src_ipaddr.as_ptr(), KNET_MAX_HOST_LEN),
	    src_port : crate::string_from_bytes_safe(c_stats.src_port.as_ptr(), KNET_MAX_HOST_LEN),
	    dst_ipaddr : crate::string_from_bytes_safe(c_stats.dst_ipaddr.as_ptr(), KNET_MAX_HOST_LEN),
	    dst_port : crate::string_from_bytes_safe(c_stats.dst_port.as_ptr(), KNET_MAX_HOST_LEN),
	    enabled : crate::u8_to_bool(c_stats.enabled),
	    connected : crate::u8_to_bool(c_stats.connected),
	    dynconnected : crate::u8_to_bool(c_stats.dynconnected),
	    pong_last : systemtime_from_timespec(c_stats.pong_last),
	    mtu : c_stats.mtu,
	    proto_overhead : c_stats.proto_overhead,
	    stats : LinkStats::new(c_stats.stats),
	}
    }
}
impl fmt::Display for LinkStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	write!(f, "src_ip_addr: {}:{}, ", self.src_ipaddr, self.src_port)?;
	write!(f, "dst_ip_addr: {}:{}, ", self.dst_ipaddr, self.src_port)?;
	write!(f, "enabled: {}, connected: {}, mtu: {}, overhead: {}, ",
		 self.enabled, self.connected, self.mtu, self.proto_overhead)?;
	write!(f, "stats: {}", self.stats)?;
	Ok(())
    }
}

fn systemtime_from_time_t(t: u64) -> SystemTime
{
    SystemTime::UNIX_EPOCH+Duration::from_secs(t)
}

fn systemtime_from_timespec(t: ffi::timespec) -> SystemTime
{
    SystemTime::UNIX_EPOCH+Duration::from_secs(t.tv_sec as u64)
	+Duration::from_nanos(t.tv_nsec as u64) // TODO may panic??

}

fn copy_circular_buffer_of_link_events(num: usize, times: &[ffi::time_t]) -> Vec<SystemTime>
{
    let mut times_vec = Vec::<SystemTime>::new();

    for index in (0 .. num).rev() {
	if times[index] == 0 {
	    break
	}
	times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic??
    }
    for index in (num+1 .. MAX_LINK_EVENTS).rev() {
	if times[index] == 0 {
	    break;
	}
	times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic??
    }
    times_vec
}

const MAX_LINK_EVENTS: usize = 16;
impl LinkStats {
    pub fn new(cstats: ffi::knet_link_stats) -> LinkStats
    {
	let up_times = copy_circular_buffer_of_link_events(cstats.last_up_time_index as usize,
							   &cstats.last_up_times);

	let down_times = copy_circular_buffer_of_link_events(cstats.last_down_time_index as usize,
							   &cstats.last_down_times);

	LinkStats {
	    tx_data_packets: cstats.tx_data_packets,
	    rx_data_packets: cstats.rx_data_packets,
	    tx_data_bytes: cstats.tx_data_bytes,
	    rx_data_bytes: cstats.rx_data_bytes,
	    rx_ping_packets: cstats.rx_ping_packets,
	    tx_ping_packets: cstats.tx_ping_packets,
	    rx_ping_bytes: cstats.rx_ping_bytes,
	    tx_ping_bytes: cstats.tx_ping_bytes,
	    rx_pong_packets: cstats.rx_pong_packets,
	    tx_pong_packets: cstats.tx_pong_packets,
	    rx_pong_bytes: cstats.rx_pong_bytes,
	    tx_pong_bytes: cstats.tx_pong_bytes,
	    rx_pmtu_packets: cstats.rx_pmtu_packets,
	    tx_pmtu_packets: cstats.tx_pmtu_packets,
	    rx_pmtu_bytes: cstats.rx_pmtu_bytes,
	    tx_pmtu_bytes: cstats.tx_pmtu_bytes,
	    tx_total_packets: cstats.tx_total_packets,
	    rx_total_packets: cstats.rx_total_packets,
	    tx_total_bytes: cstats.tx_total_bytes,
	    rx_total_bytes: cstats.rx_total_bytes,
	    tx_total_errors: cstats.tx_total_errors,
	    tx_total_retries: cstats.tx_total_retries,
	    tx_pmtu_errors: cstats.tx_pmtu_errors,
	    tx_pmtu_retries: cstats.tx_pmtu_retries,
	    tx_ping_errors: cstats.tx_ping_errors,
	    tx_ping_retries: cstats.tx_ping_retries,
	    tx_pong_errors: cstats.tx_pong_errors,
	    tx_pong_retries: cstats.tx_pong_retries,
	    tx_data_errors: cstats.tx_data_errors,
	    tx_data_retries: cstats.tx_data_retries,
	    latency_min: cstats.latency_min,
	    latency_max: cstats.latency_max,
	    latency_ave: cstats.latency_ave,
	    latency_samples: cstats.latency_samples,
	    down_count: cstats.down_count,
	    up_count: cstats.up_count,
	    last_up_times: up_times,
	    last_down_times: down_times,
	}
    }
}

/// Get the status (and stats) of a link
pub fn link_get_status(handle: Handle, host_id: &HostId, link_id: u8) -> Result<LinkStatus>
{
    let (res, stats) = unsafe {
	let mut c_stats : ffi::knet_link_status = ffi::knet_link_status::new();

	let res = ffi::knet_link_get_status(handle.knet_handle as ffi::knet_handle_t,
					    host_id.host_id, link_id,
					    &mut c_stats, size_of::<ffi::knet_link_status>());
	(res, c_stats)
    };
    if res == 0 {
	let r_status = LinkStatus::new(stats);
	Ok(r_status)
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the logging subsystem ID given its name
pub fn log_get_subsystem_id(name: &str) -> Result<u8>
{
    let c_name = CString::new(name)?;
    let res = unsafe {
	ffi::knet_log_get_subsystem_id(c_name.as_ptr())
    };
    Ok(res)
}

/// Get the logging subsystem name given its ID
pub fn log_get_subsystem_name(id: u8) -> Result<String>
{
    let res = unsafe {
	ffi::knet_log_get_subsystem_name(id)
    };
    crate::string_from_bytes(res, 256)
}

/// Get the name of a logging level
pub fn log_get_loglevel_id(name: &str) -> Result<u8>
{
    let c_name = CString::new(name)?;
    let res = unsafe {
	ffi::knet_log_get_loglevel_id(c_name.as_ptr())
    };
    Ok(res)
}

/// Get the ID of a logging level, given its name
pub fn log_get_loglevel_name(id: u8) -> Result<String>
{
    let res = unsafe {
	ffi::knet_log_get_loglevel_name(id)
    };
    crate::string_from_bytes(res, 256)
}

/// Logging levels
pub enum LogLevel {
    Err,
    Warn,
    Info,
    Debug,
}
impl LogLevel {
    pub fn new(level: u8) -> LogLevel {
	match level {
	    0 => LogLevel::Err,
	    1 => LogLevel::Warn,
	    2 => LogLevel::Info,
	    _ => LogLevel::Debug, // 3=Debug, but default anything to it too
	}
    }
    pub fn to_u8(self: &LogLevel) -> u8
    {
	match self {
	    LogLevel::Err => 0,
	    LogLevel::Warn => 1,
	    LogLevel::Info => 2,
	    LogLevel::Debug => 3,
	}
    }
}
impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
	match self {
	    LogLevel::Err => write!(f, "Err"),
	    LogLevel::Warn => write!(f, "Warn"),
	    LogLevel::Info => write!(f, "Info"),
	    LogLevel::Debug => write!(f, "Debug"),
	}
    }
}

/// Subsystems known to the knet logger
pub enum SubSystem
{
    Common,
    Handle,
    Host,
    Listener,
    Link,
    Transport,
    Crypto,
    Compress,

    Filter,

    Dstcache,
    Heartbeat,
    Pmtud,
    Tx,
    Rx,

    TranspBase,
    TranspLoopback,
    TranspUdp,
    TranspSctp,

    NssCrypto,
    OpensslCrypto,

    Zlibcomp,
    Lz4comp,
    Lz4hccomp,
    Lzo2comp,
    Lzmacomp,
    Bzip2comp,
    Zstdcomp,

    Unknown,
}
impl SubSystem {
    pub fn to_u8(self: &SubSystem) -> u8
    {
	match self {
	    SubSystem::Common => ffi::KNET_SUB_COMMON,
	    SubSystem::Handle => ffi::KNET_SUB_HANDLE,
	    SubSystem::Host => ffi::KNET_SUB_HOST,
	    SubSystem::Listener => ffi::KNET_SUB_LISTENER,
	    SubSystem::Link => ffi::KNET_SUB_LINK,
	    SubSystem::Transport => ffi::KNET_SUB_TRANSPORT,
	    SubSystem::Crypto => ffi::KNET_SUB_CRYPTO,
	    SubSystem::Compress => ffi::KNET_SUB_COMPRESS,
	    SubSystem::Filter => ffi::KNET_SUB_FILTER,
	    SubSystem::Dstcache => ffi::KNET_SUB_DSTCACHE,
	    SubSystem::Heartbeat => ffi::KNET_SUB_HEARTBEAT,
	    SubSystem::Pmtud => ffi::KNET_SUB_PMTUD,
	    SubSystem::Tx => ffi::KNET_SUB_TX,
	    SubSystem::Rx => ffi::KNET_SUB_RX,
	    SubSystem::TranspBase => ffi::KNET_SUB_TRANSP_BASE,
	    SubSystem::TranspLoopback => ffi::KNET_SUB_TRANSP_LOOPBACK,
	    SubSystem::TranspUdp => ffi::KNET_SUB_TRANSP_UDP,
	    SubSystem::TranspSctp => ffi::KNET_SUB_TRANSP_SCTP,
	    SubSystem::NssCrypto => ffi::KNET_SUB_NSSCRYPTO,
	    SubSystem::OpensslCrypto => ffi::KNET_SUB_OPENSSLCRYPTO,
	    SubSystem::Zlibcomp => ffi::KNET_SUB_ZLIBCOMP,
	    SubSystem::Lz4comp => ffi::KNET_SUB_LZ4COMP,
	    SubSystem::Lz4hccomp => ffi::KNET_SUB_LZ4HCCOMP,
	    SubSystem::Lzo2comp => ffi::KNET_SUB_LZO2COMP,
	    SubSystem::Lzmacomp => ffi::KNET_SUB_LZMACOMP,
	    SubSystem::Bzip2comp => ffi::KNET_SUB_BZIP2COMP,
	    SubSystem::Zstdcomp => ffi::KNET_SUB_ZSTDCOMP,
	    SubSystem::Unknown => ffi::KNET_SUB_UNKNOWN,
	}
    }
    pub fn new(subsys: u8) -> SubSystem
    {
	match subsys {
	    1 => SubSystem::Unknown,
	    2 => SubSystem::Unknown,
	    _ => SubSystem::Unknown,
	}
    }

}

/// Set the current logging level
pub fn log_set_loglevel(handle: Handle, subsystem: SubSystem, level: LogLevel) -> Result<()>
{
    let c_level = level.to_u8();
    let c_subsys = subsystem.to_u8();
    let res = unsafe {
	ffi::knet_log_set_loglevel(handle.knet_handle as ffi::knet_handle_t,
				   c_subsys, c_level)
    };
    if res == 0 {
	Ok(())
    } else {
	Err(Error::last_os_error())
    }
}

/// Get the current logging level
pub fn log_get_loglevel(handle: Handle, subsystem: SubSystem) -> Result<LogLevel>
{
    let mut c_level:u8 = 0;
    let c_subsys = subsystem.to_u8();
    let res = unsafe {
	ffi::knet_log_get_loglevel(handle.knet_handle as ffi::knet_handle_t,
				   c_subsys, &mut c_level)
    };
    if res == 0 {
	Ok(LogLevel::new(c_level))
    } else {
	Err(Error::last_os_error())
    }
}