Skip to main content

spacedb_store/
codec.rs

1//! The two codecs every layer above rests on: a **value codec** and an
2//! **order-preserving key codec**.
3//!
4//! ## Value codec — `postcard`
5//!
6//! Values serialize through `postcard`: compact and **deterministic** (the same
7//! value always produces the same bytes). Determinism is not a nicety here — in
8//! later milestones encoded values feed content-addressing (L2) and re-execution
9//! corroboration (L4), both of which compare bytes/hashes across machines.
10//!
11//! ## Key codec — order-preserving
12//!
13//! Keys are encoded so that **byte-lexicographic order equals logical order**:
14//!
15//! ```text
16//!     a < b   ⟺   encode(a) < encode(b)
17//! ```
18//!
19//! This is the single property that makes [`crate::engine::Readable::range_raw`]
20//! return rows in logical key order — the basis for time-ordered audit, ledger
21//! replay, and pushed-down range scans in the milestones above. It is verified
22//! as a law by the proptests at the bottom of this file.
23//!
24//! ### How each type achieves it
25//!
26//! - **Fixed-width integers** (`u64`) encode as big-endian bytes — BE byte order
27//!   *is* numeric order for unsigned integers.
28//! - **Signed integers** (`i64`) flip the sign bit before BE encoding, so the
29//!   negative range (which has the high bit set) sorts below the non-negative
30//!   range.
31//! - **Strings / byte strings** use an **escaped, terminated** encoding: a `0x00`
32//!   content byte becomes `0x00 0x01`, and the value ends with the terminator
33//!   `0x00 0x00`. Because the terminator sorts below every escaped content byte,
34//!   a string that is a prefix of another sorts first (`"aa" < "aab"`) — plain
35//!   concatenation would get this wrong.
36//! - **Tuples** concatenate their components' encodings. This stays
37//!   order-preserving **only because every component encoding is self-delimiting**
38//!   (integers are fixed-width; strings are terminated), so a shorter first
39//!   component can never bleed into the second.
40//!
41//! Each encoding is also a **bijection** — [`KeyDecode`] reverses it via a cursor
42//! so composite keys can be taken apart in the same order they were built.
43
44use serde::{de::DeserializeOwned, Serialize};
45
46use crate::error::{StoreError, StoreResult};
47
48// ─── Value codec ─────────────────────────────────────────────────────────────
49
50/// Serialize a value to its canonical `postcard` bytes.
51pub fn encode_value<T: Serialize>(value: &T) -> StoreResult<Vec<u8>> {
52    postcard::to_allocvec(value).map_err(StoreError::value_codec)
53}
54
55/// Deserialize a value from its `postcard` bytes.
56pub fn decode_value<T: DeserializeOwned>(bytes: &[u8]) -> StoreResult<T> {
57    postcard::from_bytes(bytes).map_err(StoreError::value_codec)
58}
59
60// ─── Key codec ───────────────────────────────────────────────────────────────
61
62/// A type that can be encoded into an order-preserving byte key.
63///
64/// Implementors MUST satisfy the ordering law `a < b ⟺ encode(a) < encode(b)`
65/// and MUST produce a **self-delimiting** encoding (so tuples compose). Both
66/// properties are checked by the proptests in this module for every built-in.
67pub trait KeyEncode {
68    /// Append this value's order-preserving encoding to `out`.
69    fn encode_into(&self, out: &mut Vec<u8>);
70
71    /// Convenience: encode into a fresh `Vec`.
72    fn encode(&self) -> Vec<u8> {
73        let mut out = Vec::new();
74        self.encode_into(&mut out);
75        out
76    }
77}
78
79/// The inverse of [`KeyEncode`]: decode a value from the front of a byte cursor,
80/// advancing the cursor past the bytes consumed (so tuple components decode in
81/// sequence).
82pub trait KeyDecode: Sized {
83    /// Decode from the front of `buf`, advancing `buf` past the consumed bytes.
84    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self>;
85
86    /// Decode a value that occupies the **entire** slice; errors if trailing
87    /// bytes remain.
88    fn decode(bytes: &[u8]) -> StoreResult<Self> {
89        let mut cur = bytes;
90        let value = Self::decode_from(&mut cur)?;
91        if !cur.is_empty() {
92            return Err(StoreError::key_decode(format!(
93                "{} trailing byte(s) after key",
94                cur.len()
95            )));
96        }
97        Ok(value)
98    }
99}
100
101// --- escaped, terminated byte-string encoding (the basis for strings) ---
102
103const ESC: u8 = 0x00;
104const ESC_LITERAL: u8 = 0x01; // 0x00 0x01 -> a literal 0x00 content byte
105const ESC_TERM: u8 = 0x00; // 0x00 0x00 -> end of the byte string
106
107fn encode_bytes_escaped(bytes: &[u8], out: &mut Vec<u8>) {
108    for &b in bytes {
109        if b == ESC {
110            out.push(ESC);
111            out.push(ESC_LITERAL);
112        } else {
113            out.push(b);
114        }
115    }
116    out.push(ESC);
117    out.push(ESC_TERM);
118}
119
120fn decode_bytes_escaped(buf: &mut &[u8]) -> StoreResult<Vec<u8>> {
121    let data = *buf;
122    let mut out = Vec::new();
123    let mut i = 0;
124    while i < data.len() {
125        let b = data[i];
126        if b != ESC {
127            out.push(b);
128            i += 1;
129            continue;
130        }
131        // b == ESC: must have a following discriminator byte.
132        let next = *data
133            .get(i + 1)
134            .ok_or_else(|| StoreError::key_decode("truncated escape sequence in key"))?;
135        match next {
136            ESC_TERM => {
137                *buf = &data[i + 2..];
138                return Ok(out);
139            }
140            ESC_LITERAL => {
141                out.push(0x00);
142                i += 2;
143            }
144            other => {
145                return Err(StoreError::key_decode(format!(
146                    "invalid escape 0x00 0x{other:02x} in key"
147                )))
148            }
149        }
150    }
151    Err(StoreError::key_decode("unterminated byte string in key"))
152}
153
154// --- u64 ---
155
156impl KeyEncode for u64 {
157    fn encode_into(&self, out: &mut Vec<u8>) {
158        out.extend_from_slice(&self.to_be_bytes());
159    }
160}
161
162impl KeyDecode for u64 {
163    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self> {
164        if buf.len() < 8 {
165            return Err(StoreError::key_decode("need 8 bytes for u64 key"));
166        }
167        let (head, tail) = buf.split_at(8);
168        *buf = tail;
169        let arr: [u8; 8] = head.try_into().expect("split_at(8) yields 8 bytes");
170        Ok(u64::from_be_bytes(arr))
171    }
172}
173
174// --- i64 (sign-bit-flipped big-endian: negatives sort below non-negatives) ---
175
176const I64_SIGN_FLIP: u64 = 1 << 63;
177
178impl KeyEncode for i64 {
179    fn encode_into(&self, out: &mut Vec<u8>) {
180        let biased = (*self as u64) ^ I64_SIGN_FLIP;
181        out.extend_from_slice(&biased.to_be_bytes());
182    }
183}
184
185impl KeyDecode for i64 {
186    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self> {
187        if buf.len() < 8 {
188            return Err(StoreError::key_decode("need 8 bytes for i64 key"));
189        }
190        let (head, tail) = buf.split_at(8);
191        *buf = tail;
192        let arr: [u8; 8] = head.try_into().expect("split_at(8) yields 8 bytes");
193        Ok((u64::from_be_bytes(arr) ^ I64_SIGN_FLIP) as i64)
194    }
195}
196
197// --- String / str ---
198
199impl KeyEncode for String {
200    fn encode_into(&self, out: &mut Vec<u8>) {
201        encode_bytes_escaped(self.as_bytes(), out);
202    }
203}
204
205impl KeyEncode for str {
206    fn encode_into(&self, out: &mut Vec<u8>) {
207        encode_bytes_escaped(self.as_bytes(), out);
208    }
209}
210
211impl KeyEncode for &str {
212    fn encode_into(&self, out: &mut Vec<u8>) {
213        encode_bytes_escaped(self.as_bytes(), out);
214    }
215}
216
217impl KeyDecode for String {
218    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self> {
219        let bytes = decode_bytes_escaped(buf)?;
220        String::from_utf8(bytes).map_err(|e| StoreError::key_decode(format!("key not utf-8: {e}")))
221    }
222}
223
224// --- tuples (self-delimiting components compose) ---
225
226impl<A: KeyEncode, B: KeyEncode> KeyEncode for (A, B) {
227    fn encode_into(&self, out: &mut Vec<u8>) {
228        self.0.encode_into(out);
229        self.1.encode_into(out);
230    }
231}
232
233impl<A: KeyDecode, B: KeyDecode> KeyDecode for (A, B) {
234    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self> {
235        let a = A::decode_from(buf)?;
236        let b = B::decode_from(buf)?;
237        Ok((a, b))
238    }
239}
240
241impl<A: KeyEncode, B: KeyEncode, C: KeyEncode> KeyEncode for (A, B, C) {
242    fn encode_into(&self, out: &mut Vec<u8>) {
243        self.0.encode_into(out);
244        self.1.encode_into(out);
245        self.2.encode_into(out);
246    }
247}
248
249impl<A: KeyDecode, B: KeyDecode, C: KeyDecode> KeyDecode for (A, B, C) {
250    fn decode_from(buf: &mut &[u8]) -> StoreResult<Self> {
251        let a = A::decode_from(buf)?;
252        let b = B::decode_from(buf)?;
253        let c = C::decode_from(buf)?;
254        Ok((a, b, c))
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use proptest::prelude::*;
262
263    // --- value codec ---
264
265    #[test]
266    fn value_codec_round_trips_and_is_deterministic() {
267        #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
268        struct V {
269            a: u32,
270            b: String,
271            c: Vec<u8>,
272        }
273        let v = V {
274            a: 7,
275            b: "hello".into(),
276            c: vec![1, 2, 3],
277        };
278        let e1 = encode_value(&v).unwrap();
279        let e2 = encode_value(&v).unwrap();
280        assert_eq!(e1, e2, "postcard must be deterministic");
281        assert_eq!(decode_value::<V>(&e1).unwrap(), v);
282    }
283
284    // --- key codec: explicit tricky cases for the ordering law ---
285
286    fn enc<K: KeyEncode>(k: &K) -> Vec<u8> {
287        k.encode()
288    }
289
290    #[test]
291    fn string_prefix_sorts_before_extension() {
292        // "aa" is a prefix of "aab"; the terminator must make it sort first.
293        assert!(enc(&"aa".to_string()) < enc(&"aab".to_string()));
294        assert!(enc(&"aa".to_string()) < enc(&"ab".to_string()));
295        assert!(enc(&"".to_string()) < enc(&"a".to_string()));
296    }
297
298    #[test]
299    fn string_with_embedded_null_round_trips_and_orders() {
300        let with_null = String::from_utf8(vec![b'a', 0x00, b'b']).unwrap();
301        let mut buf = enc(&with_null);
302        // round-trip
303        assert_eq!(String::decode(&buf).unwrap(), with_null);
304        // the 0x00 must be escaped, never appear as a bare terminator mid-value
305        buf.clear();
306        with_null.encode_into(&mut buf);
307        assert!(buf.windows(2).filter(|w| *w == [0x00, 0x00]).count() == 1,
308            "only the terminator may be 0x00 0x00");
309    }
310
311    #[test]
312    fn i64_negatives_sort_below_non_negatives() {
313        assert!(enc(&-1i64) < enc(&0i64));
314        assert!(enc(&i64::MIN) < enc(&i64::MAX));
315        assert!(enc(&-5i64) < enc(&-1i64));
316    }
317
318    #[test]
319    fn tuple_orders_by_first_then_second_component() {
320        assert!(enc(&(1u64, "z".to_string())) < enc(&(2u64, "a".to_string())));
321        assert!(enc(&(2u64, "a".to_string())) < enc(&(2u64, "b".to_string())));
322        // a longer first string component must not bleed into the second
323        assert!(enc(&("a".to_string(), "z".to_string())) < enc(&("ab".to_string(), "a".to_string())));
324    }
325
326    // --- key codec: the ordering law + bijection, fuzzed ---
327
328    /// The encoding is order-preserving iff the byte comparison of two encodings
329    /// equals the logical comparison of the values.
330    fn assert_order_law<K: KeyEncode + Ord>(a: &K, b: &K) {
331        assert_eq!(
332            a.cmp(b),
333            enc(a).cmp(&enc(b)),
334            "encoding must preserve order"
335        );
336    }
337
338    proptest! {
339        #[test]
340        fn u64_round_trips(x in any::<u64>()) {
341            prop_assert_eq!(u64::decode(&x.encode()).unwrap(), x);
342        }
343
344        #[test]
345        fn i64_round_trips(x in any::<i64>()) {
346            prop_assert_eq!(i64::decode(&x.encode()).unwrap(), x);
347        }
348
349        #[test]
350        fn string_round_trips(s in any::<String>()) {
351            prop_assert_eq!(String::decode(&s.encode()).unwrap(), s);
352        }
353
354        #[test]
355        fn u64_order_preserving(a in any::<u64>(), b in any::<u64>()) {
356            assert_order_law(&a, &b);
357        }
358
359        #[test]
360        fn i64_order_preserving(a in any::<i64>(), b in any::<i64>()) {
361            assert_order_law(&a, &b);
362        }
363
364        #[test]
365        fn string_order_preserving(a in any::<String>(), b in any::<String>()) {
366            assert_order_law(&a, &b);
367        }
368
369        #[test]
370        fn tuple_u64_string_order_preserving(
371            a in any::<(u64, String)>(),
372            b in any::<(u64, String)>(),
373        ) {
374            assert_order_law(&a, &b);
375        }
376
377        #[test]
378        fn tuple_string_string_round_trips_and_orders(
379            a in any::<(String, String)>(),
380            b in any::<(String, String)>(),
381        ) {
382            prop_assert_eq!(<(String, String)>::decode(&a.encode()).unwrap(), a.clone());
383            assert_order_law(&a, &b);
384        }
385    }
386}