xabi 0.1.0

Generate stable native ABI glue from Rust traits
Documentation
use std::ffi::c_void;
use std::marker::PhantomData;

use crate::{Error, Result, XabiOption, XabiOwnedBytes};

/// Trait implemented by ABI descriptors generated by [`crate::xabi`].
///
/// Exporting modules do not implement this manually in normal use; the generated
/// ABI type is consumed by [`crate::module`].
pub trait XabiContract<P: 'static> {
    /// Stable ABI identifier exported by this contract.
    const ID: &'static str;

    /// Export a concrete implementation as an ABI-specific vtable.
    fn export(plugin: P) -> *mut c_void;
}

/// Rust type that has a stable xabi representation.
///
/// Types used by value in an xabi trait must implement this trait. Most users
/// should generate the implementation with [`crate::data`].
pub trait XabiType: Sized {
    /// C-compatible wire type passed across the ABI boundary.
    type Wire: Copy + 'static;

    /// Stable snapshot name for the wire type.
    const WIRE_TYPE_NAME: &'static str;

    /// Convert this value into its wire representation.
    fn into_wire(self) -> Self::Wire;

    /// Decode this value from a borrowed wire pointer.
    ///
    /// # Safety
    ///
    /// `wire` must be valid for reads of `Self::Wire`.
    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self>;

    /// Collect ABI layout entries required by this type.
    fn collect_xabi_layout(_collector: &mut dyn crate::XabiLayoutCollector) {}

    /// Encode this value as an owned payload.
    ///
    /// The default implementation copies the wire representation. Manual
    /// implementations that use this default must return fully initialized wire
    /// values, including padding bytes. Types with variable-sized payloads
    /// should override this method.
    fn into_payload(self) -> XabiOwnedBytes {
        let wire = self.into_wire();
        let bytes = unsafe {
            std::slice::from_raw_parts(
                std::ptr::addr_of!(wire).cast::<u8>(),
                std::mem::size_of::<Self::Wire>(),
            )
        };
        XabiOwnedBytes::from_vec(bytes.to_vec())
    }

    /// Decode this value from an owned payload.
    ///
    /// # Safety
    ///
    /// `payload` must have been produced by a compatible xabi implementation.
    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        let bytes = unsafe { payload.to_vec_and_free() }?;
        if bytes.len() != std::mem::size_of::<Self::Wire>() {
            return Err(Error::AbiMismatch(format!(
                "xabi payload size {} does not match expected {}",
                bytes.len(),
                std::mem::size_of::<Self::Wire>()
            )));
        }
        let mut wire = std::mem::MaybeUninit::<Self::Wire>::uninit();
        unsafe {
            std::ptr::copy_nonoverlapping(
                bytes.as_ptr(),
                wire.as_mut_ptr().cast::<u8>(),
                bytes.len(),
            );
            Self::from_wire(wire.as_ptr())
        }
    }
}

macro_rules! impl_xabi_type_for_int {
    ($($ty:ty),* $(,)?) => {
        $(
            impl XabiType for $ty {
                type Wire = $ty;
                const WIRE_TYPE_NAME: &'static str = stringify!($ty);

                fn into_wire(self) -> Self::Wire {
                    self
                }

                unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
                    unsafe {
                        wire.as_ref()
                            .copied()
                            .ok_or(Error::NullPointer(concat!(stringify!($ty), " pointer")))
                    }
                }
            }
        )*
    };
}

impl_xabi_type_for_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);

impl XabiType for bool {
    type Wire = u8;
    const WIRE_TYPE_NAME: &'static str = "u8";

    fn into_wire(self) -> Self::Wire {
        self as u8
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        unsafe {
            match wire
                .as_ref()
                .copied()
                .ok_or(Error::NullPointer("bool pointer"))?
            {
                0 => Ok(false),
                1 => Ok(true),
                other => Err(Error::AbiMismatch(format!(
                    "bool wire value {other} is not 0 or 1"
                ))),
            }
        }
    }
}

impl XabiType for Vec<u8> {
    type Wire = XabiOwnedBytes;
    const WIRE_TYPE_NAME: &'static str = "XabiOwnedBytes";

    fn into_wire(self) -> Self::Wire {
        XabiOwnedBytes::from_vec(self)
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_ref()
                .copied()
                .ok_or(Error::NullPointer("Vec<u8> pointer"))?
        };
        unsafe { wire.to_vec_and_free() }
    }

    fn into_payload(self) -> XabiOwnedBytes {
        XabiOwnedBytes::from_vec(self)
    }

    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        unsafe { payload.to_vec_and_free() }
    }
}

impl XabiType for String {
    type Wire = XabiOwnedBytes;
    const WIRE_TYPE_NAME: &'static str = "XabiOwnedBytes";

    fn into_wire(self) -> Self::Wire {
        XabiOwnedBytes::from_string(self)
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_ref()
                .copied()
                .ok_or(Error::NullPointer("String pointer"))?
        };
        unsafe { wire.to_string_and_free() }
    }

    fn into_payload(self) -> XabiOwnedBytes {
        XabiOwnedBytes::from_string(self)
    }

    unsafe fn from_payload(payload: XabiOwnedBytes) -> Result<Self> {
        unsafe { payload.to_string_and_free() }
    }
}

impl<T> XabiType for Option<T>
where
    T: XabiType + 'static,
{
    type Wire = XabiOption;
    const WIRE_TYPE_NAME: &'static str = "XabiOption";

    fn collect_xabi_layout(collector: &mut dyn crate::XabiLayoutCollector) {
        T::collect_xabi_layout(collector);
    }

    fn into_wire(self) -> Self::Wire {
        match self {
            Some(value) => XabiOption::some(value.into_payload()),
            None => XabiOption::none(),
        }
    }

    unsafe fn from_wire(wire: *const Self::Wire) -> Result<Self> {
        let wire = unsafe {
            wire.as_ref()
                .ok_or(Error::NullPointer("XabiOption pointer"))?
        };
        wire.validate()?;
        if wire.is_some == 0 {
            return Ok(None);
        }
        unsafe { T::from_payload(wire.payload).map(Some) }
    }
}

/// Sendable wrapper for raw pointers that are only dereferenced on a known-safe thread.
///
/// This is useful when a raw ABI pointer must be moved into an async task but
/// the caller controls where it is dereferenced.
///
/// ```
/// let mut value = 1_u32;
/// let ptr = xabi::SendPtr::new(&mut value as *mut u32);
/// let raw = ptr.as_ptr();
/// unsafe { *raw = 2 };
/// assert_eq!(value, 2);
/// ```
pub struct SendPtr<T> {
    value: usize,
    _marker: PhantomData<*mut T>,
}

impl<T> SendPtr<T> {
    /// Wrap a raw mutable pointer.
    pub fn new(ptr: *mut T) -> Self {
        Self {
            value: ptr as usize,
            _marker: PhantomData,
        }
    }

    /// Return the wrapped raw pointer.
    pub fn as_ptr(self) -> *mut T {
        self.value as *mut T
    }
}

unsafe impl<T> Send for SendPtr<T> {}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_send<T: Send>() {}

    #[test]
    fn send_ptr_is_send() {
        assert_send::<SendPtr<u8>>();
    }

    #[test]
    fn send_ptr_roundtrips_pointer_value() {
        let mut value = 5_u32;
        let ptr = &mut value as *mut u32;

        assert_eq!(SendPtr::new(ptr).as_ptr(), ptr);
    }
}