use std::ffi::c_void;
use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::{ABI_VERSION, Error, Result, XabiSlice, XabiStr, validate_abi_version, validate_size};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XabiExport {
pub size: usize,
pub abi_version: u32,
pub abi_id: XabiStr,
pub name: XabiStr,
pub contract_version: u32,
pub export_version: u32,
pub capabilities: u64,
pub make: unsafe extern "C" fn() -> *mut c_void,
}
impl XabiExport {
pub const ABI_VERSION: u32 = ABI_VERSION;
pub const MIN_SIZE: usize = std::mem::offset_of!(XabiExport, make)
+ std::mem::size_of::<unsafe extern "C" fn() -> *mut c_void>();
pub const FULL_SIZE: usize = std::mem::size_of::<Self>();
pub const fn new(
abi_id: XabiStr,
name: XabiStr,
contract_version: u32,
export_version: u32,
capabilities: u64,
make: unsafe extern "C" fn() -> *mut c_void,
) -> Self {
Self {
size: std::mem::size_of::<Self>(),
abi_version: Self::ABI_VERSION,
abi_id,
name,
contract_version,
export_version,
capabilities,
make,
}
}
pub fn validate(&self) -> Result<()> {
validate_size(self.size, Self::MIN_SIZE, "XabiExport")?;
validate_abi_version(self.abi_version, Self::ABI_VERSION, "XabiExport")?;
Ok(())
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XabiManifest {
pub size: usize,
pub abi_version: u32,
pub exports: XabiSlice<XabiExport>,
}
impl XabiManifest {
pub const ABI_VERSION: u32 = ABI_VERSION;
pub const MIN_SIZE: usize =
std::mem::offset_of!(XabiManifest, exports) + std::mem::size_of::<XabiSlice<XabiExport>>();
pub const FULL_SIZE: usize = std::mem::size_of::<Self>();
pub const fn new(exports: &'static [XabiExport]) -> Self {
Self {
size: std::mem::size_of::<XabiManifest>(),
abi_version: Self::ABI_VERSION,
exports: XabiSlice {
ptr: exports.as_ptr(),
len: exports.len(),
},
}
}
pub fn validate(&self) -> Result<()> {
validate_manifest(self)
}
}
pub struct ModuleHandle {
pub(crate) library: libloading::Library,
}
impl ModuleHandle {
pub unsafe fn load(path: impl AsRef<Path>) -> Result<Arc<Self>> {
let library = unsafe { libloading::Library::new(path.as_ref()) }
.map_err(|err| Error::LoadLibrary(err.to_string()))?;
Ok(Arc::new(Self { library }))
}
pub unsafe fn get<T>(&self, symbol: &[u8]) -> Result<libloading::Symbol<'_, T>> {
unsafe { self.library.get(symbol) }.map_err(|err| {
Error::LoadSymbol(
String::from_utf8_lossy(symbol).into_owned(),
err.to_string(),
)
})
}
}
pub struct Module {
handle: Arc<ModuleHandle>,
manifest: NonNull<XabiManifest>,
}
unsafe impl Send for Module {}
unsafe impl Sync for Module {}
impl Module {
pub unsafe fn load(path: impl AsRef<Path>) -> Result<Self> {
let handle = unsafe { ModuleHandle::load(path) }?;
let manifest = {
let symbol: libloading::Symbol<'_, unsafe extern "C" fn() -> *const XabiManifest> =
unsafe { handle.get(b"xabi_manifest") }?;
unsafe { symbol() }
};
let manifest = NonNull::new(manifest as *mut XabiManifest)
.ok_or(Error::NullPointer("XabiManifest"))?;
unsafe { manifest.as_ref().validate() }?;
Ok(Self { handle, manifest })
}
pub fn handle(&self) -> Arc<ModuleHandle> {
Arc::clone(&self.handle)
}
pub fn exports(&self) -> Result<&[XabiExport]> {
let exports = unsafe { self.manifest.as_ref().exports.as_slice() }?;
for export in exports {
export.validate()?;
}
Ok(exports)
}
}
pub unsafe fn load(path: impl AsRef<Path>) -> Result<Module> {
unsafe { Module::load(path) }
}
fn validate_manifest(manifest: &XabiManifest) -> Result<()> {
validate_size(manifest.size, XabiManifest::MIN_SIZE, "XabiManifest")?;
validate_abi_version(
manifest.abi_version,
XabiManifest::ABI_VERSION,
"XabiManifest",
)?;
if manifest.exports.len > 0 && manifest.exports.ptr.is_null() {
return Err(Error::NullPointer("XabiManifest::exports"));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_accepts_current_layout() {
static EXPORTS: [XabiExport; 0] = [];
let manifest = XabiManifest::new(&EXPORTS);
manifest.validate().expect("current manifest is valid");
}
#[test]
fn manifest_rejects_short_layout() {
let manifest = XabiManifest {
size: std::mem::size_of::<XabiManifest>() - 1,
abi_version: ABI_VERSION,
exports: XabiSlice::empty(),
};
assert!(manifest.validate().is_err());
}
#[test]
fn manifest_rejects_wrong_abi_version() {
let manifest = XabiManifest {
size: std::mem::size_of::<XabiManifest>(),
abi_version: ABI_VERSION + 1,
exports: XabiSlice::empty(),
};
assert!(manifest.validate().is_err());
}
#[test]
fn manifest_rejects_non_empty_null_exports() {
let manifest = XabiManifest {
size: std::mem::size_of::<XabiManifest>(),
abi_version: ABI_VERSION,
exports: XabiSlice {
ptr: std::ptr::null(),
len: 1,
},
};
assert!(manifest.validate().is_err());
}
}