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