From 90d3c2615fad91ef52f146ad78170fc233632b6a Mon Sep 17 00:00:00 2001 From: Avril Date: Sun, 20 Jun 2021 23:10:35 +0100 Subject: [PATCH] start FFI function implementations --- Cargo.toml | 15 +++++- src/ffi.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++ src/ffi/error.rs | 67 ++++++++++++++++++++++++++ src/ffi/interop.rs | 25 ++++++++++ src/lib.rs | 2 + 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/ffi.rs create mode 100644 src/ffi/error.rs create mode 100644 src/ffi/interop.rs diff --git a/Cargo.toml b/Cargo.toml index df83bb1..8c530db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,17 @@ edition = "2018" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["rlib", "cdylib", "staticlib"] + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +panic = "unwind" + [features] -default = ["smallvec"] +default = ["smallvec", "ffi"] # Enable async version with tokio v2.0 AsyncRead/AsyncWrite. async = ["tokio", "pin-project"] @@ -19,9 +28,13 @@ async = ["tokio", "pin-project"] # Explicitly clear in-memory buffers with `explicit_bzero()` instead of normal `bzero()`. explicit_clear = [] +# Add C interface bindings +ffi = ["libc"] + [dependencies] base64 = "0.13" getrandom = "0.2" +libc = {version = "0.2.97", optional = true} openssl = "0.10" pin-project = {version = "1.0.6", optional = true} serde = {version = "1.0", features = ["derive"], optional = true} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..5e12242 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,115 @@ +//! Raw C interface bindings +//! +//! Low level bindings to create in-memory translators for the cipher stream API. +//! Intended to be linked to the C wrapper object `wrapper.c`. +use super::*; +use std::ptr; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Default)] +#[repr(C)] +struct CKey([u8; key::KEY_SIZE]); + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Default)] +#[repr(C)] +struct CIv([u8; key::IV_SIZE]); + +/// Non-encrypted wrapper +#[derive(Debug)] +#[repr(C)] +struct CPassthrough +{ + backing: *mut libc::FILE, + key: key::Key, + iv: key::IV, + mode: CMode +} + +/// A sink wrapper of `CPassthrough`. +//TODO: Create a Custom Stream in `wrapper.c` that allows creating a FILE* object with `fopencookie()` from this. +#[derive(Debug)] +#[repr(C)] +struct CSink +{ + sink: Sink, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] +#[repr(C)] +enum CMode +{ + Encrypt, + Decrypt, +} + +mod interop; + +mod error; +use error::*; + +#[no_mangle] extern "C" fn cc20_gen_meta(file: *mut libc::FILE, key: *const CKey, iv: *const CIv, mode: CMode, output: *mut CPassthrough) -> i32 +{ + no_unwind!({ + if file.is_null() { + return CErr::InvalidFile; + } + let key = nullchk!(<- key); + let iv = nullchk!(<- iv); + let write = CPassthrough { + backing: file, + key: key::Key::from_bytes(key.0), + iv: key::IV::from_bytes(iv.0), + mode, + }; + unsafe { + nullchk!(output); + output.write(write); + } + CErr::Success + }).unwrap_or(CErr::Panic).into() +} + +/// Create an encrypting `Sink` over a `FILE*`. +#[no_mangle] extern "C" fn cc20_gen_sink(file: *mut libc::FILE, key: *const CKey, iv: *const CIv, mode: CMode) -> *mut CSink +{ + let meta = { + // No need to `no_unwind` this, `cc20_gen_meta` already does it, and nothing else here can panic. + let mut meta: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); + match cc20_gen_meta(file, key, iv, mode, &mut meta as *mut _ as *mut CPassthrough) { + 0 => unsafe { meta.assume_init() }, + _ => return ptr::null_mut(), + } + }; + no_unwind!({ + //TODO: Create CSink from `meta` (`CPassthrough`). + let sink = CSink { + sink: match meta.mode { + CMode::Encrypt => Sink::encrypt(meta, meta.key, meta.iv), + CMode::Decrypt => Sink::decrypt(meta, meta.key, meta.iv), + }, + }; + interop::give(sink) + }).unwrap_or(ptr::null_mut()) +} +/// Create a wrapper `FILE*` that acts as a `Sink` when written to. +#[no_mangle] extern "C" fn cc20_wrap(file: *mut libc::FILE, key: *const CKey, iv: *const CIv, mode: CMode) -> *mut libc::FILE +{ + // No need to `no_unwind` this, nothing here can panic. + let csink = cc20_gen_sink(file, key, iv, mode); + if csink.is_null() { + return ptr::null_mut(); + } + cc20_wrap_sink(csink) +} +/// Closes the wrapper `sink`, and writes inner `FILE*` pointer to `file`, if `file` is non-null. +#[no_mangle] extern "C" fn cc20_close_sink(sink: *mut CSink, file: *mut *mut libc::FILE) +{ + todo!() +} + +/// Convert a `Sink` into a `FILE*`. +#[no_mangle] extern "C" fn cc20_wrap_sink(sink: *mut CSink) -> *mut libc::FILE +{ + todo!("Create a Custom Stream in `wrapper.c` that allows creating a FILE* object from `sink`.") +} + +//TODO: `impl io::Write for CPassthrough`... diff --git a/src/ffi/error.rs b/src/ffi/error.rs new file mode 100644 index 0000000..1bc1ce3 --- /dev/null +++ b/src/ffi/error.rs @@ -0,0 +1,67 @@ +//! FFI errors +use super::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] +#[repr(C)] +pub enum CErr +{ + Success = 0, + InvalidFile, + /// Unexpected null pointer + NullPointer, + + Panic = -1, +} + +impl CErr +{ + #[inline] pub fn is_error(&self) -> bool + { + *self != Self::Success + } + #[inline] pub fn is_success(&self) -> bool + { + *self == Self::Success + } +} + +impl Default for CErr +{ + #[inline] + fn default() -> Self + { + Self::Success + } +} + +impl From for i32 +{ + fn from(from: CErr) -> Self + { + from as i32 + } +} + +/// Null check a pointer. If it's not null, dereference it. +#[macro_export] macro_rules! nullchk { + (<- $ptr:expr) => { + { + let ptr = $ptr; + if ptr.is_null() { + return From::from(CErr::NullPointer); + } else { + unsafe { + *ptr + } + } + } + }; + ($ptr:expr) => { + { + if $ptr.is_null() { + return From::from(CErr::NullPointer); + } + () + } + } +} diff --git a/src/ffi/interop.rs b/src/ffi/interop.rs new file mode 100644 index 0000000..205ef45 --- /dev/null +++ b/src/ffi/interop.rs @@ -0,0 +1,25 @@ +use super::*; + +#[macro_export] macro_rules! no_unwind { + ($expr:expr) => { + ::std::panic::catch_unwind(move || $expr).ok() + }; +} + +/// Expose an opaque pointer to FFI +#[inline] pub fn give(val: T) -> *mut T +{ + Box::into_raw(Box::new(val)) +} + +/// Take a value back from an opaque FFI pointer +/// +/// # Panics +/// If the pointer is `null`. +#[inline] pub unsafe fn take(val: *mut T) -> T +{ + if val.is_null() { + panic!("null value in opaque take"); + } + *Box::from_raw(val) +} diff --git a/src/lib.rs b/src/lib.rs index 61a82c0..0833260 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,3 +64,5 @@ pub use key::{ }; pub use cha::keygen; + +#[cfg(feature="ffi")] pub mod ffi;