Skip to main content

yo_kv/
frozen.rs

1//! The bytes a collection body turns into on its way out of memory.
2//!
3//! A string demotes by handing the bytes it already is to the tier. A set, a
4//! hash, a list, a sorted set, an array or a stream cannot do that, because its
5//! body is a table or a band or a listpack sitting in a [`Slab`](crate::slab),
6//! and the pointers in it mean nothing to anyone reading the file back. So it is written out as a form
7//! byte and whatever that form needs, and read back into the same representation
8//! it left in. This module is the plumbing both directions share.
9//!
10//! # Why not the RDB serialiser
11//!
12//! [`crate::rdb`] already turns a value into bytes and back, and it is the wrong
13//! tool twice over. It writes Redis's format, so every blob carries a version
14//! envelope and a crc64 that nothing here needs and that costs a pass over the
15//! payload in each direction. Worse, it writes the simple shape: a set that is a
16//! partitioned band comes back a table, and an intset past its ceiling comes back
17//! something that answers a different word to `OBJECT ENCODING`. A value that
18//! changes encoding because it was quiet long enough to be demoted is a value
19//! whose behaviour depends on memory pressure, and that is not something a client
20//! can be asked to reason about. The forms here carry enough to land back in the
21//! exact representation that left, including the flags that only affect what the
22//! encoding is called.
23//!
24//! # The encoding
25//!
26//! Unsigned numbers are LEB128, seven bits a byte, low group first. Signed
27//! numbers are zigzag over the same thing, so a small negative is one byte rather
28//! than ten. Byte strings are a length and then the bytes.
29//!
30//! There is no alignment and no padding anywhere, because the reader is a cursor
31//! over a slice and never a cast. A frozen body is read exactly once, straight
32//! into the structure it rebuilds, so the only thing the layout is tuned for is
33//! being short.
34
35/// What is wrong with a frozen body that will not parse.
36///
37/// Every arm means the same thing operationally, which is that the store handed
38/// back bytes that are not what was written, and the value is gone. They are
39/// separate so the error a client sees can say which check failed, because the
40/// three causes are different bugs: a truncated read, a form written by a newer
41/// version, and a payload whose own structure is broken.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Broken {
44    /// It ended in the middle of something.
45    Short,
46    /// The form byte is not one this version writes.
47    Form,
48    /// The form was understood and the payload inside it was not.
49    Body,
50}
51
52impl std::fmt::Display for Broken {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.write_str(match self {
55            Broken::Short => "the frozen body ends early",
56            Broken::Form => "the frozen body has a form this version cannot read",
57            Broken::Body => "the frozen body has a payload that does not parse",
58        })
59    }
60}
61
62impl std::error::Error for Broken {}
63
64/// Append `n` as LEB128.
65///
66/// Most numbers that come through here are a member count or a byte length and
67/// most of those are under 128, which is the case the loop is shaped for: one
68/// comparison and one push.
69#[inline]
70pub fn put_uint(out: &mut Vec<u8>, mut n: u64) {
71    while n >= 0x80 {
72        out.push((n as u8) | 0x80);
73        n >>= 7;
74    }
75    out.push(n as u8);
76}
77
78/// Append `v` zigzagged, so that a small magnitude is a short encoding either
79/// side of zero.
80#[inline]
81pub fn put_int(out: &mut Vec<u8>, v: i64) {
82    put_uint(out, ((v << 1) ^ (v >> 63)) as u64);
83}
84
85/// Append `v` as its eight raw bytes, little endian.
86///
87/// Not LEB128 and not zigzag, because a double's bit pattern carries its
88/// exponent in the high bits, so every ordinary score would take the full ten
89/// groups plus the sign work. Eight flat bytes are shorter and cost nothing to
90/// read back.
91#[inline]
92pub fn put_f64(out: &mut Vec<u8>, v: f64) {
93    out.extend_from_slice(&v.to_le_bytes());
94}
95
96/// Append `bytes` behind its length.
97#[inline]
98pub fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
99    put_uint(out, bytes.len() as u64);
100    out.extend_from_slice(bytes);
101}
102
103/// A cursor over a frozen body.
104///
105/// Every read either moves the cursor forward and answers, or answers
106/// [`Broken::Short`] and leaves the cursor wherever it was. There is no way to
107/// go backwards, because nothing that reads one of these needs to: a form is
108/// written in the order it is read.
109pub struct Cut<'a> {
110    bytes: &'a [u8],
111    at: usize,
112}
113
114impl<'a> Cut<'a> {
115    /// A cursor at the start of `bytes`.
116    #[must_use]
117    pub const fn new(bytes: &'a [u8]) -> Cut<'a> {
118        Cut { bytes, at: 0 }
119    }
120
121    /// The next byte.
122    #[inline]
123    pub fn byte(&mut self) -> Result<u8, Broken> {
124        let b = *self.bytes.get(self.at).ok_or(Broken::Short)?;
125        self.at += 1;
126        Ok(b)
127    }
128
129    /// The next LEB128 number.
130    ///
131    /// Ten groups at most, which is what a u64 takes, so a payload of nothing but
132    /// continuation bits cannot spin here.
133    #[inline]
134    pub fn uint(&mut self) -> Result<u64, Broken> {
135        let mut n = 0u64;
136        let mut shift = 0;
137        loop {
138            let b = self.byte()?;
139            n |= u64::from(b & 0x7f) << shift;
140            if b < 0x80 {
141                return Ok(n);
142            }
143            shift += 7;
144            if shift >= 64 {
145                return Err(Broken::Body);
146            }
147        }
148    }
149
150    /// The next zigzagged number.
151    #[inline]
152    pub fn int(&mut self) -> Result<i64, Broken> {
153        let n = self.uint()?;
154        Ok(((n >> 1) as i64) ^ -((n & 1) as i64))
155    }
156
157    /// The next double, from its eight raw bytes.
158    #[inline]
159    pub fn f64(&mut self) -> Result<f64, Broken> {
160        let s = self.take(8)?;
161        let mut b = [0u8; 8];
162        b.copy_from_slice(s);
163        Ok(f64::from_le_bytes(b))
164    }
165
166    /// The next `n` bytes.
167    #[inline]
168    pub fn take(&mut self, n: usize) -> Result<&'a [u8], Broken> {
169        let end = self.at.checked_add(n).ok_or(Broken::Short)?;
170        let s = self.bytes.get(self.at..end).ok_or(Broken::Short)?;
171        self.at = end;
172        Ok(s)
173    }
174
175    /// The next length prefixed byte string.
176    #[inline]
177    pub fn bytes(&mut self) -> Result<&'a [u8], Broken> {
178        let n = self.uint()?;
179        // Through `usize` after the read rather than before, so that a length
180        // larger than this machine's address space is a short body and not a
181        // truncating cast that reads the wrong amount.
182        let n = usize::try_from(n).map_err(|_| Broken::Short)?;
183        self.take(n)
184    }
185
186    /// Everything from here to the end, which is what a form that ends in one
187    /// blob wants.
188    ///
189    /// A blob at the end of a form needs no length, because the frozen body is
190    /// its own length. That is the whole reason the two forms that hand back a
191    /// structure Redis already knows how to lay out cost one byte of overhead.
192    #[must_use]
193    #[inline]
194    pub const fn rest(&self) -> &'a [u8] {
195        // Not a slice expression, because this is `const` and indexing is not.
196        self.bytes.split_at(self.at).1
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn a_number_comes_back_as_itself() {
206        for n in [0u64, 1, 127, 128, 300, 16383, 16384, u64::MAX] {
207            let mut out = Vec::new();
208            put_uint(&mut out, n);
209            assert_eq!(Cut::new(&out).uint(), Ok(n), "{n}");
210        }
211    }
212
213    #[test]
214    fn a_signed_number_comes_back_as_itself_and_a_small_one_is_short() {
215        for v in [0i64, 1, -1, 63, -64, 1000, -1000, i64::MIN, i64::MAX] {
216            let mut out = Vec::new();
217            put_int(&mut out, v);
218            assert_eq!(Cut::new(&out).int(), Ok(v), "{v}");
219        }
220        let mut out = Vec::new();
221        put_int(&mut out, -5);
222        assert_eq!(out.len(), 1);
223    }
224
225    #[test]
226    fn a_body_that_ends_early_is_short_and_not_a_panic() {
227        let mut out = Vec::new();
228        put_bytes(&mut out, b"hello");
229        out.truncate(3);
230        assert_eq!(Cut::new(&out).bytes(), Err(Broken::Short));
231        assert_eq!(Cut::new(&[]).byte(), Err(Broken::Short));
232        assert_eq!(Cut::new(&[0x80]).uint(), Err(Broken::Short));
233    }
234
235    #[test]
236    fn nothing_but_continuation_bits_stops() {
237        assert_eq!(Cut::new(&[0x80; 32]).uint(), Err(Broken::Body));
238    }
239
240    #[test]
241    fn the_rest_is_what_is_left() {
242        let mut out = vec![7u8];
243        out.extend_from_slice(b"the blob");
244        let mut cut = Cut::new(&out);
245        assert_eq!(cut.byte(), Ok(7));
246        assert_eq!(cut.rest(), b"the blob");
247    }
248}