Skip to main content

common/serde/
key_prefix.rs

1//! Common record key prefix encoding for OpenData storage systems.
2//!
3//! This module implements RFC 0001: Record Key Prefix. All OpenData records
4//! stored in SlateDB use keys with a standardized 2-byte prefix:
5//!
6//! ```text
7//! ┌───────────┬─────────┬─────────────────────────────────┐
8//! │ subsystem │ version │  ... subsystem-defined fields   │
9//! │  1 byte   │ 1 byte  │            (varies)             │
10//! └───────────┴─────────┴─────────────────────────────────┘
11//! ```
12//!
13//! # Subsystem Byte
14//!
15//! The first byte identifies the subsystem that owns the key.
16//! A value of 0x00 is reserved as an invalid subsystem. See
17//! [`super::subsystem`] for the registry of assigned subsystem bytes.
18//!
19//! # Version Byte
20//!
21//! The second byte identifies the key format version. Each subsystem manages
22//! its version independently. A version of 0x00 is reserved as an invalid version.
23//!
24//! Everything after the 2-byte prefix is defined by the subsystem, including
25//! any record-type discriminator. See [`super::record_tag`] for a reusable
26//! 4+4 bit tag-byte encoding that several subsystems opt into.
27
28use bytes::{BufMut, Bytes, BytesMut};
29
30use super::DeserializeError;
31
32/// A 2-byte key prefix containing subsystem and version.
33///
34/// This type encapsulates the standard prefix used by all OpenData records.
35/// It provides methods for serialization, deserialization, and validation.
36/// Callers constructing a prefix directly must provide non-zero `subsystem`
37/// and `version` values.
38///
39/// # Examples
40///
41/// ```
42/// use common::serde::key_prefix::KeyPrefix;
43///
44/// // Create a prefix
45/// let prefix = KeyPrefix::new(0x10, 0x01);
46/// assert_eq!(prefix.subsystem(), 0x10);
47/// assert_eq!(prefix.version(), 0x01);
48///
49/// // Serialize and deserialize
50/// let bytes = prefix.to_bytes();
51/// let parsed = KeyPrefix::from_bytes(&bytes).unwrap();
52/// assert_eq!(parsed, prefix);
53/// ```
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct KeyPrefix {
56    subsystem: u8,
57    version: u8,
58}
59
60/// Serialized length of the key prefix in bytes.
61pub const KEY_PREFIX_LEN: usize = 2;
62
63impl KeyPrefix {
64    /// Creates a new key prefix with the given subsystem and version.
65    ///
66    /// # Panics
67    ///
68    /// Panics if `subsystem` is 0 or `version` is 0.
69    pub fn new(subsystem: u8, version: u8) -> Self {
70        assert!(subsystem > 0, "subsystem 0 is reserved");
71        assert!(version > 0, "key version 0 is reserved");
72        Self { subsystem, version }
73    }
74
75    /// Returns the subsystem byte.
76    pub fn subsystem(&self) -> u8 {
77        self.subsystem
78    }
79
80    /// Returns the version byte.
81    pub fn version(&self) -> u8 {
82        self.version
83    }
84
85    /// Parses a key prefix from a byte slice.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if:
90    /// - The buffer is too short (less than 2 bytes)
91    /// - The subsystem is 0 (reserved)
92    /// - The key version is 0 (reserved)
93    pub fn from_bytes(data: &[u8]) -> Result<Self, DeserializeError> {
94        if data.len() < KEY_PREFIX_LEN {
95            return Err(DeserializeError {
96                message: format!(
97                    "buffer too short for key prefix: need {} bytes, got {}",
98                    KEY_PREFIX_LEN,
99                    data.len()
100                ),
101            });
102        }
103        let subsystem = validate_subsystem(data[0])?;
104        let version = validate_key_version(data[1])?;
105        Ok(Self { subsystem, version })
106    }
107
108    /// Parses a key prefix, validating the subsystem and version match expected values.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if:
113    /// - The buffer is too short
114    /// - The subsystem is 0
115    /// - The subsystem doesn't match `expected_subsystem`
116    /// - The key version is 0
117    /// - The version doesn't match `expected_version`
118    pub fn from_bytes_with_validation(
119        data: &[u8],
120        expected_subsystem: u8,
121        expected_version: u8,
122    ) -> Result<Self, DeserializeError> {
123        let prefix = Self::from_bytes(data)?;
124        if prefix.subsystem != expected_subsystem {
125            return Err(DeserializeError {
126                message: format!(
127                    "invalid subsystem: expected 0x{:02x}, got 0x{:02x}",
128                    expected_subsystem, prefix.subsystem
129                ),
130            });
131        }
132        if prefix.version != expected_version {
133            return Err(DeserializeError {
134                message: format!(
135                    "invalid key version: expected 0x{:02x}, got 0x{:02x}",
136                    expected_version, prefix.version
137                ),
138            });
139        }
140        Ok(prefix)
141    }
142
143    /// Serializes the prefix to a 2-byte array.
144    pub fn to_bytes(&self) -> Bytes {
145        Bytes::from(vec![self.subsystem, self.version])
146    }
147
148    /// Writes the prefix to a buffer.
149    pub fn write_to(&self, buf: &mut BytesMut) {
150        buf.put_u8(self.subsystem);
151        buf.put_u8(self.version);
152    }
153}
154
155fn validate_key_version(byte: u8) -> Result<u8, DeserializeError> {
156    if byte == 0 {
157        return Err(DeserializeError {
158            message: format!(
159                "invalid key version: 0x{:02x} (version 0 is reserved)",
160                byte
161            ),
162        });
163    }
164    Ok(byte)
165}
166
167fn validate_subsystem(byte: u8) -> Result<u8, DeserializeError> {
168    if byte == 0 {
169        return Err(DeserializeError {
170            message: format!(
171                "invalid subsystem: 0x{:02x} (subsystem 0 is reserved)",
172                byte
173            ),
174        });
175    }
176    Ok(byte)
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn should_create_key_prefix() {
185        // given
186        let subsystem = 0x10;
187        let version = 0x01;
188
189        // when
190        let prefix = KeyPrefix::new(subsystem, version);
191
192        // then
193        assert_eq!(prefix.subsystem(), subsystem);
194        assert_eq!(prefix.version(), version);
195    }
196
197    #[test]
198    #[should_panic(expected = "subsystem 0 is reserved")]
199    fn should_panic_on_zero_subsystem() {
200        KeyPrefix::new(0, 0x01);
201    }
202
203    #[test]
204    #[should_panic(expected = "key version 0 is reserved")]
205    fn should_panic_on_zero_version() {
206        KeyPrefix::new(0x10, 0);
207    }
208
209    #[test]
210    fn should_parse_prefix_from_bytes() {
211        // given
212        let bytes = [0x42, 0x17];
213
214        // when
215        let key_prefix = KeyPrefix::from_bytes(&bytes).unwrap();
216
217        // then
218        assert_eq!(key_prefix.subsystem(), 0x42);
219        assert_eq!(key_prefix.version(), 0x17);
220    }
221
222    #[test]
223    fn should_parse_prefix_ignoring_trailing_bytes() {
224        // given - a buffer longer than the prefix (subsystem-defined fields follow)
225        let bytes = [0x42, 0x17, 0x99, 0xAA, 0xBB];
226
227        // when
228        let key_prefix = KeyPrefix::from_bytes(&bytes).unwrap();
229
230        // then
231        assert_eq!(key_prefix.subsystem(), 0x42);
232        assert_eq!(key_prefix.version(), 0x17);
233    }
234
235    #[test]
236    fn should_reject_zero_subsystem_byte() {
237        // given
238        let bytes = [0x00, 0x17];
239
240        // when
241        let result = KeyPrefix::from_bytes(&bytes);
242
243        // then
244        assert!(
245            matches!(result, Err(DeserializeError { message }) if  message.contains("subsystem 0 is reserved"))
246        );
247    }
248
249    #[test]
250    fn should_reject_zero_version_byte() {
251        // given
252        let bytes = [0x53, 0x00];
253
254        // when
255        let result = KeyPrefix::from_bytes(&bytes);
256
257        // then
258        assert!(
259            matches!(result, Err(DeserializeError { message }) if  message.contains("version 0 is reserved"))
260        );
261    }
262
263    #[test]
264    fn should_write_and_read_key_prefix() {
265        // given
266        let prefix = KeyPrefix::new(0x10, 0x01);
267        let mut buf = BytesMut::new();
268
269        // when
270        prefix.write_to(&mut buf);
271        let parsed = KeyPrefix::from_bytes(&buf).unwrap();
272
273        // then
274        assert_eq!(parsed, prefix);
275    }
276
277    #[test]
278    fn should_serialize_key_prefix_to_bytes() {
279        // given
280        let prefix = KeyPrefix::new(0x10, 0x01);
281
282        // when
283        let bytes = prefix.to_bytes();
284
285        // then
286        assert_eq!(bytes.len(), KEY_PREFIX_LEN);
287        assert_eq!(bytes[0], 0x10);
288        assert_eq!(bytes[1], 0x01);
289    }
290
291    #[test]
292    fn should_parse_key_prefix_with_validation() {
293        // given
294        let expected_subsystem = 0x10;
295        let expected_version = 0x01;
296        let data = [expected_subsystem, expected_version];
297
298        // when
299        let prefix =
300            KeyPrefix::from_bytes_with_validation(&data, expected_subsystem, expected_version)
301                .unwrap();
302
303        // then
304        assert_eq!(prefix.subsystem(), expected_subsystem);
305        assert_eq!(prefix.version(), expected_version);
306    }
307
308    #[test]
309    fn should_reject_wrong_subsystem() {
310        // given
311        let data = [0x10, 0x01];
312
313        // when
314        let result = KeyPrefix::from_bytes_with_validation(&data, 0x20, 0x01);
315
316        // then
317        assert!(
318            matches!(result, Err(DeserializeError { message }) if  message.contains("invalid subsystem"))
319        );
320    }
321
322    #[test]
323    fn should_reject_wrong_version() {
324        // given
325        let data = [0x10, 0x02]; // wrong version
326
327        // when
328        let result = KeyPrefix::from_bytes_with_validation(&data, 0x10, 0x01);
329
330        // then
331        assert!(
332            matches!(result, Err(DeserializeError { message }) if  message.contains("invalid key version"))
333        );
334    }
335
336    #[test]
337    fn should_reject_short_buffer() {
338        // given
339        let data = [0x01]; // only 1 byte
340
341        // when
342        let result = KeyPrefix::from_bytes(&data);
343
344        // then
345        assert!(
346            matches!(result, Err(DeserializeError { message }) if  message.contains("buffer too short"))
347        );
348    }
349}