use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Value
{
None,
U8(u8),
U16(u16),
U32(u32),
U64(u64),
Usize(usize),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
Isize(isize),
F32(f32),
F64(f64),
Bool(bool),
Pointer(usize),
RawString(Vec<u8>),
CString(Vec<u8>),
String(Vec<u8>)
}
#[derive(Serialize, Deserialize)]
pub enum Type
{
None,
U8,
U16,
U32,
U64,
Usize,
I8,
I16,
I32,
I64,
Isize,
F32,
F64,
Bool,
Pointer
}
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::ffi::value::{Type, Value};
use crate::ffi::errors::FFIError;
#[test]
fn signedIntegers() -> ()
{
ffi!{
let libc: Library = Library::load("libc.so.6")?;
let resI8: Value = libc.call("abs", vec![Value::I8(-5)], Type::I8)?;
let resI16: Value = libc.call("abs", vec![Value::I16(-15)], Type::I16)?;
assert!(matches!(resI8, Value::I8(5)));
assert!(matches!(resI16, Value::I16(15)));
let resI32: Value = libc.call("abs", vec![Value::I32(-42)], Type::I32)?;
let resI64: Value = libc.call("labs", vec![Value::I64(-100000)], Type::I64)?;
let resIsize: Value = libc.call("labs", vec![Value::Isize(-500)], Type::Isize)?;
assert!(matches!(resI32, Value::I32(42)));
assert!(matches!(resI64, Value::I64(100000)));
assert!(matches!(resIsize, Value::Isize(500)));
Ok(())
}.expect("Signed integers test failed");
}
#[test]
fn unsignedIntegers() -> ()
{
ffi!{
let libc: Library = Library::load("libc.so.6")?;
let resU8: Value = libc.call("strnlen", vec![Value::CString(b"a".to_vec()), Value::U8(10)], Type::U8)?;
let resU16: Value = libc.call("strnlen", vec![Value::CString(b"ab".to_vec()), Value::U16(10)], Type::U16)?;
let resU32: Value = libc.call("strnlen", vec![Value::CString(b"abc".to_vec()), Value::U32(10)], Type::U32)?;
assert!(matches!(resU8, Value::U8(1)));
assert!(matches!(resU16, Value::U16(2)));
assert!(matches!(resU32, Value::U32(3)));
let resU64: Value = libc.call("strnlen", vec![Value::CString(b"abcd".to_vec()), Value::U64(10)], Type::U64)?;
let resUsize: Value = libc.call("strnlen", vec![Value::CString(b"abcde".to_vec()), Value::Usize(10)], Type::Usize)?;
assert!(matches!(resU64, Value::U64(4)));
assert!(matches!(resUsize, Value::Usize(5)));
Ok(())
}.expect("Unsigned integers test failed");
}
#[test]
fn float() -> ()
{
let resultF32: Value = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let args: Vec<Value> = vec![Value::F32(16.0)];
Ok(libm.call("sqrtf", args, Type::F32)?)
}.expect("FFI F32 call failed");
if let Value::F32(val) = resultF32 {
assert!((val - 4.0).abs() < f32::EPSILON);
} else {
panic!("Expected Value::F32");
}
let resultF64: Value = ffi!{
let libm: Library = Library::load("libm.so.6")?;
let args: Vec<Value> = vec![Value::F64(2.0), Value::F64(3.0)];
Ok(libm.call("pow", args, Type::F64)?)
}.expect("FFI F64 call failed");
if let Value::F64(val) = resultF64 {
assert!((val - 8.0).abs() < f64::EPSILON);
} else {
panic!("Expected Value::F64");
}
}
#[test]
fn bool() -> ()
{
let resultBool: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let args: Vec<Value> = vec![Value::Bool(true)];
Ok(libc.call("isalpha", args, Type::Bool)?)
}.expect("FFI Bool call failed");
if let Value::Bool(val) = resultBool {
assert!(!val);
} else {
panic!("Expected Value::Bool");
}
}
#[test]
fn pointer() -> ()
{
let result: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let args: Vec<Value> = vec![Value::CString(b"noSuchVar".to_vec())];
Ok(libc.call("getenv", args, Type::Pointer)?)
}.expect("FFI pointer call failed");
match result {
Value::Pointer(addr) => assert_eq!(addr, 0),
_ => panic!("Expected Value::Pointer"),
}
}
#[test]
fn pointerRoundtrip() -> ()
{
let len: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let source: Vec<Value> = vec![Value::CString(b"hello".to_vec())];
let ptr: Value = libc.call("strdup", source, Type::Pointer)?;
let Value::Pointer(addr) = ptr else { return Err(FFIError::Other("expected pointer".into())) };
assert_ne!(addr, 0);
let result: Value = libc.call("strlen", vec![Value::Pointer(addr)], Type::Usize)?;
libc.call("free", vec![Value::Pointer(addr)], Type::None)?;
Ok(result)
}.expect("pointer roundtrip failed");
assert!(matches!(len, Value::Usize(5)));
}
#[test]
fn cString() -> ()
{
let result: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let args: Vec<Value> = vec![Value::CString(b"hello".to_vec())];
Ok(libc.call("strlen", args, Type::Usize)?)
}.expect("FFI CString call failed");
if let Value::Usize(len) = result {
assert_eq!(len, 5);
} else {
panic!("Expected Value::Usize");
}
}
#[test]
fn string() -> ()
{
let result: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let args: Vec<Value> = vec![Value::String(b"hello world".to_vec())];
Ok(libc.call("strnlen", args, Type::Usize)?)
}.expect("FFI String call failed");
if let Value::Usize(len) = result {
assert_eq!(len, 11);
} else {
panic!("Expected Value::Usize");
}
}
#[test]
fn rawString() -> ()
{
let result: Value = ffi!{
let libc: Library = Library::load("libc.so.6")?;
let args: Vec<Value> = vec![Value::RawString(b"12345\0".to_vec())];
Ok(libc.call("atoi", args, Type::I32)?)
}.expect("FFI RawString call failed");
if let Value::I32(val) = result {
assert_eq!(val, 12345);
} else {
panic!("Expected Value::I32");
}
}
}