audio-plugin-bsd 0.1.0

Dynamic .so audio-plugin loader with ABI verification and FreeBSD Capsicum/pdfork per-process sandboxing for real-time audio in Rust
Documentation
//! Dynamic `.so` audio-plugin loader with ABI verification and FreeBSD
//! Capsicum/`pdfork` per-process sandboxing for real-time audio in Rust.
//!
//! This crate loads third-party audio plugins compiled as `cdylib`
//! (`.so` / `.dylib` / `.dll`) shared objects, verifies their ABI against the
//! host, instantiates them behind a stable `extern "C"` entry point, and — on
//! FreeBSD — confines each plugin to a Capsicum capability sandbox
//! (optionally one isolated child process per plugin via `pdfork`).
//!
//! # Wire ABI
//!
//! Plugins do **not** cross the `.so` boundary as Rust trait objects
//! (`Box<dyn AudioNode>`). A Rust trait object's layout and vtable are defined
//! by the *compiling* toolchain and are **not** ABI-stable across different
//! rustc or dependency versions; casting a foreign `.so`'s pointer to a Rust
//! trait object is undefined behaviour. Instead, every plugin exposes plain
//! `extern "C"` entry symbols:
//!
//! - `audio_plugin_abi_magic()  -> u32` — must equal
//!   [`AUDIO_PLUGIN_ABI_MAGIC`] (`0x41504C47`, ASCII `"APLG"`).
//! - `audio_plugin_abi_version() -> u32` — encoded via
//!   [`encode_abi_version`]; the host accepts any plugin whose **major**
//!   version matches its own (see [`is_abi_compatible`]).
//! - `audio_plugin_metadata()` and `audio_plugin_create()` — identity and an
//!   opaque handle consumed by the loader (follow-up task).
//!
//! The host never reinterpret-casts the returned pointer as a Rust trait
//! object; it only calls further `extern "C"` functions on it. See [`abi`]
//! for the version-encoding and compatibility rules.
//!
//! # Security warning — untrusted shared objects
//!
//! Loading a `.so` executes its initialisation code in the host process.
//! **Never load a plugin you do not fully trust** unless it is confined. On
//! FreeBSD this crate confines plugins with Capsicum; on other platforms the
//! sandbox is a no-op and **untrusted plugins must not be loaded at all**.
//!
//! # Platform notes
//!
//! The `capsicum` and `pdfork` dependencies are gated behind
//! `cfg(target_os = "freebsd")` and are **not** compiled on Linux, macOS, or
//! Windows. On non-FreeBSD targets this crate still compiles and exposes the
//! ABI / error / metadata types, but the loader's sandboxing is inactive.
//!
//! # IPC protocol
//!
//! Host↔plugin communication uses a simple binary protocol defined in
//! [`proto`]. Every message is a single tag byte followed by a fixed-layout
//! payload (u32 little-endian integers and length-prefixed UTF-8 strings).
//! All decode functions are panic-free; malformed input returns
//! [`PluginError::Sandbox`].
//!
//! # Sandbox configuration
//!
//! [`sandbox`] exposes [`IsolationLevel`] and [`SandboxConfig`]
//! for controlling per-plugin confinement. On non-FreeBSD platforms only
//! [`IsolationLevel::None`] is available.
//!
//! # Per-plugin process isolation
//!
//! On FreeBSD, [`isolation`] exposes [`spawn_isolated`], which runs
//! each plugin in a dedicated `pdfork` child process confined to a Capsicum
//! capability sandbox (`cap_enter`). Host↔plugin communication uses the
//! length-prefixed [`proto`] messages over a Unix-domain socketpair. On
//! non-FreeBSD targets [`spawn_isolated`] always returns
//! [`PluginError::Sandbox`]. See [`isolation`] for the 0.1.0 control-loop
//! scope and the real-time audio follow-up.
//!
//! # Real-time safety boundary
//!
//! The loader, ABI check, sandbox setup, and plugin instantiation all run on a
//! **non-RT** (setup) thread. Code on the RT audio thread must only call into
//! an already-instantiated plugin's `process` path (provided by the adapter in
//! a follow-up task) and must obey the RT contract documented in
//! `audio-core-bsd`.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#![warn(clippy::all, clippy::pedantic)]

pub mod abi;
pub mod error;
pub mod isolation;
pub mod loader;
pub mod metadata;
pub mod plugin;
pub mod proto;
pub mod sandbox;
pub mod symbols;

pub use abi::{
    abi_major, abi_minor, encode_abi_version, is_abi_compatible, AbiVersion,
    AUDIO_PLUGIN_ABI_MAGIC, AUDIO_PLUGIN_ABI_VERSION,
};
pub use error::{PluginError, Result};
pub use isolation::{spawn_isolated, SandboxedProcess};
pub use loader::{LoadedPlugin, PluginId, PluginLoader};
pub use metadata::PluginMetadata;
pub use plugin::Plugin;
pub use proto::{
    decode_request, decode_response, encode_request, encode_response, PluginMetadataPayload,
    Request, Response,
};
pub use sandbox::{IsolationLevel, SandboxConfig};
pub use symbols::{
    AbiMagicFn, AbiVersionFn, CreateFn, DestroyFn, MetadataFn, RawPluginMetadata,
    AUDIO_PLUGIN_ABI_MAGIC_SYMBOL, AUDIO_PLUGIN_ABI_VERSION_SYMBOL, AUDIO_PLUGIN_CREATE_SYMBOL,
    AUDIO_PLUGIN_DESTROY_SYMBOL, AUDIO_PLUGIN_METADATA_SYMBOL,
};