i2pd-sys 0.0.6

Raw FFI bindings to a minimal C shim over libi2pd (PurpleI2P/i2pd)
Documentation
//! Every shim entry point null-checks its arguments before touching any i2pd global state.
//!
//! Nothing here calls `i2pd_init`/`i2pd_start` — that is the point: none of these calls should
//! reach i2pd's internals, so they are safe to run as ordinary parallel `#[test]` functions
//! (unlike `tests/local_lifecycle.rs`, which is one combined test for that reason).
#![allow(unsafe_code, clippy::missing_safety_doc)]

use i2pd_sys::*;
use std::os::raw::c_void;
use std::ptr;

#[test]
fn null_pointer_returns_null() {
    let hash = [0u8; 32];
    unsafe {
        assert!(i2pd_destination_b32_address(ptr::null_mut()).is_null());
        assert!(i2pd_create_stream(ptr::null_mut(), hash.as_ptr(), 1).is_null());
        assert!(i2pd_create_stream(ptr::null_mut(), ptr::null(), 1).is_null());
        assert!(i2pd_create_persistent_destination(ptr::null(), 0, 1, ptr::null()).is_null());
        assert!(i2pd_create_persistent_destination(hash.as_ptr(), 0, 1, ptr::null()).is_null());
    }
}

#[test]
fn null_pointer_returns_failure() {
    let mut out = [0u8; 32];
    let mut out_buf: *mut u8 = ptr::null_mut();
    let mut out_len: usize = 0;
    unsafe {
        assert_eq!(i2pd_destination_ident_hash(ptr::null_mut(), out.as_mut_ptr()), 0);
        assert_eq!(i2pd_destination_ident_hash(ptr::null_mut(), ptr::null_mut()), 0);
        assert_eq!(i2pd_generate_keys(7, 4, ptr::null_mut(), &mut out_len), 0);
        assert_eq!(i2pd_generate_keys(7, 4, &mut out_buf, ptr::null_mut()), 0);
        assert_eq!(i2pd_stream_is_open(ptr::null_mut()), 0);
    }
}

#[test]
fn null_stream_returns_error() {
    let payload = b"hello";
    let mut buf = [0u8; 16];
    unsafe {
        assert_eq!(i2pd_stream_send(ptr::null_mut(), payload.as_ptr(), payload.len()), -1);
        assert_eq!(i2pd_stream_receive(ptr::null_mut(), buf.as_mut_ptr(), buf.len(), 1), -1);
    }
}

#[test]
fn null_pointer_is_noop() {
    unsafe extern "C" fn cb(_ctx: *mut c_void, _stream: *mut I2pdStream) {
        panic!("callback must never fire for a null destination");
    }
    unsafe {
        i2pd_init(ptr::null());
        i2pd_destroy_destination(ptr::null_mut());
        i2pd_accept_stream(ptr::null_mut(), Some(cb), ptr::null_mut());
        i2pd_accept_stream(ptr::null_mut(), None, ptr::null_mut());
        i2pd_stream_close(ptr::null_mut());
        i2pd_destroy_stream(ptr::null_mut());
        i2pd_free_string(ptr::null_mut());
        i2pd_free_buffer(ptr::null_mut(), 0);
        i2pd_free_buffer(ptr::null_mut(), 64);
    }
}

#[test]
fn malformed_keys_buffer_is_rejected() {
    let garbage = [0xAAu8; 64];
    unsafe {
        assert!(
            i2pd_create_persistent_destination(garbage.as_ptr(), garbage.len(), 1, ptr::null()).is_null()
        );
    }
}

#[test]
fn transit_support_matches_the_selected_feature() {
    let reported = unsafe { i2pd_accepts_transit() };
    assert_eq!(
        reported,
        i32::from(cfg!(feature = "transit")),
        "i2pd_accepts_transit() reported {reported}, which disagrees with the `transit` feature -- \
         the I2PD_SYS_NO_TRANSIT define is not reaching both the shim and libi2pd"
    );
}

#[test]
fn participation_setters_before_init_are_noops() {
    unsafe {
        i2pd_set_accepts_transit(1);
        i2pd_set_accepts_transit(0);
        i2pd_set_bandwidth_limit(1024);
        i2pd_set_bandwidth_limit(0);
        i2pd_set_bandwidth_limit(-5);
        i2pd_set_share_percent(50);
        i2pd_set_share_percent(-10);
        i2pd_set_share_percent(1000);
        i2pd_set_max_transit_tunnels(64);
        i2pd_set_max_transit_tunnels(0);
        i2pd_set_max_transit_tunnels(-1);
        i2pd_set_floodfill(0);
    }
}