Skip to main content

yo_kv/
rdb.rs

1//! The RDB payload that `DUMP` hands out and `RESTORE` takes back.
2//!
3//! A payload is one value with no key and no deadline, wrapped in ten bytes that
4//! say it is intact:
5//!
6//! ```text
7//! +------+------------------+---------+-----------+
8//! | type | the object       | version | crc64     |
9//! | 1 B  | as many as it is | 2 B LE  | 8 B LE    |
10//! +------+------------------+---------+-----------+
11//!                                     ^ over everything to its left
12//! ```
13//!
14//! This is not a file format even though it is spelled like one. There is no
15//! header, no database selector and no end of file opcode, because the whole
16//! point is that it fits in a bulk string. The version is here so that a server
17//! reading a payload can refuse one from a newer server rather than misread it,
18//! and the checksum is here because `RESTORE` takes bytes from a client and a
19//! client is allowed to be wrong.
20//!
21//! # Two version numbers and not one
22//!
23//! The version we stamp on a payload and the version we will read are different
24//! numbers, because they are answers to opposite questions. What we write is a
25//! promise about how old a server can be and still understand us, so it is as
26//! low as it can be. What we read is a statement about how new a server can be
27//! before a type byte might not mean what it used to, so it is as high as has
28//! actually been checked. [`VERSION`] and [`READS_UP_TO`] say which is which.
29//!
30//! # It has to be Redis's bytes, not ours
31//!
32//! Nothing here is an internal format we get to choose. `MIGRATE` sends this to
33//! another server and `RESTORE` accepts it from any client, so a payload we
34//! produce has to load into a real Redis and a payload a real Redis produces has
35//! to load here. That is the only reason CRC64 exists in `yo-common`, and it is
36//! why the type bytes below are copied from `rdb.h` rather than numbered from
37//! zero in the order this file happens to handle them.
38//!
39//! # Writing the simple shape and reading every shape
40//!
41//! The two directions are deliberately not symmetric. Reading accepts every
42//! encoding a modern Redis emits, because we do not get to pick what arrives.
43//! Writing picks the plainest legal type for each kind, a count followed by the
44//! elements, because every one of those loads into Redis 8.2 and one shape per
45//! kind is one shape to get right.
46//!
47//! # Copying the blob when there is one
48//!
49//! That is the shape for values that are stored as a structure. A value that is
50//! already sitting in one packed blob does not go through it, because
51//! [`crate::listpack`] and [`crate::intset`] are byte compatible with Redis's
52//! own on purpose, so the payload for one of those is the blob with a length in
53//! front of it. A small set, a small hash and a small sorted set are one memcpy
54//! each instead of a walk that decodes every element and encodes it again, and
55//! they are the overwhelming majority of what `DUMP` and `MIGRATE` are pointed
56//! at.
57//!
58//! The rule for which type byte a value gets is the same word `OBJECT ENCODING`
59//! answers with and not the body underneath it, so a set that calls itself a
60//! hashtable is walked even in the corner where its members happen to still be
61//! in one intset run. There is one rule and one place to read it.
62//!
63//! A hash that has been widened for field deadlines is not copied. That band
64//! carries a third element per field and keeps it after the last deadline has
65//! been taken off, so the blob it holds is not the blob `HASH_LISTPACK` means
66//! and the walk is what makes it one.
67//!
68//! What that is worth, from `benches/rdb.rs` at a hundred elements, walked
69//! against copied:
70//!
71//! ```text
72//!   set of text      7.57 us    3.41 us    2.2x
73//!   set of integers  1.32 us    0.57 us    2.3x
74//!   hash            24.94 us    3.95 us    6.3x
75//!   sorted set      21.04 us    3.82 us    5.5x
76//! ```
77//!
78//! The same rows at a thousand elements, which is past every packed band and is
79//! therefore the walk in both runs, moved by under one percent, so nothing here
80//! was paid for by the values that do not benefit. What is left on the copied
81//! rows is a checksum over the payload and one allocation to put it in, and both
82//! of those are paid whichever way the payload was built, which is why the hash
83//! and the sorted set gain more than the two sets do: their walk was the more
84//! expensive one, not their copy the cheaper.
85//!
86//! The load side pays about five percent for this on a hash and a sorted set,
87//! because a listpack entry has to be decoded where a count prefixed element is
88//! read straight off a length. Copying the blob is not free on the way back in
89//! and the trade is still worth making, since a payload is written once and this
90//! is a five percent loss against a five hundred percent gain.
91//!
92//! The load side also stopped asking a listpack for element `i`. There is no
93//! offset table in a listpack, so `get(i)` walks from the front and a loop that
94//! asks for every element in turn costs the square of the count. A hundred field
95//! hash loaded in 81 us and loads in 60. That was there before any of this and
96//! the only thing that ever reached it was a payload from a real server, which
97//! is the case that matters most.
98//!
99//! # Taking the blob back
100//!
101//! A payload for a sorted set on the packed band goes the other way too. The
102//! blob that arrives is the layout that band uses, so it moves in whole rather
103//! than being added a member at a time, and the difference is not small: adding
104//! costs a scan to see whether the member is already there and a second scan to
105//! find where it belongs, both over everything added so far, so it is the square
106//! of the count twice over with a memmove on each one. A hundred member sorted
107//! set restored in 534 us and restores in 4.6 us.
108//!
109//! The blob is checked before it is taken. This band answers a rank query by
110//! position and by nothing else, so a payload that says it is a sorted set while
111//! not being sorted would answer `ZRANGE` with the wrong members and never say
112//! why, and a payload with the same member twice would report a length nothing
113//! else agrees with. One pass rules out both, since strictly increasing means no
114//! two members compare equal on the score and then equal on the bytes. A blob
115//! that fails the check, or that is past this server's limits, is handed back
116//! and walked, which is what the reader did with every payload before this.
117//!
118//! A sorted set past the band is sized from the count now, the way a set and a
119//! hash already were. It used to start packed whatever the count said, fill to
120//! the band limit at a scan a member, and throw the listpack away. A thousand
121//! member sorted set restored in 1.23 ms and restores in 88 us.
122//!
123//! The hash gets the same treatment, and the only hard part was the bit the
124//! sorted set got for free. A sorted set blob is ordered, so one pass proving it
125//! is strictly increasing also proves no member is in it twice. A hash blob is
126//! in insertion order, and a repeated field would give a hash whose `HLEN`
127//! counts both rows and whose `HGET` and `HDEL` only ever reach the first, so
128//! the length would disagree with `HGETALL` and a delete would leave the field
129//! behind. `Hash::from_packed` rules that out by hashing each field into a
130//! stack array and sorting it, which is one pass and a sort rather than the
131//! square of the count, and a collision costs a fallback to the walk and not a
132//! wrong answer. A hundred field hash restored in 62.8 us and restores in 5.8.
133//!
134//! Only the two element form. The band with a deadline after every value is not
135//! handed over, for the same reason `Hash::packed_bytes` will not copy it on
136//! the way out: that column has its own type byte and its own header, and a hash
137//! that has been widened once keeps the third element per field forever after.
138//!
139//! # Compression
140//!
141//! Redis compresses strings over twenty bytes with LZF when `rdbcompression` is
142//! on, which it is by default. Nothing here compresses on the way out, because
143//! an uncompressed string is legal and every reader accepts it. Decompression on
144//! the way in is not optional, because payloads arriving from a real Redis are
145//! full of LZF strings.
146
147use std::borrow::Cow;
148
149use yo_common::crc::crc64;
150use yo_common::num::{self, DIGITS_MAX};
151
152use crate::hash::{self, Hash};
153use crate::intset::Intset;
154use crate::keys::{Body, Record};
155use crate::list::{self, List};
156use crate::listpack::{Entry, Listpack};
157use crate::set::{self, Set};
158use crate::zset::{self, Zset};
159
160/// The RDB version this server writes into the footer.
161///
162/// Redis refuses a payload whose version is above its own, so this being right
163/// is the difference between a payload another server will look at and one it
164/// throws away without reading. Lower is friendlier, and twelve is as low as
165/// this can go: it is the version that introduced the hash with field deadlines,
166/// which is a shape this server writes.
167pub const VERSION: u16 = 12;
168
169/// The highest version in a footer this server will still read.
170///
171/// A different number from [`VERSION`], and the two mean opposite things. What
172/// we write is a promise about how old a server can be and still understand us.
173/// What we read is a statement about how new a server can be before we stop
174/// trusting that a type byte still means what it used to.
175///
176/// Fifteen because that is what a Redis 8.10.1 stamps on a payload, read off one
177/// over a socket rather than out of a header file. Refusing it is not a small
178/// bug: it means `RESTORE` turns down every payload a current server produces,
179/// with a message about the checksum that sends the reader to entirely the wrong
180/// place. That is what this constant existing separately is here to stop.
181///
182/// It goes up when a newer server has been checked and not before. The guard is
183/// worth keeping rather than removing, because the day Redis reuses a type byte
184/// for a different layout, refusing to read it is the only safe answer and a
185/// wrong value is worse than no value.
186pub const READS_UP_TO: u16 = 15;
187
188/// The footer: two bytes of version and eight of checksum.
189const FOOTER: usize = 10;
190
191// The object type byte. These are `rdb.h`, and the gaps are types this server
192// cannot hold, so they are not named.
193const T_STRING: u8 = 0;
194const T_LIST: u8 = 1;
195const T_SET: u8 = 2;
196const T_ZSET: u8 = 3;
197const T_HASH: u8 = 4;
198const T_ZSET_2: u8 = 5;
199const T_SET_INTSET: u8 = 11;
200const T_HASH_LISTPACK: u8 = 16;
201const T_ZSET_LISTPACK: u8 = 17;
202const T_LIST_QUICKLIST_2: u8 = 18;
203const T_SET_LISTPACK: u8 = 20;
204const T_HASH_METADATA: u8 = 24;
205const T_HASH_LISTPACK_EX: u8 = 25;
206
207// The length encoding, `00` and `01` in the top two bits for six and fourteen
208// bit lengths, then two whole byte forms, and `11` for the special encodings.
209const LEN_6BIT: u8 = 0;
210const LEN_14BIT: u8 = 1;
211const LEN_32BIT: u8 = 0x80;
212const LEN_64BIT: u8 = 0x81;
213const LEN_ENCODED: u8 = 3;
214
215// What a `11` length means: three integer widths and a compressed blob.
216const ENC_INT8: u64 = 0;
217const ENC_INT16: u64 = 1;
218const ENC_INT32: u64 = 2;
219const ENC_LZF: u64 = 3;
220
221/// A quicklist node holding a listpack rather than one long value.
222const NODE_PACKED: u64 = 2;
223/// A quicklist node that is one value too big for a listpack.
224const NODE_PLAIN: u64 = 1;
225
226/// Why a payload was not accepted.
227///
228/// Two variants because `RESTORE` has two complaints and a client can tell them
229/// apart. A bad footer means the bytes were damaged or came from a newer server,
230/// and everything else means they were intact and still did not make sense.
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232pub enum Bad {
233    /// The version is from the future or the checksum does not match.
234    Footer,
235    /// The bytes are self consistent and are not a value this server can hold.
236    Format,
237}
238
239/// Where a load is allowed to put what it builds.
240///
241/// The four representation thresholds, borrowed rather than copied, because a
242/// restore has to land in the same band the same data would have landed in had
243/// it been written a command at a time. A hash restored into a listpack on a
244/// server configured for tables would answer the wrong thing to `OBJECT
245/// ENCODING` and would be a different size for the rest of its life.
246#[derive(Debug, Clone, Copy)]
247pub struct Limits<'a> {
248    /// `set-max-intset-entries` and the two listpack thresholds.
249    pub set: &'a set::Limits,
250    /// `hash-max-listpack-entries` and `hash-max-listpack-value`.
251    pub hash: &'a hash::Limits,
252    /// `list-max-listpack-size`, as bytes or as a count.
253    pub list: &'a list::Limits,
254    /// `zset-max-listpack-entries` and `zset-max-listpack-value`.
255    pub zset: &'a zset::Limits,
256}
257
258// ---------------------------------------------------------------------------
259// The wrapper: version and checksum.
260// ---------------------------------------------------------------------------
261
262/// Put the version and the checksum on the end of a serialised object.
263fn seal(mut body: Vec<u8>) -> Vec<u8> {
264    body.extend_from_slice(&VERSION.to_le_bytes());
265    let crc = crc64(0, &body);
266    body.extend_from_slice(&crc.to_le_bytes());
267    body
268}
269
270/// Check the footer and hand back everything in front of it.
271///
272/// The version check comes before the checksum, which is the order Redis uses
273/// and the order that gives the better answer: a payload from a newer server is
274/// usually intact, and telling somebody their bytes are corrupt when they are
275/// merely from next year sends them looking in the wrong place.
276fn unseal(payload: &[u8]) -> Result<&[u8], Bad> {
277    if payload.len() < FOOTER {
278        return Err(Bad::Footer);
279    }
280    let split = payload.len() - FOOTER;
281    let (body, foot) = payload.split_at(split);
282    let version = u16::from_le_bytes([foot[0], foot[1]]);
283    if version > READS_UP_TO {
284        return Err(Bad::Footer);
285    }
286    let stored = u64::from_le_bytes(foot[2..].try_into().expect("ten byte footer, eight left"));
287    if stored != crc64(0, &payload[..payload.len() - 8]) {
288        return Err(Bad::Footer);
289    }
290    Ok(body)
291}
292
293// ---------------------------------------------------------------------------
294// Writing.
295// ---------------------------------------------------------------------------
296
297/// Serialise a record's value, footer and all.
298///
299/// The deadline does not go in. `DUMP` deliberately drops it and `RESTORE` takes
300/// a fresh one as an argument, because a payload that travels for a while would
301/// otherwise arrive already expired or, worse, silently alive for longer than
302/// anybody meant.
303///
304/// `None` for a value with no RDB shape at all, which today is only the sparse
305/// array. No command on the wire can create one yet, so no client can reach this,
306/// and it is a `None` rather than a panic so that the day the document commands
307/// land the answer is a missing key and not a dead server.
308pub(crate) fn dump(rec: &Record) -> Option<Vec<u8>> {
309    let mut out = Vec::new();
310    match rec.body() {
311        // The same `None` a sparse array gets, and for a stronger reason: a
312        // foreign body is an engine that lives above this crate and there is no
313        // byte shape for it here to write even in principle. `DUMP` on a graph
314        // is refused by the dispatch before it reaches this, so nothing sees
315        // the null bulk this would otherwise produce.
316        Body::Foreign(_) => return None,
317        Body::String(bytes) => {
318            out.push(T_STRING);
319            put_str(&mut out, bytes);
320        }
321        Body::List(list) => {
322            out.push(T_LIST);
323            put_len(&mut out, list.len() as u64);
324            for element in list.iter() {
325                put_entry(&mut out, element);
326            }
327        }
328        Body::Set(set) => match (set.encoding(), set.packed_bytes()) {
329            (set::Encoding::Intset, Some(blob)) => {
330                out.push(T_SET_INTSET);
331                put_str(&mut out, blob);
332            }
333            (set::Encoding::Listpack, Some(blob)) => {
334                out.push(T_SET_LISTPACK);
335                put_str(&mut out, blob);
336            }
337            _ => {
338                out.push(T_SET);
339                put_len(&mut out, set.len() as u64);
340                for member in set.iter() {
341                    put_entry(&mut out, member);
342                }
343            }
344        },
345        Body::Zset(zset) => match zset.packed_bytes() {
346            Some(blob) => {
347                out.push(T_ZSET_LISTPACK);
348                put_str(&mut out, blob);
349            }
350            None => {
351                out.push(T_ZSET_2);
352                put_len(&mut out, zset.len() as u64);
353                zset.walk(0, zset.len(), false, |member, score| {
354                    put_entry(&mut out, member);
355                    out.extend_from_slice(&score.to_le_bytes());
356                });
357            }
358        },
359        Body::Hash(hash) => put_hash(&mut out, hash),
360        // Neither of these has an RDB shape here yet. The sparse array is ours
361        // and has no Redis number to write under, and the stream does have one
362        // and is the next thing to land: the node layout is already byte for
363        // byte Redis's and there is a test holding it against a real `DUMP`, so
364        // what is missing is the envelope around it and the `RESTORE` side.
365        // Until then `DUMP` of a stream answers the same null a missing key
366        // does, which is a divergence and is registered as one.
367        Body::Array(_) | Body::Stream(_) => return None,
368    }
369    Some(seal(out))
370}
371
372/// A hash, in the plain shape or the one that carries field deadlines.
373///
374/// Two types because the deadline costs a length prefixed number on every single
375/// field, and the overwhelming majority of hashes have no deadline anywhere.
376/// Redis makes the same split for the same reason, and the trick it uses is
377/// worth copying: the earliest deadline in the hash goes in the header, and each
378/// field stores the difference from it plus one, so a field with no deadline is
379/// a zero and everything else is a small number rather than a full timestamp.
380fn put_hash(out: &mut Vec<u8>, hash: &Hash) {
381    let Some(soonest) = hash.soonest_deadline() else {
382        if let Some(blob) = hash.packed_bytes() {
383            out.push(T_HASH_LISTPACK);
384            put_str(out, blob);
385            return;
386        }
387        out.push(T_HASH);
388        put_len(out, hash.len() as u64);
389        for (field, value) in hash.iter() {
390            put_entry(out, field);
391            put_entry(out, value);
392        }
393        return;
394    };
395    out.push(T_HASH_METADATA);
396    out.extend_from_slice(&soonest.to_le_bytes());
397    put_len(out, hash.len() as u64);
398    for i in 0..hash.len() {
399        let (field, value) = hash.at(i).expect("index is under the length");
400        // Saturating rather than subtracting, because `soonest_deadline` is
401        // documented as a lower bound and a bound that is early by a millisecond
402        // would underflow into a deadline a few hundred million years out.
403        let ttl = match hash.deadline_at(i) {
404            Some(at) => at.saturating_sub(soonest) + 1,
405            None => 0,
406        };
407        put_len(out, ttl);
408        put_entry(out, field);
409        put_entry(out, value);
410    }
411}
412
413/// A length, in the smallest of the four forms that holds it.
414fn put_len(out: &mut Vec<u8>, n: u64) {
415    if n < 1 << 6 {
416        out.push((LEN_6BIT << 6) | n as u8);
417    } else if n < 1 << 14 {
418        out.push((LEN_14BIT << 6) | (n >> 8) as u8);
419        out.push(n as u8);
420    } else if n <= u64::from(u32::MAX) {
421        out.push(LEN_32BIT);
422        out.extend_from_slice(&(n as u32).to_be_bytes());
423    } else {
424        out.push(LEN_64BIT);
425        out.extend_from_slice(&n.to_be_bytes());
426    }
427}
428
429/// A string, integer encoded when that is both possible and shorter.
430fn put_str(out: &mut Vec<u8>, s: &[u8]) {
431    // Redis only tries the integer encoding on strings short enough to be one,
432    // which saves parsing every long value that starts with a digit.
433    if s.len() <= 11
434        && let Some(n) = num::parse_i64(s)
435        && let mut buf = [0u8; DIGITS_MAX]
436        && num::i64_digits(&mut buf, n) == s
437        && put_int(out, n)
438    {
439        return;
440    }
441    put_len(out, s.len() as u64);
442    out.extend_from_slice(s);
443}
444
445/// An element straight out of a collection.
446///
447/// A listpack already knows whether it is holding an integer, so an integer
448/// element goes out in the integer encoding without ever being formatted into
449/// digits and parsed back. That is the same saving the reply path makes and it
450/// is why elements come back as an [`Entry`] rather than as bytes.
451fn put_entry(out: &mut Vec<u8>, entry: Entry<'_>) {
452    match entry {
453        Entry::Int(n) => {
454            if !put_int(out, n) {
455                let mut buf = [0u8; DIGITS_MAX];
456                let digits = num::i64_digits(&mut buf, n);
457                put_len(out, digits.len() as u64);
458                out.extend_from_slice(digits);
459            }
460        }
461        Entry::Str(s) => put_str(out, s),
462    }
463}
464
465/// An integer in one of the three widths, or `false` if it does not fit any.
466///
467/// There is no 64 bit form. A number past `i32` goes out as digits, which is
468/// what Redis does, and it is not the oversight it looks like: the encoding is
469/// there to make short strings shorter and a nineteen digit number in eight
470/// bytes saves eleven bytes on a value that is already rare.
471fn put_int(out: &mut Vec<u8>, n: i64) -> bool {
472    if let Ok(v) = i8::try_from(n) {
473        out.push((LEN_ENCODED << 6) | ENC_INT8 as u8);
474        out.push(v as u8);
475    } else if let Ok(v) = i16::try_from(n) {
476        out.push((LEN_ENCODED << 6) | ENC_INT16 as u8);
477        out.extend_from_slice(&v.to_le_bytes());
478    } else if let Ok(v) = i32::try_from(n) {
479        out.push((LEN_ENCODED << 6) | ENC_INT32 as u8);
480        out.extend_from_slice(&v.to_le_bytes());
481    } else {
482        return false;
483    }
484    true
485}
486
487// ---------------------------------------------------------------------------
488// Reading.
489// ---------------------------------------------------------------------------
490
491/// A position in a payload, and the only thing allowed to advance it.
492///
493/// Every read goes through here so that a truncated payload is one error at one
494/// place rather than a bounds check per field that somebody eventually forgets.
495struct Reader<'a> {
496    buf: &'a [u8],
497    at: usize,
498}
499
500impl<'a> Reader<'a> {
501    const fn new(buf: &'a [u8]) -> Reader<'a> {
502        Reader { buf, at: 0 }
503    }
504
505    fn byte(&mut self) -> Result<u8, Bad> {
506        let b = *self.buf.get(self.at).ok_or(Bad::Format)?;
507        self.at += 1;
508        Ok(b)
509    }
510
511    fn take(&mut self, n: usize) -> Result<&'a [u8], Bad> {
512        let end = self.at.checked_add(n).ok_or(Bad::Format)?;
513        let s = self.buf.get(self.at..end).ok_or(Bad::Format)?;
514        self.at = end;
515        Ok(s)
516    }
517
518    /// How many elements follow, refusing a count the payload cannot hold.
519    ///
520    /// The count in a payload is four bytes wide and the payload is whatever
521    /// length it happens to be, so nothing in the format stops one from claiming
522    /// two billion members. Every reader that takes a count then hands it to a
523    /// `with_hint`, which is the whole point of a hint, and a hint of two billion
524    /// asks the allocator for thirty four gigabytes before a single element has
525    /// been read. That is not a hypothetical: it is what a `RESTORE` of a
526    /// truncated payload did, and on Linux the allocator refused and the process
527    /// went down, which turns a bad payload from one client into an outage for
528    /// everybody.
529    ///
530    /// The bound is the bytes that are left. An element takes at least one byte
531    /// however it is encoded, so a count past what remains cannot be honest, and
532    /// checking it here means every reader gets the check rather than the ones
533    /// somebody remembered. It is deliberately loose: it is not trying to work
534    /// out the real minimum for each type, only to keep an allocation in the same
535    /// order of magnitude as the bytes that arrived.
536    ///
537    /// Zero is refused with it, for the reason [`non_empty`] gives.
538    fn count(&mut self) -> Result<usize, Bad> {
539        let n = non_empty(self.len()?)?;
540        if n > self.buf.len() - self.at {
541            return Err(Bad::Format);
542        }
543        Ok(n)
544    }
545
546    /// A length, refusing the `11` forms that are not lengths at all.
547    fn len(&mut self) -> Result<usize, Bad> {
548        match self.len_or_encoding()? {
549            (n, false) => usize::try_from(n).map_err(|_| Bad::Format),
550            (_, true) => Err(Bad::Format),
551        }
552    }
553
554    /// A length, and whether it was one of the special encodings instead.
555    fn len_or_encoding(&mut self) -> Result<(u64, bool), Bad> {
556        let first = self.byte()?;
557        match first >> 6 {
558            LEN_6BIT => Ok((u64::from(first & 0x3f), false)),
559            LEN_14BIT => {
560                let second = self.byte()?;
561                Ok(((u64::from(first & 0x3f) << 8) | u64::from(second), false))
562            }
563            LEN_ENCODED => Ok((u64::from(first & 0x3f), true)),
564            // The remaining two bit pattern is `10`, where the whole first byte
565            // says which width follows rather than carrying any of the length.
566            _ => match first {
567                LEN_32BIT => {
568                    let b = self.take(4)?;
569                    Ok((
570                        u64::from(u32::from_be_bytes(b.try_into().expect("four bytes"))),
571                        false,
572                    ))
573                }
574                LEN_64BIT => {
575                    let b = self.take(8)?;
576                    Ok((
577                        u64::from_be_bytes(b.try_into().expect("eight bytes")),
578                        false,
579                    ))
580                }
581                _ => Err(Bad::Format),
582            },
583        }
584    }
585
586    /// A string, whichever of the five ways it was written.
587    ///
588    /// Borrowed when the bytes are already there and owned when they had to be
589    /// built, which is the integer encodings and LZF. Most strings in a payload
590    /// are plain, so most of them cost nothing here.
591    fn str(&mut self) -> Result<Cow<'a, [u8]>, Bad> {
592        let (n, encoded) = self.len_or_encoding()?;
593        if !encoded {
594            let n = usize::try_from(n).map_err(|_| Bad::Format)?;
595            return Ok(Cow::Borrowed(self.take(n)?));
596        }
597        let value = match n {
598            ENC_INT8 => i64::from(self.byte()? as i8),
599            ENC_INT16 => {
600                let b = self.take(2)?;
601                i64::from(i16::from_le_bytes(b.try_into().expect("two bytes")))
602            }
603            ENC_INT32 => {
604                let b = self.take(4)?;
605                i64::from(i32::from_le_bytes(b.try_into().expect("four bytes")))
606            }
607            ENC_LZF => {
608                let packed = self.len()?;
609                let plain = self.len()?;
610                let bytes = self.take(packed)?;
611                return unpack(bytes, plain).map(Cow::Owned).ok_or(Bad::Format);
612            }
613            _ => return Err(Bad::Format),
614        };
615        let mut buf = [0u8; DIGITS_MAX];
616        Ok(Cow::Owned(num::i64_digits(&mut buf, value).to_vec()))
617    }
618
619    /// A score in the binary form, which is `ZSET_2` and everything since.
620    fn double(&mut self) -> Result<f64, Bad> {
621        let b = self.take(8)?;
622        Ok(f64::from_le_bytes(b.try_into().expect("eight bytes")))
623    }
624
625    /// A score in the old text form, which only `ZSET` uses.
626    ///
627    /// A length byte and then that many digits, with three of the lengths
628    /// reserved to mean the three values that have no digits.
629    fn double_text(&mut self) -> Result<f64, Bad> {
630        match self.byte()? {
631            255 => Ok(f64::NEG_INFINITY),
632            254 => Ok(f64::INFINITY),
633            253 => Ok(f64::NAN),
634            n => {
635                let digits = self.take(n as usize)?;
636                num::parse_f64(digits).ok_or(Bad::Format)
637            }
638        }
639    }
640
641    /// Whether every byte has been read, which a well formed payload has.
642    const fn done(&self) -> bool {
643        self.at == self.buf.len()
644    }
645}
646
647/// LZF, the one compression Redis puts in an RDB payload.
648///
649/// A control byte either introduces a run of literals or points backwards into
650/// what has already been written. The back reference is allowed to overlap what
651/// it is producing, which is how a long run of one byte compresses, so the copy
652/// has to go one byte at a time rather than through a slice copy.
653///
654/// `plain` is the length the payload claims the result will be, and it is used
655/// as the bound rather than trusted, so a payload claiming four bytes and
656/// describing four gigabytes stops at four.
657fn unpack(packed: &[u8], plain: usize) -> Option<Vec<u8>> {
658    let mut out = Vec::with_capacity(plain.min(1 << 20));
659    let mut i = 0;
660    while i < packed.len() {
661        let ctrl = usize::from(packed[i]);
662        i += 1;
663        if ctrl < 32 {
664            let run = ctrl + 1;
665            let end = i.checked_add(run)?;
666            if end > packed.len() || out.len() + run > plain {
667                return None;
668            }
669            out.extend_from_slice(&packed[i..end]);
670            i = end;
671        } else {
672            let mut run = ctrl >> 5;
673            if run == 7 {
674                run += usize::from(*packed.get(i)?);
675                i += 1;
676            }
677            let back = ((ctrl & 0x1f) << 8) + usize::from(*packed.get(i)?) + 1;
678            i += 1;
679            let run = run + 2;
680            if back > out.len() || out.len() + run > plain {
681                return None;
682            }
683            let from = out.len() - back;
684            for at in from..from + run {
685                out.push(out[at]);
686            }
687        }
688    }
689    (out.len() == plain).then_some(out)
690}
691
692/// Turn a payload back into a value.
693///
694/// `now` is here for one reason: a hash can carry deadlines and a field whose
695/// deadline has already gone is not put back. Restoring it would leave a field
696/// that the very next read would delete, and a count that is wrong until
697/// somebody looks.
698pub(crate) fn load(payload: &[u8], limits: Limits<'_>, now: u64) -> Result<Body, Bad> {
699    let body = unseal(payload)?;
700    let mut r = Reader::new(body);
701    let kind = r.byte()?;
702    let value = match kind {
703        T_STRING => Body::String(r.str()?.into_owned()),
704        T_LIST => read_list(&mut r, limits.list)?,
705        T_LIST_QUICKLIST_2 => read_quicklist(&mut r, limits.list)?,
706        T_SET => read_set(&mut r, limits.set)?,
707        T_SET_INTSET => read_intset(&mut r, limits.set)?,
708        T_SET_LISTPACK => read_set_listpack(&mut r, limits.set)?,
709        T_ZSET | T_ZSET_2 => read_zset(&mut r, limits.zset, kind == T_ZSET_2)?,
710        T_ZSET_LISTPACK => read_zset_listpack(&mut r, limits.zset)?,
711        T_HASH => read_hash(&mut r, limits.hash)?,
712        T_HASH_METADATA => read_hash_metadata(&mut r, limits.hash, now)?,
713        T_HASH_LISTPACK => read_hash_listpack(&mut r, limits.hash, false, now)?,
714        T_HASH_LISTPACK_EX => read_hash_listpack(&mut r, limits.hash, true, now)?,
715        _ => return Err(Bad::Format),
716    };
717    // Trailing bytes mean the payload was not what it said it was, even though
718    // everything read so far parsed. Redis is stricter than it looks here and so
719    // is this, because a payload with something extra on the end is either a
720    // different version's idea of the same type or somebody probing.
721    if !r.done() {
722        return Err(Bad::Format);
723    }
724    Ok(value)
725}
726
727/// An empty collection is not a value, it is a deleted key.
728///
729/// Redis calls this `emptykey` and refuses the payload rather than creating a
730/// key that every command would treat as missing. A zero length collection
731/// cannot be produced by any command, so a payload holding one was either
732/// hand written or corrupted in a way the checksum happened to survive.
733fn non_empty(n: usize) -> Result<usize, Bad> {
734    if n == 0 { Err(Bad::Format) } else { Ok(n) }
735}
736
737fn read_list(r: &mut Reader<'_>, limits: &list::Limits) -> Result<Body, Bad> {
738    let n = r.count()?;
739    let mut list = List::new();
740    for _ in 0..n {
741        list.push_back(&r.str()?, limits);
742    }
743    Ok(Body::List(list))
744}
745
746/// A quicklist, which is a count of nodes and then a blob each.
747///
748/// A packed node is a whole listpack and a plain node is a single value that was
749/// too long to pack, and both of them are written as one string, so the only
750/// difference is whether the string is parsed or pushed.
751fn read_quicklist(r: &mut Reader<'_>, limits: &list::Limits) -> Result<Body, Bad> {
752    let nodes = r.count()?;
753    let mut list = List::new();
754    for _ in 0..nodes {
755        let container = r.len_or_encoding()?.0;
756        let blob = r.str()?;
757        match container {
758            NODE_PLAIN => list.push_back(&blob, limits),
759            NODE_PACKED => {
760                let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
761                let mut buf = [0u8; DIGITS_MAX];
762                for entry in lp.iter() {
763                    list.push_back(text(entry, &mut buf), limits);
764                }
765            }
766            _ => return Err(Bad::Format),
767        }
768    }
769    if list.is_empty() {
770        return Err(Bad::Format);
771    }
772    Ok(Body::List(list))
773}
774
775fn read_set(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
776    let n = r.count()?;
777    let first = r.str()?;
778    // The hint and the first member together are what decide the band, so the
779    // first member is read before the set is built rather than after.
780    let mut set = Set::with_hint(&first, n, limits);
781    set.add(&first, limits);
782    for _ in 1..n {
783        set.add(&r.str()?, limits);
784    }
785    Ok(Body::Set(set))
786}
787
788fn read_intset(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
789    let blob = r.str()?;
790    let ints = Intset::from_bytes(&blob).map_err(|_| Bad::Format)?;
791    non_empty(ints.len())?;
792    let mut buf = [0u8; DIGITS_MAX];
793    let mut set = Set::with_hint(num::i64_digits(&mut buf, ints.at(0)), ints.len(), limits);
794    for v in ints.iter() {
795        set.add(num::i64_digits(&mut buf, v), limits);
796    }
797    Ok(Body::Set(set))
798}
799
800fn read_set_listpack(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
801    let blob = r.str()?;
802    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
803    non_empty(lp.len())?;
804    let mut buf = [0u8; DIGITS_MAX];
805    let first = text(lp.get(0).ok_or(Bad::Format)?, &mut buf).to_vec();
806    let mut set = Set::with_hint(&first, lp.len(), limits);
807    for entry in lp.iter() {
808        set.add(text(entry, &mut buf), limits);
809    }
810    Ok(Body::Set(set))
811}
812
813fn read_zset(r: &mut Reader<'_>, limits: &zset::Limits, binary: bool) -> Result<Body, Bad> {
814    let n = r.count()?;
815    // Sized from the count, the way `read_set` and `read_hash` are. A sorted set
816    // that is going to end up on the table should start there, rather than fill
817    // the packed band to its limit at a scan a member and then throw it away.
818    let mut zset = Zset::with_hint(n, limits);
819    for _ in 0..n {
820        let member = r.str()?;
821        let score = if binary {
822            r.double()?
823        } else {
824            r.double_text()?
825        };
826        zset.add(&member, score, limits);
827    }
828    Ok(Body::Zset(zset))
829}
830
831fn read_zset_listpack(r: &mut Reader<'_>, limits: &zset::Limits) -> Result<Body, Bad> {
832    let blob = r.str()?;
833    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
834    if lp.is_empty() || lp.len() % 2 != 0 {
835        return Err(Bad::Format);
836    }
837    // The payload is already the layout the packed band uses, so the fast answer
838    // is to take it whole rather than to add a member at a time. `from_packed`
839    // hands the blob back when it will not have it, and then the walk below
840    // rebuilds it, which is what happens to a payload that is out of order or
841    // past this server's limits.
842    let lp = match Zset::from_packed(lp, limits) {
843        Ok(zset) => return Ok(Body::Zset(zset)),
844        Err(lp) => lp,
845    };
846    let mut zset = Zset::with_hint(lp.len() / 2, limits);
847    let mut member = [0u8; DIGITS_MAX];
848    let mut score = [0u8; DIGITS_MAX];
849    // Walked and not indexed. A listpack has no offset table, so asking it for
850    // element `i` costs a walk from the front and asking it for every element in
851    // turn costs the square of the count.
852    let mut walk = lp.iter();
853    while let Some(entry) = walk.next() {
854        let name = text(entry, &mut member).to_vec();
855        let at = text(walk.next().ok_or(Bad::Format)?, &mut score);
856        let at = num::parse_f64(at).ok_or(Bad::Format)?;
857        zset.add(&name, at, limits);
858    }
859    Ok(Body::Zset(zset))
860}
861
862fn read_hash(r: &mut Reader<'_>, limits: &hash::Limits) -> Result<Body, Bad> {
863    let n = r.count()?;
864    let mut hash = Hash::with_hint(n, limits);
865    for _ in 0..n {
866        let field = r.str()?;
867        let value = r.str()?;
868        hash.set(&field, &value, limits);
869    }
870    Ok(Body::Hash(hash))
871}
872
873/// A hash with field deadlines, which is a header, a count and then triples.
874///
875/// The header is the earliest deadline in the hash and each field holds its own
876/// distance from it, plus one so that a zero can mean no deadline at all.
877fn read_hash_metadata(r: &mut Reader<'_>, limits: &hash::Limits, now: u64) -> Result<Body, Bad> {
878    let soonest = u64::from_le_bytes(r.take(8)?.try_into().expect("eight bytes"));
879    let n = r.count()?;
880    let mut hash = Hash::with_hint(n, limits);
881    for _ in 0..n {
882        let ttl = r.len_or_encoding()?.0;
883        let field = r.str()?;
884        let value = r.str()?;
885        put_field(
886            &mut hash,
887            &field,
888            &value,
889            deadline(soonest, ttl),
890            limits,
891            now,
892        );
893    }
894    if hash.is_empty() {
895        return Err(Bad::Format);
896    }
897    Ok(Body::Hash(hash))
898}
899
900/// A hash packed into one listpack, with or without the deadline column.
901fn read_hash_listpack(
902    r: &mut Reader<'_>,
903    limits: &hash::Limits,
904    with_ttl: bool,
905    now: u64,
906) -> Result<Body, Bad> {
907    let soonest = if with_ttl {
908        u64::from_le_bytes(r.take(8)?.try_into().expect("eight bytes"))
909    } else {
910        0
911    };
912    let blob = r.str()?;
913    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
914    let step = if with_ttl { 3 } else { 2 };
915    if lp.is_empty() || lp.len() % step != 0 {
916        return Err(Bad::Format);
917    }
918    // The payload is already the layout the packed band uses, so take it whole
919    // rather than set a field at a time. Only the two element form: the deadline
920    // column is a band this cannot hand over, for the reason `packed_bytes`
921    // refuses to copy it on the way out.
922    let lp = if with_ttl {
923        lp
924    } else {
925        match Hash::from_packed(lp, limits) {
926            Ok(hash) => return Ok(Body::Hash(hash)),
927            Err(lp) => lp,
928        }
929    };
930    let mut hash = Hash::with_hint(lp.len() / step, limits);
931    let mut field_buf = [0u8; DIGITS_MAX];
932    let mut value_buf = [0u8; DIGITS_MAX];
933    // Walked and not indexed, for the reason `read_zset_listpack` gives: element
934    // `i` of a listpack costs a walk from the front.
935    let mut walk = lp.iter();
936    while let Some(entry) = walk.next() {
937        let field = text(entry, &mut field_buf).to_vec();
938        let value = text(walk.next().ok_or(Bad::Format)?, &mut value_buf).to_vec();
939        // The packed form holds the deadline as an absolute time, not as a
940        // distance from the header, which is the one place the two hash layouts
941        // disagree about the same number.
942        let at = if with_ttl {
943            match walk.next().ok_or(Bad::Format)? {
944                Entry::Int(0) => None,
945                Entry::Int(n) => Some(u64::try_from(n).map_err(|_| Bad::Format)?),
946                Entry::Str(_) => return Err(Bad::Format),
947            }
948        } else {
949            None
950        };
951        put_field(&mut hash, &field, &value, at, limits, now);
952    }
953    let _ = soonest;
954    if hash.is_empty() {
955        return Err(Bad::Format);
956    }
957    Ok(Body::Hash(hash))
958}
959
960/// A field's absolute deadline from the header and its stored distance.
961const fn deadline(soonest: u64, ttl: u64) -> Option<u64> {
962    if ttl == 0 {
963        None
964    } else {
965        Some(soonest + ttl - 1)
966    }
967}
968
969/// Put one field in, unless its deadline has already gone.
970fn put_field(
971    hash: &mut Hash,
972    field: &[u8],
973    value: &[u8],
974    at: Option<u64>,
975    limits: &hash::Limits,
976    now: u64,
977) {
978    if let Some(at) = at
979        && at <= now
980    {
981        return;
982    }
983    hash.set(field, value, limits);
984    if let Some(at) = at {
985        hash.expire(field, at, crate::ttl::Cond::Always, now);
986    }
987}
988
989/// A listpack entry as bytes, formatting an integer into the caller's buffer.
990fn text<'a>(entry: Entry<'a>, buf: &'a mut [u8; DIGITS_MAX]) -> &'a [u8] {
991    match entry {
992        Entry::Int(n) => num::i64_digits(buf, n),
993        Entry::Str(s) => s,
994    }
995}
996
997#[cfg(test)]
998mod tests {
999    use super::*;
1000    use crate::ttl::{Ask, Cond};
1001
1002    fn limits() -> (set::Limits, hash::Limits, list::Limits, zset::Limits) {
1003        (
1004            set::Limits::DEFAULT,
1005            hash::Limits::DEFAULT,
1006            list::Limits::default(),
1007            zset::Limits::DEFAULT,
1008        )
1009    }
1010
1011    fn round_trip(body: Body) -> Body {
1012        let (s, h, l, z) = limits();
1013        let all = Limits {
1014            set: &s,
1015            hash: &h,
1016            list: &l,
1017            zset: &z,
1018        };
1019        let rec = Record::new(body, None);
1020        let payload = dump(&rec).expect("this type has an RDB shape");
1021        load(&payload, all, 0).expect("what we wrote we can read")
1022    }
1023
1024    fn set_of(members: &[&[u8]]) -> Set {
1025        let l = set::Limits::DEFAULT;
1026        let mut set = Set::with_hint(members[0], members.len(), &l);
1027        for m in members {
1028            set.add(m, &l);
1029        }
1030        set
1031    }
1032
1033    #[test]
1034    fn a_payload_is_ten_bytes_longer_than_the_object() {
1035        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1036        let payload = dump(&rec).expect("a string has an RDB shape");
1037        // One type byte, one length byte, five of text, and the footer.
1038        assert_eq!(payload.len(), 1 + 1 + 5 + FOOTER);
1039        assert_eq!(payload[0], T_STRING);
1040    }
1041
1042    #[test]
1043    fn a_string_that_is_a_number_goes_out_as_one() {
1044        let rec = Record::new(Body::String(b"1234".to_vec()), None);
1045        let payload = dump(&rec).expect("a string has an RDB shape");
1046        // The type byte, the encoding byte, two bytes of integer, and the footer.
1047        assert_eq!(payload.len(), 1 + 1 + 2 + FOOTER);
1048        let Body::String(back) = round_trip(Body::String(b"1234".to_vec())) else {
1049            panic!("a string came back as something else");
1050        };
1051        assert_eq!(back, b"1234");
1052    }
1053
1054    /// A number with a leading zero is not the same string as the number, so it
1055    /// has to stay text or the round trip changes the value.
1056    #[test]
1057    fn a_string_that_only_looks_like_a_number_stays_text() {
1058        for s in [&b"007"[..], b"+7", b"-0", b" 7", b"9223372036854775808"] {
1059            let Body::String(back) = round_trip(Body::String(s.to_vec())) else {
1060                panic!("a string came back as something else");
1061            };
1062            assert_eq!(back, s, "{} did not survive", String::from_utf8_lossy(s));
1063        }
1064    }
1065
1066    #[test]
1067    fn every_integer_width_survives() {
1068        for n in [0i64, 1, -1, 127, -128, 128, -129, 32767, -32768, 32768] {
1069            let mut buf = [0u8; DIGITS_MAX];
1070            let s = num::i64_digits(&mut buf, n).to_vec();
1071            let Body::String(back) = round_trip(Body::String(s.clone())) else {
1072                panic!("a string came back as something else");
1073            };
1074            assert_eq!(back, s, "{n} did not survive");
1075        }
1076        // Past `i32` there is no encoding, so it goes as digits and still has to
1077        // come back the same.
1078        let big = b"2147483648".to_vec();
1079        let Body::String(back) = round_trip(Body::String(big.clone())) else {
1080            panic!("a string came back as something else");
1081        };
1082        assert_eq!(back, big);
1083    }
1084
1085    #[test]
1086    fn a_set_comes_back_with_the_same_members() {
1087        let set = set_of(&[b"alpha", b"beta", b"gamma"]);
1088        let Body::Set(back) = round_trip(Body::Set(set)) else {
1089            panic!("a set came back as something else");
1090        };
1091        assert_eq!(back.len(), 3);
1092        for m in [&b"alpha"[..], b"beta", b"gamma"] {
1093            assert!(
1094                back.contains(m),
1095                "{} went missing",
1096                String::from_utf8_lossy(m)
1097            );
1098        }
1099    }
1100
1101    /// An all integer set is held as an intset here and the round trip has to
1102    /// land it back in the same band, not in a listpack that happens to hold the
1103    /// same members.
1104    #[test]
1105    fn an_integer_set_comes_back_as_an_integer_set() {
1106        let set = set_of(&[b"1", b"2", b"3"]);
1107        let was = set.encoding();
1108        let Body::Set(back) = round_trip(Body::Set(set)) else {
1109            panic!("a set came back as something else");
1110        };
1111        assert_eq!(back.encoding(), was);
1112        assert_eq!(back.len(), 3);
1113        assert!(back.contains(b"2"));
1114    }
1115
1116    #[test]
1117    fn a_list_keeps_its_order() {
1118        let l = list::Limits::default();
1119        let mut list = List::new();
1120        for v in [&b"one"[..], b"two", b"three"] {
1121            list.push_back(v, &l);
1122        }
1123        let Body::List(back) = round_trip(Body::List(list)) else {
1124            panic!("a list came back as something else");
1125        };
1126        let mut seen = Vec::new();
1127        for e in back.iter() {
1128            let mut buf = Vec::new();
1129            e.write_to(&mut buf);
1130            seen.push(buf);
1131        }
1132        assert_eq!(
1133            seen,
1134            vec![b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
1135        );
1136    }
1137
1138    #[test]
1139    fn a_sorted_set_keeps_its_scores() {
1140        let l = zset::Limits::DEFAULT;
1141        let mut zset = Zset::new();
1142        zset.add(b"a", 1.5, &l);
1143        zset.add(b"b", -2.0, &l);
1144        zset.add(b"c", f64::INFINITY, &l);
1145        let Body::Zset(back) = round_trip(Body::Zset(zset)) else {
1146            panic!("a sorted set came back as something else");
1147        };
1148        assert_eq!(back.score(b"a"), Some(1.5));
1149        assert_eq!(back.score(b"b"), Some(-2.0));
1150        assert_eq!(back.score(b"c"), Some(f64::INFINITY));
1151    }
1152
1153    #[test]
1154    fn a_hash_with_no_deadlines_uses_the_plain_type() {
1155        let l = hash::Limits::DEFAULT;
1156        let mut hash = Hash::new();
1157        // Past the packed band, so this is the table and there is no blob to
1158        // copy. The small case is the listpack one and it is tested below.
1159        for i in 0..1000 {
1160            hash.set(format!("f{i}").as_bytes(), b"1", &l);
1161        }
1162        assert_eq!(hash.encoding(), hash::Encoding::Hashtable);
1163        let rec = Record::new(Body::Hash(hash.clone()), None);
1164        assert_eq!(dump(&rec).expect("a hash has an RDB shape")[0], T_HASH);
1165        let Body::Hash(back) = round_trip(Body::Hash(hash)) else {
1166            panic!("a hash came back as something else");
1167        };
1168        assert_eq!(back.len(), 1000);
1169        assert_eq!(back.get(b"f7").map(|v| v.byte_len()), Some(1));
1170    }
1171
1172    /// A value that is one packed blob goes out as the blob.
1173    ///
1174    /// The type byte is the thing being pinned here. Every one of these round
1175    /// trips already, through the walk, and the point of the check is that it is
1176    /// no longer going through the walk.
1177    #[test]
1178    fn a_packed_value_goes_out_as_its_blob() {
1179        let (sl, hl, _, zl) = limits();
1180        let mut hash = Hash::new();
1181        hash.set(b"one", b"1", &hl);
1182        hash.set(b"two", b"2", &hl);
1183        assert_eq!(hash.encoding(), hash::Encoding::Listpack);
1184
1185        let mut set = Set::new();
1186        set.add(b"alpha", &sl);
1187        set.add(b"beta", &sl);
1188        assert_eq!(set.encoding(), set::Encoding::Listpack);
1189
1190        let mut ints = Set::new();
1191        ints.add(b"1", &sl);
1192        ints.add(b"9", &sl);
1193        assert_eq!(ints.encoding(), set::Encoding::Intset);
1194
1195        let mut zset = Zset::new();
1196        zset.add(b"a", 1.5, &zl);
1197        assert_eq!(zset.encoding(), zset::Encoding::Listpack);
1198
1199        for (want, body) in [
1200            (T_HASH_LISTPACK, Body::Hash(hash)),
1201            (T_SET_LISTPACK, Body::Set(set)),
1202            (T_SET_INTSET, Body::Set(ints)),
1203            (T_ZSET_LISTPACK, Body::Zset(zset)),
1204        ] {
1205            let rec = Record::new(body.clone(), None);
1206            let payload = dump(&rec).expect("a packed value has an RDB shape");
1207            assert_eq!(payload[0], want, "wrong type byte for {body:?}");
1208            // And the walk is gone, not merely bypassed: what comes back has to
1209            // be the same value or the copy was of the wrong bytes.
1210            assert_eq!(
1211                format!("{:?}", round_trip(body.clone())),
1212                format!("{body:?}")
1213            );
1214        }
1215    }
1216
1217    /// A hash that has been widened for deadlines is walked even once they have
1218    /// all gone, because the blob it holds still has the third element per field
1219    /// and `HASH_LISTPACK` has no room for it.
1220    #[test]
1221    fn a_widened_hash_is_not_copied() {
1222        let l = hash::Limits::DEFAULT;
1223        let mut hash = Hash::new();
1224        hash.set(b"one", b"1", &l);
1225        hash.expire(b"one", 5_000, Cond::Always, 0);
1226        hash.persist(b"one");
1227        // The bound leans early and only a reap that walks puts it right, so
1228        // this is what it takes to get a hash that is on the wider band and has
1229        // nothing left to say about deadlines.
1230        hash.reap(6_000);
1231        assert_eq!(hash.encoding(), hash::Encoding::ListpackEx);
1232        assert_eq!(hash.soonest_deadline(), None);
1233        let rec = Record::new(Body::Hash(hash.clone()), None);
1234        assert_eq!(dump(&rec).expect("a hash has an RDB shape")[0], T_HASH);
1235        let Body::Hash(back) = round_trip(Body::Hash(hash)) else {
1236            panic!("a hash came back as something else");
1237        };
1238        assert_eq!(back.len(), 1);
1239        assert_eq!(back.deadline(b"one"), Ask::NoDeadline);
1240    }
1241
1242    #[test]
1243    fn a_hash_carries_its_field_deadlines_across() {
1244        let l = hash::Limits::DEFAULT;
1245        let mut hash = Hash::new();
1246        hash.set(b"keep", b"1", &l);
1247        hash.set(b"timed", b"2", &l);
1248        hash.expire(b"timed", 5_000, Cond::Always, 1_000);
1249        let rec = Record::new(Body::Hash(hash.clone()), None);
1250        assert_eq!(
1251            dump(&rec).expect("a hash has an RDB shape")[0],
1252            T_HASH_METADATA
1253        );
1254        let (s, h, li, z) = limits();
1255        let all = Limits {
1256            set: &s,
1257            hash: &h,
1258            list: &li,
1259            zset: &z,
1260        };
1261        let payload = dump(&rec).expect("a hash has an RDB shape");
1262        let Body::Hash(back) = load(&payload, all, 1_000).expect("it reads back") else {
1263            panic!("a hash came back as something else");
1264        };
1265        assert_eq!(back.len(), 2);
1266        assert_eq!(back.deadline(b"timed"), crate::ttl::Ask::At(5_000));
1267        assert_eq!(back.deadline(b"keep"), crate::ttl::Ask::NoDeadline);
1268    }
1269
1270    /// A field whose deadline went while the payload was in flight is not put
1271    /// back, because the next read would delete it anyway and a count that is
1272    /// wrong until somebody looks is worse than a field that never arrived.
1273    #[test]
1274    fn a_field_that_expired_in_transit_does_not_come_back() {
1275        let l = hash::Limits::DEFAULT;
1276        let mut hash = Hash::new();
1277        hash.set(b"keep", b"1", &l);
1278        hash.set(b"gone", b"2", &l);
1279        hash.expire(b"gone", 5_000, Cond::Always, 1_000);
1280        let rec = Record::new(Body::Hash(hash), None);
1281        let payload = dump(&rec).expect("a hash has an RDB shape");
1282        let (s, h, li, z) = limits();
1283        let all = Limits {
1284            set: &s,
1285            hash: &h,
1286            list: &li,
1287            zset: &z,
1288        };
1289        let Body::Hash(back) = load(&payload, all, 9_000).expect("it reads back") else {
1290            panic!("a hash came back as something else");
1291        };
1292        assert_eq!(back.len(), 1);
1293        assert!(back.contains(b"keep"));
1294        assert!(!back.contains(b"gone"));
1295    }
1296
1297    #[test]
1298    fn a_flipped_byte_is_caught() {
1299        let rec = Record::new(Body::String(b"hello there".to_vec()), None);
1300        let good = dump(&rec).expect("a string has an RDB shape");
1301        for i in 0..good.len() {
1302            let mut bad = good.clone();
1303            bad[i] ^= 1;
1304            let (s, h, l, z) = limits();
1305            let all = Limits {
1306                set: &s,
1307                hash: &h,
1308                list: &l,
1309                zset: &z,
1310            };
1311            assert!(
1312                load(&bad, all, 0).is_err(),
1313                "byte {i} could be changed without anything noticing"
1314            );
1315        }
1316    }
1317
1318    #[test]
1319    fn a_payload_from_a_newer_server_is_refused() {
1320        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1321        let mut payload = dump(&rec).expect("a string has an RDB shape");
1322        let n = payload.len();
1323        payload[n - 10] = 99;
1324        // The checksum has to be put right, or this would pass for the wrong
1325        // reason and the version check would never be reached.
1326        let crc = crc64(0, &payload[..n - 8]);
1327        payload[n - 8..].copy_from_slice(&crc.to_le_bytes());
1328        let (s, h, l, z) = limits();
1329        let all = Limits {
1330            set: &s,
1331            hash: &h,
1332            list: &l,
1333            zset: &z,
1334        };
1335        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Footer);
1336    }
1337
1338    /// Every version up to the one a current Redis stamps is read, and the one
1339    /// after it is not.
1340    ///
1341    /// This is the check that was missing when `RESTORE` was turning down every
1342    /// payload a real 8.10.1 produced. The old code compared against the version
1343    /// it writes, which is deliberately old so that old servers accept us, so
1344    /// making one number do both jobs meant refusing everything modern.
1345    #[test]
1346    fn a_payload_is_read_up_to_the_version_that_has_been_checked() {
1347        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1348        let (s, h, l, z) = limits();
1349        let all = Limits {
1350            set: &s,
1351            hash: &h,
1352            list: &l,
1353            zset: &z,
1354        };
1355        for stamp in [VERSION, READS_UP_TO, READS_UP_TO + 1] {
1356            let mut payload = dump(&rec).expect("a string has an RDB shape");
1357            let n = payload.len();
1358            payload[n - 10..n - 8].copy_from_slice(&stamp.to_le_bytes());
1359            let crc = crc64(0, &payload[..n - 8]);
1360            payload[n - 8..].copy_from_slice(&crc.to_le_bytes());
1361            let got = load(&payload, all, 0);
1362            if stamp > READS_UP_TO {
1363                assert_eq!(got.unwrap_err(), Bad::Footer, "{stamp} should be refused");
1364            } else {
1365                assert!(got.is_ok(), "{stamp} should be read");
1366            }
1367        }
1368        const {
1369            assert!(
1370                VERSION <= READS_UP_TO,
1371                "a server that cannot read what it writes is no use to anybody"
1372            )
1373        };
1374    }
1375
1376    #[test]
1377    fn a_payload_shorter_than_its_footer_is_refused() {
1378        for n in 0..FOOTER {
1379            assert_eq!(unseal(&vec![0u8; n]), Err(Bad::Footer));
1380        }
1381    }
1382
1383    /// Nothing here should be able to panic on bytes a client made up, so the
1384    /// whole space of short payloads gets tried with a correct footer on it.
1385    #[test]
1386    fn arbitrary_bytes_are_an_error_and_not_a_panic() {
1387        let (s, h, l, z) = limits();
1388        let all = Limits {
1389            set: &s,
1390            hash: &h,
1391            list: &l,
1392            zset: &z,
1393        };
1394        for kind in 0u8..=26 {
1395            for len in 0usize..6 {
1396                for fill in [0u8, 1, 0x40, 0x80, 0x81, 0xc0, 0xc3, 0xff] {
1397                    let mut body = vec![kind];
1398                    body.extend(std::iter::repeat_n(fill, len));
1399                    let payload = seal(body);
1400                    let _ = load(&payload, all, 0);
1401                }
1402            }
1403        }
1404    }
1405
1406    /// A count bigger than the payload is refused before anything is reserved.
1407    ///
1408    /// [`arbitrary_bytes_are_an_error_and_not_a_panic`] already sends these
1409    /// bytes and could not catch this, for two reasons worth writing down. An
1410    /// out of memory abort is not a panic, so a test that only says nothing
1411    /// panics will watch the process die and report nothing. And the dev machine
1412    /// overcommits, so the reservation succeeded there and only ever failed on
1413    /// Linux and Windows, which is to say in CI on the release tag and nowhere a
1414    /// person would see it.
1415    ///
1416    /// The bytes are the ones that did it. `0x80` opens a thirty two bit length
1417    /// and the four after it are the length, so the count comes out as
1418    /// `0x80808080`, and a row sixteen bytes wide makes that a request for
1419    /// thirty four gigabytes from a six byte payload.
1420    #[test]
1421    fn a_count_past_the_payload_is_refused_before_anything_is_reserved() {
1422        let (s, h, l, z) = limits();
1423        let all = Limits {
1424            set: &s,
1425            hash: &h,
1426            list: &l,
1427            zset: &z,
1428        };
1429        for kind in [T_SET, T_HASH, T_ZSET_2, T_ZSET, T_LIST_QUICKLIST_2] {
1430            let payload = seal(vec![kind, 0x80, 0x80, 0x80, 0x80, 0x80]);
1431            assert_eq!(
1432                load(&payload, all, 0).err(),
1433                Some(Bad::Format),
1434                "type {kind} took a count of 0x80808080 from six bytes"
1435            );
1436        }
1437
1438        // The bound is the bytes that are left and not a fixed ceiling, so a
1439        // count that is small in absolute terms is still refused when the
1440        // payload cannot possibly hold it. Ten members and nothing after the
1441        // count to hold them.
1442        let mut body = vec![T_SET];
1443        put_len(&mut body, 10);
1444        assert_eq!(load(&seal(body), all, 0).err(), Some(Bad::Format));
1445
1446        // And a count the payload can hold is read, so the bound is not simply
1447        // refusing everything.
1448        let mut body = vec![T_SET];
1449        put_len(&mut body, 2);
1450        put_str(&mut body, b"a");
1451        put_str(&mut body, b"b");
1452        assert!(load(&seal(body), all, 0).is_ok());
1453    }
1454
1455    #[test]
1456    fn lzf_unpacks_a_literal_run() {
1457        // One control byte saying four literals, then the four.
1458        assert_eq!(
1459            unpack(&[3, b'a', b'b', b'c', b'd'], 4).as_deref(),
1460            Some(&b"abcd"[..])
1461        );
1462    }
1463
1464    /// The case the byte at a time copy exists for: a back reference that reads
1465    /// bytes it is in the middle of writing.
1466    #[test]
1467    fn lzf_unpacks_an_overlapping_reference() {
1468        // One literal `a`, then a reference one byte back for five bytes. The
1469        // low five bits of the control byte and the byte after it are the
1470        // distance, and they are both zero because a distance is stored one
1471        // less than it is.
1472        let packed = [0u8, b'a', 3 << 5, 0];
1473        assert_eq!(unpack(&packed, 6).as_deref(), Some(&b"aaaaaa"[..]));
1474    }
1475
1476    #[test]
1477    fn lzf_refuses_a_reference_to_nothing() {
1478        assert_eq!(unpack(&[(3 << 5), 0], 5), None);
1479        assert_eq!(unpack(&[3, b'a'], 4), None);
1480    }
1481
1482    #[test]
1483    fn an_empty_collection_is_not_a_value() {
1484        let mut body = vec![T_SET];
1485        put_len(&mut body, 0);
1486        let payload = seal(body);
1487        let (s, h, l, z) = limits();
1488        let all = Limits {
1489            set: &s,
1490            hash: &h,
1491            list: &l,
1492            zset: &z,
1493        };
1494        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Format);
1495    }
1496
1497    #[test]
1498    fn trailing_bytes_are_refused() {
1499        let mut body = vec![T_STRING];
1500        put_str(&mut body, b"hello");
1501        body.push(0);
1502        let payload = seal(body);
1503        let (s, h, l, z) = limits();
1504        let all = Limits {
1505            set: &s,
1506            hash: &h,
1507            list: &l,
1508            zset: &z,
1509        };
1510        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Format);
1511    }
1512
1513    #[test]
1514    fn every_length_form_round_trips() {
1515        for n in [0u64, 63, 64, 16383, 16384, u64::from(u32::MAX), 1 << 40] {
1516            let mut out = Vec::new();
1517            put_len(&mut out, n);
1518            let mut r = Reader::new(&out);
1519            assert_eq!(r.len_or_encoding(), Ok((n, false)), "{n} did not survive");
1520            assert!(r.done(), "{n} left bytes behind");
1521        }
1522    }
1523}