use std::ffi::c_void;
use std::path::Path;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::{validate_abi_version, validate_size, Error, Result, XabiSlice, XabiStr, ABI_VERSION};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XabiExport {
pub abi_id: XabiStr,
pub name: XabiStr,
pub version: u32,
pub make: unsafe extern "C" fn() -> *mut c_void,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct XabiManifest {
pub size: usize,
pub abi_version: u32,
pub exports: XabiSlice<XabiExport>,
}
impl XabiManifest {
pub const fn new(exports: &'static [XabiExport]) -> Self {
Self {
size: std::mem::size_of::<XabiManifest>(),
abi_version: 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]> {
unsafe { self.manifest.as_ref().exports.as_slice() }
}
}
pub unsafe fn load(path: impl AsRef<Path>) -> Result<Module> {
unsafe { Module::load(path) }
}
fn validate_manifest(manifest: &XabiManifest) -> Result<()> {
validate_size(
manifest.size,
std::mem::size_of::<XabiManifest>(),
"XabiManifest",
)?;
validate_abi_version(manifest.abi_version, 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());
}
}