1pub const AUDIO_PLUGIN_ABI_MAGIC: u32 = 0x4150_4C47;
35
36pub const AUDIO_PLUGIN_ABI_VERSION: u32 = 1 << 16;
42
43#[must_use]
47pub fn encode_abi_version(major: u16, minor: u16) -> u32 {
48 (u32::from(major) << 16) | u32::from(minor)
49}
50
51#[must_use]
58pub fn abi_major(version: u32) -> u16 {
59 u16::try_from(version >> 16).unwrap_or_default()
60}
61
62#[must_use]
69pub fn abi_minor(version: u32) -> u16 {
70 u16::try_from(version & 0xFFFF).unwrap_or_default()
71}
72
73#[must_use]
81pub fn is_abi_compatible(host_version: u32, plugin_version: u32) -> bool {
82 abi_major(host_version) == abi_major(plugin_version)
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct AbiVersion {
92 raw: u32,
94}
95
96impl AbiVersion {
97 #[must_use]
99 pub fn new(major: u16, minor: u16) -> Self {
100 Self {
101 raw: encode_abi_version(major, minor),
102 }
103 }
104
105 #[must_use]
107 pub fn decode(raw: u32) -> Self {
108 Self { raw }
109 }
110
111 #[must_use]
113 pub fn raw(self) -> u32 {
114 self.raw
115 }
116
117 #[must_use]
119 pub fn major(self) -> u16 {
120 abi_major(self.raw)
121 }
122
123 #[must_use]
125 pub fn minor(self) -> u16 {
126 abi_minor(self.raw)
127 }
128
129 #[must_use]
132 pub fn is_compatible_with(self, other: Self) -> bool {
133 self.major() == other.major()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::{
140 abi_major, abi_minor, encode_abi_version, is_abi_compatible, AbiVersion,
141 AUDIO_PLUGIN_ABI_MAGIC, AUDIO_PLUGIN_ABI_VERSION,
142 };
143
144 #[test]
145 fn host_version_matches_encode_helper() {
146 assert_eq!(AUDIO_PLUGIN_ABI_VERSION, encode_abi_version(1, 0));
148 assert_eq!(abi_major(AUDIO_PLUGIN_ABI_VERSION), 1);
149 assert_eq!(abi_minor(AUDIO_PLUGIN_ABI_VERSION), 0);
150 }
151
152 #[test]
153 fn magic_is_aplg_ascii() {
154 assert_eq!(AUDIO_PLUGIN_ABI_MAGIC, 0x4150_4C47);
156 }
157
158 #[test]
159 fn encode_decode_roundtrip_typical() {
160 let raw = encode_abi_version(3, 7);
161 assert_eq!(abi_major(raw), 3);
162 assert_eq!(abi_minor(raw), 7);
163 assert_eq!(raw, 0x0003_0007);
164 }
165
166 #[test]
167 fn encode_packs_major_high_minor_low() {
168 assert_eq!(encode_abi_version(1, 0), 0x0001_0000);
170 assert_eq!(encode_abi_version(0, 1), 0x0000_0001);
171 }
172
173 #[test]
174 fn encode_decode_roundtrip_zero_zero() {
175 let raw = encode_abi_version(0, 0);
176 assert_eq!(abi_major(raw), 0);
177 assert_eq!(abi_minor(raw), 0);
178 assert_eq!(raw, 0);
179 }
180
181 #[test]
182 fn encode_decode_roundtrip_max_max() {
183 let raw = encode_abi_version(u16::MAX, u16::MAX);
184 assert_eq!(abi_major(raw), u16::MAX);
185 assert_eq!(abi_minor(raw), u16::MAX);
186 assert_eq!(raw, u32::MAX);
187 }
188
189 #[test]
190 fn same_major_minor_differs_is_compatible() {
191 let host = encode_abi_version(1, 0);
192 let plugin = encode_abi_version(1, 9);
193 assert!(is_abi_compatible(host, plugin));
194 }
195
196 #[test]
197 fn major_differs_is_incompatible_even_if_minor_equal() {
198 let host = encode_abi_version(1, 5);
199 let plugin = encode_abi_version(2, 5);
200 assert!(!is_abi_compatible(host, plugin));
201 }
202
203 #[test]
204 fn identical_versions_are_compatible() {
205 let v = encode_abi_version(1, 0);
206 assert!(is_abi_compatible(v, v));
207 }
208
209 #[test]
210 fn is_abi_compatible_is_symmetric() {
211 let a = encode_abi_version(1, 2);
212 let b = encode_abi_version(1, 9);
213 assert_eq!(is_abi_compatible(a, b), is_abi_compatible(b, a));
214 }
215
216 #[test]
217 fn abi_version_new_roundtrips_through_major_minor() {
218 let v = AbiVersion::new(4, 2);
219 assert_eq!(v.major(), 4);
220 assert_eq!(v.minor(), 2);
221 assert_eq!(v.raw(), encode_abi_version(4, 2));
222 }
223
224 #[test]
225 fn abi_version_decode_preserves_raw() {
226 let raw = encode_abi_version(7, 1);
227 let v = AbiVersion::decode(raw);
228 assert_eq!(v.raw(), raw);
229 assert_eq!(v, AbiVersion::new(7, 1));
230 }
231
232 #[test]
233 fn abi_version_is_compatible_with_major_rule() {
234 let host = AbiVersion::new(1, 0);
235 assert!(host.is_compatible_with(AbiVersion::new(1, 99)));
236 assert!(!host.is_compatible_with(AbiVersion::new(2, 0)));
237 }
238
239 #[test]
240 fn abi_version_derives_copy_eq_debug() {
241 let a = AbiVersion::new(1, 0);
242 let b = a; assert_eq!(a, b);
244 assert_eq!(format!("{a:?}"), "AbiVersion { raw: 65536 }");
245 }
246
247 use proptest::prelude::*;
250
251 proptest! {
252 #[test]
253 fn encode_decode_roundtrip(major in 0u16..=u16::MAX, minor in 0u16..=u16::MAX) {
254 let raw = encode_abi_version(major, minor);
255 prop_assert_eq!(abi_major(raw), major);
256 prop_assert_eq!(abi_minor(raw), minor);
257 }
258
259 #[test]
260 fn abiversion_new_decode_roundtrip(major in 0u16..=u16::MAX, minor in 0u16..=u16::MAX) {
261 let v = AbiVersion::new(major, minor);
262 prop_assert_eq!(AbiVersion::decode(v.raw()), v);
263 prop_assert_eq!(v.major(), major);
264 prop_assert_eq!(v.minor(), minor);
265 }
266
267 #[test]
268 fn compatibility_depends_only_on_major(
269 h_major in 0u16..=u16::MAX,
270 h_minor in 0u16..=u16::MAX,
271 p_minor in 0u16..=u16::MAX,
272 ) {
273 let host = encode_abi_version(h_major, h_minor);
274 let same = encode_abi_version(h_major, p_minor);
276 prop_assert!(is_abi_compatible(host, same));
277 let other_major = if h_major == u16::MAX { 0 } else { h_major.wrapping_add(1) };
279 if other_major != h_major {
280 let diff = encode_abi_version(other_major, p_minor);
281 prop_assert!(!is_abi_compatible(host, diff));
282 }
283 }
284 }
285}