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}