zenroom 0.3.1

zenroom is a small, portable and secure language interpreter of a domain specific language called zencode, able to execute cryptographic operations and smart contracts in a multiplatform environment.
Documentation
use std::ffi::{CStr, CString};
use std::fmt;
use std::sync::{Mutex, MutexGuard, Once};

mod c {
    #![allow(non_upper_case_globals)]
    #![allow(non_camel_case_types)]
    #![allow(non_snake_case)]
    #![allow(dead_code)]
    #![allow(deref_nullptr)]
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

#[derive(Clone, Debug)]
pub struct ZenResult {
    pub output: String,
    pub logs: String,
}

#[derive(Clone, Debug)]
pub enum ZenError {
    Execution(ZenResult),
    InvalidInput(std::ffi::NulError),
}

impl From<std::ffi::NulError> for ZenError {
    fn from(err: std::ffi::NulError) -> Self {
        Self::InvalidInput(err)
    }
}

impl fmt::Display for ZenError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Execution(err) => f.write_fmt(format_args!("Execution Error:\n{}", err.logs)),
            Self::InvalidInput(err) => f.write_fmt(format_args!("Invalid input: {}", err)),
        }
    }
}

impl std::error::Error for ZenError {}

type ZenGIL = MutexGuard<'static, ()>;

fn aquire_zen_gil() -> ZenGIL {
    static mut MUTEX: *const Mutex<()> = std::ptr::null();
    static ONCE: Once = Once::new();
    let mutex: &Mutex<()> = unsafe {
        ONCE.call_once(|| {
            MUTEX = std::mem::transmute(Box::new(Mutex::new(())));
        });
        MUTEX.as_ref().unwrap()
    };
    mutex.lock().unwrap()
}

const BUF_SIZE: usize = 2 * 1024 * 1024;

type Fun = unsafe extern "C" fn(
    *mut ::std::os::raw::c_char,
    *mut ::std::os::raw::c_char,
    *mut ::std::os::raw::c_char,
    *mut ::std::os::raw::c_char,
    *mut ::std::os::raw::c_char,
    ::std::os::raw::c_ulong,
    *mut ::std::os::raw::c_char,
    ::std::os::raw::c_ulong,
) -> ::std::os::raw::c_int;

pub fn zencode_exec(
    script: impl AsRef<str>,
    conf: impl AsRef<str>,
    keys: impl AsRef<str>,
    data: impl AsRef<str>,
) -> Result<ZenResult, ZenError> {
    exec_f(c::zencode_exec_tobuf, script, conf, keys, data)
}

pub fn zenroom_exec(
    script: impl AsRef<str>,
    conf: impl AsRef<str>,
    keys: impl AsRef<str>,
    data: impl AsRef<str>,
) -> Result<ZenResult, ZenError> {
    exec_f(c::zenroom_exec_tobuf, script, conf, keys, data)
}

fn exec_f(
    fun: Fun,
    script: impl AsRef<str>,
    conf: impl AsRef<str>,
    keys: impl AsRef<str>,
    data: impl AsRef<str>,
) -> Result<ZenResult, ZenError> {
    let mut stdout = Vec::<i8>::with_capacity(BUF_SIZE);
    let stdout_ptr = stdout.as_mut_ptr();
    let mut stderr = Vec::<i8>::with_capacity(BUF_SIZE);
    let stderr_ptr = stderr.as_mut_ptr();

    let lock = aquire_zen_gil();
    let exit_code = unsafe {
        fun(
            CString::new(script.as_ref())?.into_raw(),
            CString::new(conf.as_ref())?.into_raw(),
            CString::new(keys.as_ref())?.into_raw(),
            CString::new(data.as_ref())?.into_raw(),
            stdout_ptr,
            BUF_SIZE as u64,
            stderr_ptr,
            BUF_SIZE as u64,
        )
    };
    drop(lock);

    let res = ZenResult {
        output: unsafe { CStr::from_ptr(stdout_ptr) }
            .to_string_lossy()
            .into_owned(),
        logs: unsafe { CStr::from_ptr(stderr_ptr) }
            .to_string_lossy()
            .into_owned(),
    };

    if exit_code == 0 {
        Ok(res)
    } else {
        Err(ZenError::Execution(res))
    }
}

#[cfg(test)]
mod tests {
    use crate::*;
    use serde_json::Value;

    const SAMPLE_SCRIPT: &str = r#"
    Scenario 'ecdh': Create the keypair
    Scenario 'schnorr'
    Given that I am known as 'Alice'
    When I create the ecdh key
    and I create the schnorr key
    Then print my 'keyring'
    "#;

    #[test]
    fn simple_script() -> Result<(), ZenError> {
        let result = zencode_exec(SAMPLE_SCRIPT, "", "", "")?;

        let json: Value = serde_json::from_str(&result.output).unwrap();
        let keypair = json
            .as_object()
            .unwrap()
            .get("Alice")
            .unwrap()
            .get("keyring")
            .unwrap();
        assert!(keypair.get("ecdh").is_some());
        assert!(keypair.get("schnorr").is_some());

        Ok(())
    }

    #[test]
    fn threaded_exec() -> Result<(), ZenError> {
        const NUM_THREADS: usize = 5;
        let mut threads = Vec::new();
        for _ in 0..NUM_THREADS {
            threads.push(std::thread::spawn(|| {
                zencode_exec(SAMPLE_SCRIPT, "", "", "")
            }));
        }
        for thread in threads {
            thread.join().expect("thread should not panic")?;
        }
        Ok(())
    }
}