1const STATE_MAGIC: &[u8; 4] = b"OAST";
13const STATE_VERSION: u32 = 1;
14
15#[must_use]
20pub fn serialize_state(
21 plugin_id_hash: u64,
22 param_ids: &[u32],
23 param_values: &[f64],
24 extra: &[u8],
25) -> Vec<u8> {
26 let mut data = Vec::new();
27
28 data.extend_from_slice(STATE_MAGIC);
30 data.extend_from_slice(&STATE_VERSION.to_le_bytes());
31 data.extend_from_slice(&plugin_id_hash.to_le_bytes());
32
33 let count = crate::cast::len_u32(param_ids.len());
35 data.extend_from_slice(&count.to_le_bytes());
36 for (id, value) in param_ids.iter().zip(param_values.iter()) {
37 data.extend_from_slice(&id.to_le_bytes());
38 data.extend_from_slice(&value.to_le_bytes());
39 }
40
41 let len = extra.len() as u64;
43 data.extend_from_slice(&len.to_le_bytes());
44 data.extend_from_slice(extra);
45
46 data
47}
48
49pub struct DeserializedState {
51 pub params: Vec<(u32, f64)>,
52 pub extra: Option<Vec<u8>>,
53}
54
55#[must_use]
57pub fn deserialize_state(data: &[u8], expected_plugin_id: u64) -> Option<DeserializedState> {
58 if data.len() < 16 {
59 return None;
60 }
61
62 if &data[0..4] != STATE_MAGIC {
64 return None;
65 }
66
67 let version = u32::from_le_bytes(data[4..8].try_into().ok()?);
68 if version != STATE_VERSION {
69 return None;
70 }
71
72 let plugin_id = u64::from_le_bytes(data[8..16].try_into().ok()?);
73 if plugin_id != expected_plugin_id {
74 return None;
75 }
76
77 let mut offset = 16;
78
79 if offset + 4 > data.len() {
81 return None;
82 }
83 let count = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize;
84 offset += 4;
85
86 let max_count = data.len().saturating_sub(offset) / 12;
94 let mut params = Vec::with_capacity(count.min(max_count));
95 for _ in 0..count {
96 if offset + 12 > data.len() {
97 return None;
98 }
99 let id = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?);
100 offset += 4;
101 let value = f64::from_le_bytes(data[offset..offset + 8].try_into().ok()?);
102 offset += 8;
103 params.push((id, value));
104 }
105
106 if offset + 8 > data.len() {
108 return None;
109 }
110 #[allow(clippy::cast_possible_truncation)]
114 let extra_len = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?) as usize;
115 offset += 8;
116
117 let extra = if extra_len > 0 {
118 match offset.checked_add(extra_len) {
123 Some(end) if end <= data.len() => Some(data[offset..end].to_vec()),
124 _ => return None,
125 }
126 } else {
127 None
128 };
129
130 Some(DeserializedState { params, extra })
131}
132
133#[must_use]
143pub fn hash_plugin_id(id: &str) -> u64 {
144 let mut hash: u64 = 0xcbf2_9ce4_8422_2325; for byte in id.bytes() {
146 hash ^= u64::from(byte);
147 hash = hash.wrapping_mul(0x0100_0000_01b3); }
149 hash
150}
151
152#[must_use]
161pub fn vst3_cid(id: &str) -> [u8; 16] {
162 const FNV_OFFSET_BASIS: u128 = 0x6C62_272E_07BB_0142_62B8_2175_6295_C58D;
163 const FNV_PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013B;
164 let mut hash = FNV_OFFSET_BASIS;
165 for byte in id.bytes() {
166 hash ^= u128::from(byte);
167 hash = hash.wrapping_mul(FNV_PRIME);
168 }
169 hash.to_le_bytes()
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn round_trip_state() {
178 let plugin_id = hash_plugin_id("com.test.plugin");
179 let ids = [0u32, 1, 2];
180 let values = [0.5f64, 1.0, -12.0];
181 let extra = b"hello extra state";
182
183 let data = serialize_state(plugin_id, &ids, &values, extra);
184 let state = deserialize_state(&data, plugin_id).unwrap();
185
186 assert_eq!(state.params.len(), 3);
187 assert_eq!(state.params[0], (0, 0.5));
188 assert_eq!(state.params[1], (1, 1.0));
189 assert_eq!(state.params[2], (2, -12.0));
190 assert_eq!(state.extra.unwrap(), b"hello extra state");
191 }
192
193 #[test]
194 fn wrong_plugin_id_fails() {
195 let plugin_id = hash_plugin_id("com.test.plugin");
196 let data = serialize_state(plugin_id, &[], &[], &[]);
197 assert!(deserialize_state(&data, 12345).is_none());
198 }
199}