audio-plugin-bsd 0.1.1

Dynamic .so audio-plugin loader with ABI verification and FreeBSD Capsicum/pdfork per-process sandboxing for real-time audio in Rust
Documentation

audio-plugin-bsd

License: BSD-2-Clause Crates.io

Dynamic .so audio-plugin loader with ABI verification and FreeBSD Capsicum/pdfork per-process sandboxing for real-time audio in Rust.

Note: The doc comments on each public item are the primary reference. This README is an overview only.

Role

A standalone dynamic plugin loader for real-time audio in Rust. It loads third-party audio plugins compiled as cdylib (.so / .dylib / .dll) shared objects, verifies their ABI against the host, instantiates them as AudioNode implementations 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).

Core types

Type Description
[PluginError] Loader / sandbox error enum (thiserror-backed) + Result alias.
[AUDIO_PLUGIN_ABI_MAGIC] Magic word (0x41504C47, ASCII "APLG") every plugin must return.
[AUDIO_PLUGIN_ABI_VERSION] Host ABI version (1.0), encoded by [encode_abi_version].
[encode_abi_version] / [abi_major] / [abi_minor] Encode/decode the (major, minor) ABI version word.
[is_abi_compatible] Major-only ABI compatibility rule.
[AbiVersion] Typed, validated wrapper around an encoded ABI version word.
[PluginMetadata] Plugin identity (name / version / description / author / license).

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; casting a foreign .so's pointer to a Rust trait object is undefined behaviour. Instead every plugin exposes plain extern "C" symbols:

  • audio_plugin_abi_magic() -> u32 — must equal AUDIO_PLUGIN_ABI_MAGIC.
  • audio_plugin_abi_version() -> u32 — encoded via encode_abi_version; the host accepts any plugin whose major matches its own.
  • audio_plugin_metadata() / audio_plugin_create() — identity + opaque handle (consumed by the loader in a follow-up task).

The host never reinterpret-casts a returned pointer as a Rust trait object; it only calls further extern "C" functions on it.

Dependencies

Dependency Version Purpose
audio-core-bsd 0.1.0 (path) Shared AudioNode / AudioFrame contract.
libloading 0.9 dlopen / dlsym shared-library loading.
thiserror 2.0 PluginError derive.
libc 0.2 FFI types for pdfork.
capsicum 0.4.5 FreeBSD only — Capsicum capability mode.
pdfork 0.1.1 FreeBSD only — process-descriptor isolation.

capsicum and pdfork are gated behind cfg(target_os = "freebsd") and are not compiled on Linux/macOS/Windows (see NOTICE for the MPL-2.0 attribution).

Status

0.x — experimental. The API is not yet frozen. Breaking changes are expected before a 1.0 release.

  • edition: 2021
  • MSRV: 1.85
  • license: BSD-2-Clause

Example

use audio_plugin_bsd::{
    abi_major, abi_minor, encode_abi_version, is_abi_compatible, AbiVersion,
    AUDIO_PLUGIN_ABI_MAGIC, AUDIO_PLUGIN_ABI_VERSION, PluginError, PluginMetadata,
};

// The host advertises ABI 1.0.
assert_eq!(AUDIO_PLUGIN_ABI_VERSION, encode_abi_version(1, 0));
assert_eq!(abi_major(AUDIO_PLUGIN_ABI_VERSION), 1);
assert_eq!(abi_minor(AUDIO_PLUGIN_ABI_VERSION), 0);

// A plugin built against 1.3 is compatible (same major).
let plugin_v = encode_abi_version(1, 3);
assert!(is_abi_compatible(AUDIO_PLUGIN_ABI_VERSION, plugin_v));

// A plugin built against 2.0 is NOT compatible (major bump).
assert!(!is_abi_compatible(AUDIO_PLUGIN_ABI_VERSION, encode_abi_version(2, 0)));

// Typed wrapper.
let host = AbiVersion::decode(AUDIO_PLUGIN_ABI_VERSION);
assert!(host.is_compatible_with(AbiVersion::new(1, 9)));
assert!(!host.is_compatible_with(AbiVersion::new(2, 0)));

// Metadata a plugin might expose.
let meta = PluginMetadata::new("reverb-plate", "0.3.1", "plate reverb", plugin_v)
    .with_author("acme-audio")
    .with_license("MIT");
assert_eq!(meta.name, "reverb-plate");

// The error path the loader raises on a mismatch.
let _err = PluginError::AbiVersionMismatch {
    host: AUDIO_PLUGIN_ABI_VERSION,
    plugin: encode_abi_version(2, 0),
};

// The magic is checked separately from the version word.
let _magic = AUDIO_PLUGIN_ABI_MAGIC;

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.

License

BSD-2-Clause. See LICENSE and NOTICE.

Design decisions are recorded in docs/adr/.