pub const AUDIO_PLUGIN_ABI_MAGIC: u32 = 0x4150_4C47;
pub const AUDIO_PLUGIN_ABI_VERSION: u32 = 1 << 16;
#[must_use]
pub fn encode_abi_version(major: u16, minor: u16) -> u32 {
(u32::from(major) << 16) | u32::from(minor)
}
#[must_use]
pub fn abi_major(version: u32) -> u16 {
u16::try_from(version >> 16).unwrap_or_default()
}
#[must_use]
pub fn abi_minor(version: u32) -> u16 {
u16::try_from(version & 0xFFFF).unwrap_or_default()
}
#[must_use]
pub fn is_abi_compatible(host_version: u32, plugin_version: u32) -> bool {
abi_major(host_version) == abi_major(plugin_version)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AbiVersion {
raw: u32,
}
impl AbiVersion {
#[must_use]
pub fn new(major: u16, minor: u16) -> Self {
Self {
raw: encode_abi_version(major, minor),
}
}
#[must_use]
pub fn decode(raw: u32) -> Self {
Self { raw }
}
#[must_use]
pub fn raw(self) -> u32 {
self.raw
}
#[must_use]
pub fn major(self) -> u16 {
abi_major(self.raw)
}
#[must_use]
pub fn minor(self) -> u16 {
abi_minor(self.raw)
}
#[must_use]
pub fn is_compatible_with(self, other: Self) -> bool {
self.major() == other.major()
}
}
#[cfg(test)]
mod tests {
use super::{
abi_major, abi_minor, encode_abi_version, is_abi_compatible, AbiVersion,
AUDIO_PLUGIN_ABI_MAGIC, AUDIO_PLUGIN_ABI_VERSION,
};
#[test]
fn host_version_matches_encode_helper() {
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);
}
#[test]
fn magic_is_aplg_ascii() {
assert_eq!(AUDIO_PLUGIN_ABI_MAGIC, 0x4150_4C47);
}
#[test]
fn encode_decode_roundtrip_typical() {
let raw = encode_abi_version(3, 7);
assert_eq!(abi_major(raw), 3);
assert_eq!(abi_minor(raw), 7);
assert_eq!(raw, 0x0003_0007);
}
#[test]
fn encode_packs_major_high_minor_low() {
assert_eq!(encode_abi_version(1, 0), 0x0001_0000);
assert_eq!(encode_abi_version(0, 1), 0x0000_0001);
}
#[test]
fn encode_decode_roundtrip_zero_zero() {
let raw = encode_abi_version(0, 0);
assert_eq!(abi_major(raw), 0);
assert_eq!(abi_minor(raw), 0);
assert_eq!(raw, 0);
}
#[test]
fn encode_decode_roundtrip_max_max() {
let raw = encode_abi_version(u16::MAX, u16::MAX);
assert_eq!(abi_major(raw), u16::MAX);
assert_eq!(abi_minor(raw), u16::MAX);
assert_eq!(raw, u32::MAX);
}
#[test]
fn same_major_minor_differs_is_compatible() {
let host = encode_abi_version(1, 0);
let plugin = encode_abi_version(1, 9);
assert!(is_abi_compatible(host, plugin));
}
#[test]
fn major_differs_is_incompatible_even_if_minor_equal() {
let host = encode_abi_version(1, 5);
let plugin = encode_abi_version(2, 5);
assert!(!is_abi_compatible(host, plugin));
}
#[test]
fn identical_versions_are_compatible() {
let v = encode_abi_version(1, 0);
assert!(is_abi_compatible(v, v));
}
#[test]
fn is_abi_compatible_is_symmetric() {
let a = encode_abi_version(1, 2);
let b = encode_abi_version(1, 9);
assert_eq!(is_abi_compatible(a, b), is_abi_compatible(b, a));
}
#[test]
fn abi_version_new_roundtrips_through_major_minor() {
let v = AbiVersion::new(4, 2);
assert_eq!(v.major(), 4);
assert_eq!(v.minor(), 2);
assert_eq!(v.raw(), encode_abi_version(4, 2));
}
#[test]
fn abi_version_decode_preserves_raw() {
let raw = encode_abi_version(7, 1);
let v = AbiVersion::decode(raw);
assert_eq!(v.raw(), raw);
assert_eq!(v, AbiVersion::new(7, 1));
}
#[test]
fn abi_version_is_compatible_with_major_rule() {
let host = AbiVersion::new(1, 0);
assert!(host.is_compatible_with(AbiVersion::new(1, 99)));
assert!(!host.is_compatible_with(AbiVersion::new(2, 0)));
}
#[test]
fn abi_version_derives_copy_eq_debug() {
let a = AbiVersion::new(1, 0);
let b = a; assert_eq!(a, b);
assert_eq!(format!("{a:?}"), "AbiVersion { raw: 65536 }");
}
use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(major in 0u16..=u16::MAX, minor in 0u16..=u16::MAX) {
let raw = encode_abi_version(major, minor);
prop_assert_eq!(abi_major(raw), major);
prop_assert_eq!(abi_minor(raw), minor);
}
#[test]
fn abiversion_new_decode_roundtrip(major in 0u16..=u16::MAX, minor in 0u16..=u16::MAX) {
let v = AbiVersion::new(major, minor);
prop_assert_eq!(AbiVersion::decode(v.raw()), v);
prop_assert_eq!(v.major(), major);
prop_assert_eq!(v.minor(), minor);
}
#[test]
fn compatibility_depends_only_on_major(
h_major in 0u16..=u16::MAX,
h_minor in 0u16..=u16::MAX,
p_minor in 0u16..=u16::MAX,
) {
let host = encode_abi_version(h_major, h_minor);
let same = encode_abi_version(h_major, p_minor);
prop_assert!(is_abi_compatible(host, same));
let other_major = if h_major == u16::MAX { 0 } else { h_major.wrapping_add(1) };
if other_major != h_major {
let diff = encode_abi_version(other_major, p_minor);
prop_assert!(!is_abi_compatible(host, diff));
}
}
}
}