use crate::error::{PluginError, Result};
use crate::metadata::PluginMetadata;
pub const AUDIO_PLUGIN_ABI_MAGIC_SYMBOL: &str = "audio_plugin_abi_magic";
pub const AUDIO_PLUGIN_ABI_VERSION_SYMBOL: &str = "audio_plugin_abi_version";
pub const AUDIO_PLUGIN_METADATA_SYMBOL: &str = "audio_plugin_metadata";
pub const AUDIO_PLUGIN_CREATE_SYMBOL: &str = "audio_plugin_create";
pub const AUDIO_PLUGIN_DESTROY_SYMBOL: &str = "audio_plugin_destroy";
pub type AbiMagicFn = unsafe extern "C" fn() -> u32;
pub type AbiVersionFn = unsafe extern "C" fn() -> u32;
pub type MetadataFn = unsafe extern "C" fn() -> *const RawPluginMetadata;
pub type CreateFn = unsafe extern "C" fn() -> *mut core::ffi::c_void;
pub type DestroyFn = unsafe extern "C" fn(*mut core::ffi::c_void);
#[repr(C)]
pub struct RawPluginMetadata {
pub name_ptr: *const u8,
pub name_len: usize,
pub version_ptr: *const u8,
pub version_len: usize,
pub description_ptr: *const u8,
pub description_len: usize,
pub abi_version: u32,
}
unsafe fn ptr_len_to_string(ptr: *const u8, len: usize, field: &'static str) -> Result<String> {
if ptr.is_null() {
return Err(PluginError::InvalidMetadata(format!(
"{field} pointer is null"
)));
}
let bytes = unsafe { core::slice::from_raw_parts(ptr, len) };
match std::str::from_utf8(bytes) {
Ok(s) => Ok(s.to_owned()),
Err(_) => Err(PluginError::InvalidMetadata(format!(
"{field} is not valid UTF-8"
))),
}
}
pub unsafe fn raw_to_metadata(raw: *const RawPluginMetadata) -> Result<PluginMetadata> {
if raw.is_null() {
return Err(PluginError::InvalidMetadata(
"metadata symbol returned a null pointer".into(),
));
}
let raw = unsafe { &*raw };
let name = unsafe { ptr_len_to_string(raw.name_ptr, raw.name_len, "name")? };
let version = unsafe { ptr_len_to_string(raw.version_ptr, raw.version_len, "version")? };
let description =
unsafe { ptr_len_to_string(raw.description_ptr, raw.description_len, "description")? };
Ok(PluginMetadata::new(
name,
version,
description,
raw.abi_version,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symbol_name_constants_match_extern_c_convention() {
assert_eq!(AUDIO_PLUGIN_ABI_MAGIC_SYMBOL, "audio_plugin_abi_magic");
assert_eq!(AUDIO_PLUGIN_ABI_VERSION_SYMBOL, "audio_plugin_abi_version");
assert_eq!(AUDIO_PLUGIN_METADATA_SYMBOL, "audio_plugin_metadata");
assert_eq!(AUDIO_PLUGIN_CREATE_SYMBOL, "audio_plugin_create");
assert_eq!(AUDIO_PLUGIN_DESTROY_SYMBOL, "audio_plugin_destroy");
}
#[test]
fn raw_to_metadata_null_pointer_is_err() {
let err = unsafe { raw_to_metadata(core::ptr::null()) }.unwrap_err();
assert!(matches!(err, PluginError::InvalidMetadata(_)));
assert!(err.to_string().contains("null pointer"));
}
#[test]
fn raw_to_metadata_roundtrips_owned_strings() {
let name = b"reverb-plate";
let version = b"0.3.1";
let description = b"plate reverb DSP";
let raw = RawPluginMetadata {
name_ptr: name.as_ptr(),
name_len: name.len(),
version_ptr: version.as_ptr(),
version_len: version.len(),
description_ptr: description.as_ptr(),
description_len: description.len(),
abi_version: 0x0001_0000,
};
let md = unsafe { raw_to_metadata(std::ptr::addr_of!(raw)) }.expect("valid metadata");
assert_eq!(md.name, "reverb-plate");
assert_eq!(md.version, "0.3.1");
assert_eq!(md.description, "plate reverb DSP");
assert_eq!(md.abi_version, 0x0001_0000);
assert_eq!(md.author, None);
assert_eq!(md.license, None);
}
#[test]
fn raw_to_metadata_null_string_pointer_is_err() {
let valid = b"x";
let raw = RawPluginMetadata {
name_ptr: core::ptr::null(),
name_len: 0,
version_ptr: valid.as_ptr(),
version_len: valid.len(),
description_ptr: valid.as_ptr(),
description_len: valid.len(),
abi_version: 0,
};
let err = unsafe { raw_to_metadata(std::ptr::addr_of!(raw)) }.unwrap_err();
assert!(matches!(err, PluginError::InvalidMetadata(_)));
assert!(err.to_string().contains("name pointer is null"));
}
#[test]
fn raw_to_metadata_invalid_utf8_is_err() {
let bad = [0xFFu8, 0xFE, 0xFD];
let raw = RawPluginMetadata {
name_ptr: bad.as_ptr(),
name_len: bad.len(),
version_ptr: b"1".as_ptr(),
version_len: 1,
description_ptr: b"d".as_ptr(),
description_len: 1,
abi_version: 0,
};
let err = unsafe { raw_to_metadata(std::ptr::addr_of!(raw)) }.unwrap_err();
assert!(matches!(err, PluginError::InvalidMetadata(_)));
assert!(err.to_string().contains("name is not valid UTF-8"));
}
}