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. [`crate::snapshot`] is the file, and
17//! it is these same bytes with a frame around them. The version is here so that a server
18//! reading a payload can refuse one from a newer server rather than misread it,
19//! and the checksum is here because `RESTORE` takes bytes from a client and a
20//! client is allowed to be wrong.
21//!
22//! # Two version numbers and not one
23//!
24//! The version we stamp on a payload and the version we will read are different
25//! numbers, because they are answers to opposite questions. What we write is a
26//! promise about how old a server can be and still understand us, so it is as
27//! low as it can be. What we read is a statement about how new a server can be
28//! before a type byte might not mean what it used to, so it is as high as has
29//! actually been checked. [`VERSION`] and [`READS_UP_TO`] say which is which.
30//!
31//! # It has to be Redis's bytes, not ours
32//!
33//! Nothing here is an internal format we get to choose. `MIGRATE` sends this to
34//! another server and `RESTORE` accepts it from any client, so a payload we
35//! produce has to load into a real Redis and a payload a real Redis produces has
36//! to load here. That is the only reason CRC64 exists in `yo-common`, and it is
37//! why the type bytes below are copied from `rdb.h` rather than numbered from
38//! zero in the order this file happens to handle them.
39//!
40//! # Writing the simple shape and reading every shape
41//!
42//! The two directions are deliberately not symmetric. Reading accepts every
43//! encoding a modern Redis emits, because we do not get to pick what arrives.
44//! Writing picks the plainest legal type for each kind, a count followed by the
45//! elements, because every one of those loads into Redis 8.2 and one shape per
46//! kind is one shape to get right.
47//!
48//! # Copying the blob when there is one
49//!
50//! That is the shape for values that are stored as a structure. A value that is
51//! already sitting in one packed blob does not go through it, because
52//! [`crate::listpack`] and [`crate::intset`] are byte compatible with Redis's
53//! own on purpose, so the payload for one of those is the blob with a length in
54//! front of it. A small set, a small hash and a small sorted set are one memcpy
55//! each instead of a walk that decodes every element and encodes it again, and
56//! they are the overwhelming majority of what `DUMP` and `MIGRATE` are pointed
57//! at.
58//!
59//! The rule for which type byte a value gets is the same word `OBJECT ENCODING`
60//! answers with and not the body underneath it, so a set that calls itself a
61//! hashtable is walked even in the corner where its members happen to still be
62//! in one intset run. There is one rule and one place to read it.
63//!
64//! A hash that has been widened for field deadlines is not copied. That band
65//! carries a third element per field and keeps it after the last deadline has
66//! been taken off, so the blob it holds is not the blob `HASH_LISTPACK` means
67//! and the walk is what makes it one.
68//!
69//! What that is worth, from `benches/rdb.rs` at a hundred elements, walked
70//! against copied:
71//!
72//! ```text
73//!   set of text      7.57 us    3.41 us    2.2x
74//!   set of integers  1.32 us    0.57 us    2.3x
75//!   hash            24.94 us    3.95 us    6.3x
76//!   sorted set      21.04 us    3.82 us    5.5x
77//! ```
78//!
79//! The same rows at a thousand elements, which is past every packed band and is
80//! therefore the walk in both runs, moved by under one percent, so nothing here
81//! was paid for by the values that do not benefit. What is left on the copied
82//! rows is a checksum over the payload and one allocation to put it in, and both
83//! of those are paid whichever way the payload was built, which is why the hash
84//! and the sorted set gain more than the two sets do: their walk was the more
85//! expensive one, not their copy the cheaper.
86//!
87//! The load side pays about five percent for this on a hash and a sorted set,
88//! because a listpack entry has to be decoded where a count prefixed element is
89//! read straight off a length. Copying the blob is not free on the way back in
90//! and the trade is still worth making, since a payload is written once and this
91//! is a five percent loss against a five hundred percent gain.
92//!
93//! The load side also stopped asking a listpack for element `i`. There is no
94//! offset table in a listpack, so `get(i)` walks from the front and a loop that
95//! asks for every element in turn costs the square of the count. A hundred field
96//! hash loaded in 81 us and loads in 60. That was there before any of this and
97//! the only thing that ever reached it was a payload from a real server, which
98//! is the case that matters most.
99//!
100//! # Taking the blob back
101//!
102//! A payload for a sorted set on the packed band goes the other way too. The
103//! blob that arrives is the layout that band uses, so it moves in whole rather
104//! than being added a member at a time, and the difference is not small: adding
105//! costs a scan to see whether the member is already there and a second scan to
106//! find where it belongs, both over everything added so far, so it is the square
107//! of the count twice over with a memmove on each one. A hundred member sorted
108//! set restored in 534 us and restores in 4.6 us.
109//!
110//! The blob is checked before it is taken. This band answers a rank query by
111//! position and by nothing else, so a payload that says it is a sorted set while
112//! not being sorted would answer `ZRANGE` with the wrong members and never say
113//! why, and a payload with the same member twice would report a length nothing
114//! else agrees with. One pass rules out both, since strictly increasing means no
115//! two members compare equal on the score and then equal on the bytes. A blob
116//! that fails the check, or that is past this server's limits, is handed back
117//! and walked, which is what the reader did with every payload before this.
118//!
119//! A sorted set past the band is sized from the count now, the way a set and a
120//! hash already were. It used to start packed whatever the count said, fill to
121//! the band limit at a scan a member, and throw the listpack away. A thousand
122//! member sorted set restored in 1.23 ms and restores in 88 us.
123//!
124//! The hash gets the same treatment, and the only hard part was the bit the
125//! sorted set got for free. A sorted set blob is ordered, so one pass proving it
126//! is strictly increasing also proves no member is in it twice. A hash blob is
127//! in insertion order, and a repeated field would give a hash whose `HLEN`
128//! counts both rows and whose `HGET` and `HDEL` only ever reach the first, so
129//! the length would disagree with `HGETALL` and a delete would leave the field
130//! behind. `Hash::from_packed` rules that out by hashing each field into a
131//! stack array and sorting it, which is one pass and a sort rather than the
132//! square of the count, and a collision costs a fallback to the walk and not a
133//! wrong answer. A hundred field hash restored in 62.8 us and restores in 5.8.
134//!
135//! Only the two element form. The band with a deadline after every value is not
136//! handed over, for the same reason `Hash::packed_bytes` will not copy it on
137//! the way out: that column has its own type byte and its own header, and a hash
138//! that has been widened once keeps the third element per field forever after.
139//!
140//! # Compression
141//!
142//! Redis compresses strings over twenty bytes with LZF when `rdbcompression` is
143//! on, which it is by default. Nothing here compresses on the way out, because
144//! an uncompressed string is legal and every reader accepts it. Decompression on
145//! the way in is not optional, because payloads arriving from a real Redis are
146//! full of LZF strings.
147
148use std::borrow::Cow;
149
150use yo_common::crc::crc64;
151use yo_common::num::{self, DIGITS_MAX};
152
153use crate::hash::{self, Hash};
154use crate::intset::Intset;
155use crate::keys::{Body, Record};
156use crate::list::{self, List};
157use crate::listpack::{Entry, Listpack};
158use crate::set::{self, Set};
159use crate::stream::{Group, Id, Stream};
160use crate::zset::{self, Zset};
161
162/// The RDB version this server writes into the footer.
163///
164/// Redis refuses a payload whose version is above its own, so this being right
165/// is the difference between a payload another server will look at and one it
166/// throws away without reading. Lower is friendlier, and twelve is as low as
167/// this can go: it is the version that introduced the hash with field deadlines,
168/// which is a shape this server writes.
169pub const VERSION: u16 = 12;
170
171/// The highest version in a footer this server will still read.
172///
173/// A different number from [`VERSION`], and the two mean opposite things. What
174/// we write is a promise about how old a server can be and still understand us.
175/// What we read is a statement about how new a server can be before we stop
176/// trusting that a type byte still means what it used to.
177///
178/// Fifteen because that is what a Redis 8.10.1 stamps on a payload, read off one
179/// over a socket rather than out of a header file. Refusing it is not a small
180/// bug: it means `RESTORE` turns down every payload a current server produces,
181/// with a message about the checksum that sends the reader to entirely the wrong
182/// place. That is what this constant existing separately is here to stop.
183///
184/// It goes up when a newer server has been checked and not before. The guard is
185/// worth keeping rather than removing, because the day Redis reuses a type byte
186/// for a different layout, refusing to read it is the only safe answer and a
187/// wrong value is worse than no value.
188pub const READS_UP_TO: u16 = 15;
189
190/// The footer: two bytes of version and eight of checksum.
191pub(crate) const FOOTER: usize = 10;
192
193// The object type byte. These are `rdb.h`, and the gaps are types this server
194// cannot hold, so they are not named.
195const T_STRING: u8 = 0;
196const T_LIST: u8 = 1;
197const T_SET: u8 = 2;
198const T_ZSET: u8 = 3;
199const T_HASH: u8 = 4;
200const T_ZSET_2: u8 = 5;
201const T_SET_INTSET: u8 = 11;
202const T_STREAM_LISTPACKS: u8 = 15;
203const T_HASH_LISTPACK: u8 = 16;
204const T_ZSET_LISTPACK: u8 = 17;
205const T_LIST_QUICKLIST_2: u8 = 18;
206const T_STREAM_LISTPACKS_2: u8 = 19;
207const T_SET_LISTPACK: u8 = 20;
208const T_STREAM_LISTPACKS_3: u8 = 21;
209const T_HASH_METADATA: u8 = 24;
210const T_HASH_LISTPACK_EX: u8 = 25;
211const T_STREAM_LISTPACKS_4: u8 = 27;
212
213// The length encoding, `00` and `01` in the top two bits for six and fourteen
214// bit lengths, then two whole byte forms, and `11` for the special encodings.
215const LEN_6BIT: u8 = 0;
216const LEN_14BIT: u8 = 1;
217const LEN_32BIT: u8 = 0x80;
218const LEN_64BIT: u8 = 0x81;
219const LEN_ENCODED: u8 = 3;
220
221// What a `11` length means: three integer widths and a compressed blob.
222const ENC_INT8: u64 = 0;
223const ENC_INT16: u64 = 1;
224const ENC_INT32: u64 = 2;
225const ENC_LZF: u64 = 3;
226
227/// A quicklist node holding a listpack rather than one long value.
228const NODE_PACKED: u64 = 2;
229/// A quicklist node that is one value too big for a listpack.
230const NODE_PLAIN: u64 = 1;
231
232/// Why a payload was not accepted.
233///
234/// Two variants because `RESTORE` has two complaints and a client can tell them
235/// apart. A bad footer means the bytes were damaged or came from a newer server,
236/// and everything else means they were intact and still did not make sense.
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum Bad {
239    /// The version is from the future or the checksum does not match.
240    Footer,
241    /// The bytes are self consistent and are not a value this server can hold.
242    Format,
243}
244
245/// Where a load is allowed to put what it builds.
246///
247/// The four representation thresholds, borrowed rather than copied, because a
248/// restore has to land in the same band the same data would have landed in had
249/// it been written a command at a time. A hash restored into a listpack on a
250/// server configured for tables would answer the wrong thing to `OBJECT
251/// ENCODING` and would be a different size for the rest of its life.
252#[derive(Debug, Clone, Copy)]
253pub struct Limits<'a> {
254    /// `set-max-intset-entries` and the two listpack thresholds.
255    pub set: &'a set::Limits,
256    /// `hash-max-listpack-entries` and `hash-max-listpack-value`.
257    pub hash: &'a hash::Limits,
258    /// `list-max-listpack-size`, as bytes or as a count.
259    pub list: &'a list::Limits,
260    /// `zset-max-listpack-entries` and `zset-max-listpack-value`.
261    pub zset: &'a zset::Limits,
262}
263
264// ---------------------------------------------------------------------------
265// The wrapper: version and checksum.
266// ---------------------------------------------------------------------------
267
268/// Put the version and the checksum on the end of a serialised object.
269fn seal(mut body: Vec<u8>) -> Vec<u8> {
270    body.extend_from_slice(&VERSION.to_le_bytes());
271    let crc = crc64(0, &body);
272    body.extend_from_slice(&crc.to_le_bytes());
273    body
274}
275
276/// Check the footer and hand back everything in front of it.
277///
278/// The version check comes before the checksum, which is the order Redis uses
279/// and the order that gives the better answer: a payload from a newer server is
280/// usually intact, and telling somebody their bytes are corrupt when they are
281/// merely from next year sends them looking in the wrong place.
282fn unseal(payload: &[u8]) -> Result<&[u8], Bad> {
283    if payload.len() < FOOTER {
284        return Err(Bad::Footer);
285    }
286    let split = payload.len() - FOOTER;
287    let (body, foot) = payload.split_at(split);
288    let version = u16::from_le_bytes([foot[0], foot[1]]);
289    if version > READS_UP_TO {
290        return Err(Bad::Footer);
291    }
292    let stored = u64::from_le_bytes(foot[2..].try_into().expect("ten byte footer, eight left"));
293    // A checksum of zero means there is no checksum. Redis writes one when it
294    // has been told not to spend the time on it, and it reads one the same way,
295    // so a payload from a server with checksumming turned off has to be accepted
296    // here or `MIGRATE` from such a server never lands. The cost is that a
297    // payload damaged into exactly eight zero bytes gets read anyway, which is
298    // the same cost Redis pays and a smaller one than refusing a whole class of
299    // real payloads.
300    if stored != 0 && stored != crc64(0, &payload[..payload.len() - 8]) {
301        return Err(Bad::Footer);
302    }
303    Ok(body)
304}
305
306// ---------------------------------------------------------------------------
307// Writing.
308// ---------------------------------------------------------------------------
309
310/// Serialise a record's value, footer and all.
311///
312/// The deadline does not go in. `DUMP` deliberately drops it and `RESTORE` takes
313/// a fresh one as an argument, because a payload that travels for a while would
314/// otherwise arrive already expired or, worse, silently alive for longer than
315/// anybody meant.
316///
317/// `None` for a value with no RDB shape at all, which today is only the sparse
318/// array. No command on the wire can create one yet, so no client can reach this,
319/// and it is a `None` rather than a panic so that the day the document commands
320/// land the answer is a missing key and not a dead server.
321pub(crate) fn dump(rec: &Record) -> Option<Vec<u8>> {
322    let mut out = Vec::new();
323    if !object(rec, None, &mut out) {
324        return None;
325    }
326    Some(seal(out))
327}
328
329/// Serialise a record's value with no footer, and with a key if there is one.
330///
331/// The two callers want the same bytes in two frames. `DUMP` wants the value on
332/// its own and passes `None`, and [`crate::snapshot`] wants it as one entry in a
333/// file, where the key name sits between the type byte and the value. That is
334/// the whole reason the key is threaded down here rather than written by the
335/// caller: the type byte comes first and the key comes second, so there is no
336/// point either of them could splice a name in without copying the value again.
337///
338/// `false` for a value with no RDB shape at all, and it is answered before
339/// anything has been written, so a caller building a file does not have to undo
340/// a half written entry.
341pub(crate) fn object(rec: &Record, key: Option<&[u8]>, out: &mut Vec<u8>) -> bool {
342    match rec.body() {
343        // The same answer a sparse array gets, and for a stronger reason: a
344        // foreign body is an engine that lives above this crate and there is no
345        // byte shape for it here to write even in principle. `DUMP` on a graph
346        // is refused by the dispatch before it reaches this, so nothing sees
347        // the null bulk this would otherwise produce.
348        //
349        // The sparse array is ours and has no Redis number to write under, so
350        // there is nothing for it to go out as either.
351        Body::Foreign(_) | Body::Array(_) => return false,
352        Body::String(bytes) => {
353            put_head(out, T_STRING, key);
354            put_str(out, bytes);
355        }
356        Body::List(list) => {
357            put_head(out, T_LIST, key);
358            put_len(out, list.len() as u64);
359            for element in list.iter() {
360                put_entry(out, element);
361            }
362        }
363        Body::Set(set) => match (set.encoding(), set.packed_bytes()) {
364            (set::Encoding::Intset, Some(blob)) => {
365                put_head(out, T_SET_INTSET, key);
366                put_str(out, blob);
367            }
368            (set::Encoding::Listpack, Some(blob)) => {
369                put_head(out, T_SET_LISTPACK, key);
370                put_str(out, blob);
371            }
372            _ => {
373                put_head(out, T_SET, key);
374                put_len(out, set.len() as u64);
375                for member in set.iter() {
376                    put_entry(out, member);
377                }
378            }
379        },
380        Body::Zset(zset) => match zset.packed_bytes() {
381            Some(blob) => {
382                put_head(out, T_ZSET_LISTPACK, key);
383                put_str(out, blob);
384            }
385            None => {
386                put_head(out, T_ZSET_2, key);
387                put_len(out, zset.len() as u64);
388                zset.walk(0, zset.len(), false, |member, score| {
389                    put_entry(out, member);
390                    out.extend_from_slice(&score.to_le_bytes());
391                });
392            }
393        },
394        Body::Hash(hash) => put_hash(out, hash, key),
395        Body::Stream(stream) => put_stream(out, stream, key),
396    }
397    true
398}
399
400/// The type byte, and the key name behind it when this is going into a file.
401fn put_head(out: &mut Vec<u8>, ty: u8, key: Option<&[u8]>) {
402    out.push(ty);
403    if let Some(key) = key {
404        put_str(out, key);
405    }
406}
407
408/// A hash, in the plain shape or the one that carries field deadlines.
409///
410/// Two types because the deadline costs a length prefixed number on every single
411/// field, and the overwhelming majority of hashes have no deadline anywhere.
412/// Redis makes the same split for the same reason, and the trick it uses is
413/// worth copying: the earliest deadline in the hash goes in the header, and each
414/// field stores the difference from it plus one, so a field with no deadline is
415/// a zero and everything else is a small number rather than a full timestamp.
416fn put_hash(out: &mut Vec<u8>, hash: &Hash, key: Option<&[u8]>) {
417    let Some(soonest) = hash.soonest_deadline() else {
418        if let Some(blob) = hash.packed_bytes() {
419            put_head(out, T_HASH_LISTPACK, key);
420            put_str(out, blob);
421            return;
422        }
423        put_head(out, T_HASH, key);
424        put_len(out, hash.len() as u64);
425        for (field, value) in hash.iter() {
426            put_entry(out, field);
427            put_entry(out, value);
428        }
429        return;
430    };
431    put_head(out, T_HASH_METADATA, key);
432    out.extend_from_slice(&soonest.to_le_bytes());
433    put_len(out, hash.len() as u64);
434    for i in 0..hash.len() {
435        let (field, value) = hash.at(i).expect("index is under the length");
436        // Saturating rather than subtracting, because `soonest_deadline` is
437        // documented as a lower bound and a bound that is early by a millisecond
438        // would underflow into a deadline a few hundred million years out.
439        let ttl = match hash.deadline_at(i) {
440            Some(at) => at.saturating_sub(soonest) + 1,
441            None => 0,
442        };
443        put_len(out, ttl);
444        put_entry(out, field);
445        put_entry(out, value);
446    }
447}
448
449/// A stream: the node blobs, then everything the nodes do not say.
450///
451/// This is where the node layout pays for itself. A node is already the rax key
452/// and the listpack a real server writes, so the whole of the entry data goes
453/// out as a length and a memcpy each and nothing is decoded on the way. What
454/// follows is the counters, and then the consumer groups, which are the part
455/// that is genuinely a structure rather than bytes.
456///
457/// `LISTPACKS_3` and not the newest type. A Redis 8.10 writes 27, which is this
458/// with a per group field and an idempotency block on the end, and both are
459/// empty for every stream this server can hold because it does not track
460/// producer IDs. Writing the older type is the same data in a shape more servers
461/// accept, which is the rule the rest of this file already follows.
462fn put_stream(out: &mut Vec<u8>, s: &Stream, key: Option<&[u8]>) {
463    put_head(out, T_STREAM_LISTPACKS_3, key);
464    put_len(out, s.nodes() as u64);
465    for (master, blob) in s.raw_nodes() {
466        // The sixteen big endian bytes, which is the rax key Redis writes here
467        // and is the only place in a payload where an ID is not a length.
468        put_str(out, &master.to_bytes());
469        put_str(out, blob);
470    }
471    put_len(out, s.len());
472    put_id(out, s.last_id());
473    // A stream with nothing left in it has no first entry, and Redis writes 0-0
474    // for that rather than leaving the field out.
475    put_id(out, s.first_id().unwrap_or(Id::MIN));
476    put_id(out, s.max_deleted_id());
477    put_len(out, s.added());
478
479    put_len(out, s.groups().count() as u64);
480    for (name, group) in s.groups() {
481        put_str(out, name);
482        put_id(out, group.last_id());
483        // Minus one for a count that cannot be worked out, which is what Redis
484        // writes for the same thing and reads back as unknown. Checked on
485        // 8.10.1: a group over a stream something has been deleted from dumps
486        // with eight bytes of ones here and reports a null `entries-read`.
487        put_len(out, group.entries_read().unwrap_or(u64::MAX));
488
489        // The whole ledger first and the consumers after it, so a reader meets
490        // an entry before it meets whoever is holding it. That is not an
491        // accident of the format: it is what lets an entry nobody holds be
492        // written at all.
493        put_len(out, group.pending_len() as u64);
494        for (id, nack) in group.pending_all() {
495            out.extend_from_slice(&id.to_bytes());
496            put_millis(out, nack.time() as i64);
497            put_len(out, nack.count());
498        }
499
500        put_len(out, group.consumers().count() as u64);
501        for c in group.consumers() {
502            put_str(out, c.name());
503            put_millis(out, c.seen() as i64);
504            // Minus one for a consumer that has never had anything, which is
505            // what `XINFO CONSUMERS` on a real server reports for one made by
506            // `XGROUP CREATECONSUMER` and never read from.
507            put_millis(out, c.active().map_or(-1, |at| at as i64));
508            put_len(out, c.len() as u64);
509            for id in c.pending() {
510                out.extend_from_slice(&id.to_bytes());
511            }
512        }
513    }
514}
515
516/// An entry ID, as the two lengths a stream writes everywhere but a rax key.
517fn put_id(out: &mut Vec<u8>, id: Id) {
518    put_len(out, id.ms);
519    put_len(out, id.seq);
520}
521
522/// A time, which a stream writes as eight signed little endian bytes.
523///
524/// Signed because minus one is a value the format uses, for a consumer that has
525/// never read anything.
526fn put_millis(out: &mut Vec<u8>, at: i64) {
527    out.extend_from_slice(&at.to_le_bytes());
528}
529
530/// A length, in the smallest of the four forms that holds it.
531pub(crate) fn put_len(out: &mut Vec<u8>, n: u64) {
532    if n < 1 << 6 {
533        out.push((LEN_6BIT << 6) | n as u8);
534    } else if n < 1 << 14 {
535        out.push((LEN_14BIT << 6) | (n >> 8) as u8);
536        out.push(n as u8);
537    } else if n <= u64::from(u32::MAX) {
538        out.push(LEN_32BIT);
539        out.extend_from_slice(&(n as u32).to_be_bytes());
540    } else {
541        out.push(LEN_64BIT);
542        out.extend_from_slice(&n.to_be_bytes());
543    }
544}
545
546/// A string, integer encoded when that is both possible and shorter.
547pub(crate) fn put_str(out: &mut Vec<u8>, s: &[u8]) {
548    // Redis only tries the integer encoding on strings short enough to be one,
549    // which saves parsing every long value that starts with a digit.
550    if s.len() <= 11
551        && let Some(n) = num::parse_i64(s)
552        && let mut buf = [0u8; DIGITS_MAX]
553        && num::i64_digits(&mut buf, n) == s
554        && put_int(out, n)
555    {
556        return;
557    }
558    put_len(out, s.len() as u64);
559    out.extend_from_slice(s);
560}
561
562/// An element straight out of a collection.
563///
564/// A listpack already knows whether it is holding an integer, so an integer
565/// element goes out in the integer encoding without ever being formatted into
566/// digits and parsed back. That is the same saving the reply path makes and it
567/// is why elements come back as an [`Entry`] rather than as bytes.
568fn put_entry(out: &mut Vec<u8>, entry: Entry<'_>) {
569    match entry {
570        Entry::Int(n) => {
571            if !put_int(out, n) {
572                let mut buf = [0u8; DIGITS_MAX];
573                let digits = num::i64_digits(&mut buf, n);
574                put_len(out, digits.len() as u64);
575                out.extend_from_slice(digits);
576            }
577        }
578        Entry::Str(s) => put_str(out, s),
579    }
580}
581
582/// An integer in one of the three widths, or `false` if it does not fit any.
583///
584/// There is no 64 bit form. A number past `i32` goes out as digits, which is
585/// what Redis does, and it is not the oversight it looks like: the encoding is
586/// there to make short strings shorter and a nineteen digit number in eight
587/// bytes saves eleven bytes on a value that is already rare.
588fn put_int(out: &mut Vec<u8>, n: i64) -> bool {
589    if let Ok(v) = i8::try_from(n) {
590        out.push((LEN_ENCODED << 6) | ENC_INT8 as u8);
591        out.push(v as u8);
592    } else if let Ok(v) = i16::try_from(n) {
593        out.push((LEN_ENCODED << 6) | ENC_INT16 as u8);
594        out.extend_from_slice(&v.to_le_bytes());
595    } else if let Ok(v) = i32::try_from(n) {
596        out.push((LEN_ENCODED << 6) | ENC_INT32 as u8);
597        out.extend_from_slice(&v.to_le_bytes());
598    } else {
599        return false;
600    }
601    true
602}
603
604// ---------------------------------------------------------------------------
605// Reading.
606// ---------------------------------------------------------------------------
607
608/// A position in a payload, and the only thing allowed to advance it.
609///
610/// Every read goes through here so that a truncated payload is one error at one
611/// place rather than a bounds check per field that somebody eventually forgets.
612struct Reader<'a> {
613    buf: &'a [u8],
614    at: usize,
615}
616
617impl<'a> Reader<'a> {
618    const fn new(buf: &'a [u8]) -> Reader<'a> {
619        Reader { buf, at: 0 }
620    }
621
622    fn byte(&mut self) -> Result<u8, Bad> {
623        let b = *self.buf.get(self.at).ok_or(Bad::Format)?;
624        self.at += 1;
625        Ok(b)
626    }
627
628    fn take(&mut self, n: usize) -> Result<&'a [u8], Bad> {
629        let end = self.at.checked_add(n).ok_or(Bad::Format)?;
630        let s = self.buf.get(self.at..end).ok_or(Bad::Format)?;
631        self.at = end;
632        Ok(s)
633    }
634
635    /// How many elements follow, refusing a count the payload cannot hold.
636    ///
637    /// The count in a payload is four bytes wide and the payload is whatever
638    /// length it happens to be, so nothing in the format stops one from claiming
639    /// two billion members. Every reader that takes a count then hands it to a
640    /// `with_hint`, which is the whole point of a hint, and a hint of two billion
641    /// asks the allocator for thirty four gigabytes before a single element has
642    /// been read. That is not a hypothetical: it is what a `RESTORE` of a
643    /// truncated payload did, and on Linux the allocator refused and the process
644    /// went down, which turns a bad payload from one client into an outage for
645    /// everybody.
646    ///
647    /// The bound is the bytes that are left. An element takes at least one byte
648    /// however it is encoded, so a count past what remains cannot be honest, and
649    /// checking it here means every reader gets the check rather than the ones
650    /// somebody remembered. It is deliberately loose: it is not trying to work
651    /// out the real minimum for each type, only to keep an allocation in the same
652    /// order of magnitude as the bytes that arrived.
653    ///
654    /// Zero is refused with it, for the reason [`non_empty`] gives.
655    fn count(&mut self) -> Result<usize, Bad> {
656        non_empty(self.bounded()?)
657    }
658
659    /// The same bound without the empty rule.
660    ///
661    /// A stream has four counts that are allowed to be zero and every one of
662    /// them is an ordinary state rather than a payload nobody could have
663    /// produced. It holds no nodes at all once everything in it has been
664    /// deleted, and a real server dumps that and restores it as a live stream of
665    /// length zero. It can have no consumer groups, a group can have nothing
666    /// pending, and a consumer can be holding nothing.
667    fn bounded(&mut self) -> Result<usize, Bad> {
668        let n = self.len()?;
669        if n > self.buf.len() - self.at {
670            return Err(Bad::Format);
671        }
672        Ok(n)
673    }
674
675    /// A length, refusing the `11` forms that are not lengths at all.
676    fn len(&mut self) -> Result<usize, Bad> {
677        usize::try_from(self.num()?).map_err(|_| Bad::Format)
678    }
679
680    /// A number written in the length encoding, which is how a stream writes
681    /// every counter it has and both halves of almost every ID.
682    ///
683    /// Separate from [`Reader::len`] because a stream's numbers are not lengths
684    /// and are routinely past what a `usize` has to hold: a millisecond
685    /// timestamp is one, and an unknown `entries-read` is written as the whole
686    /// sixty four bits set.
687    fn num(&mut self) -> Result<u64, Bad> {
688        match self.len_or_encoding()? {
689            (n, false) => Ok(n),
690            (_, true) => Err(Bad::Format),
691        }
692    }
693
694    /// An entry ID, as the two lengths a stream writes almost everywhere.
695    fn id(&mut self) -> Result<Id, Bad> {
696        Ok(Id::new(self.num()?, self.num()?))
697    }
698
699    /// An entry ID as the sixteen big endian bytes a pending list writes.
700    fn raw_id(&mut self) -> Result<Id, Bad> {
701        let b = self.take(16)?;
702        Ok(Id::from_bytes(b.try_into().expect("sixteen bytes")))
703    }
704
705    /// A time, which a stream writes as eight signed little endian bytes.
706    fn millis(&mut self) -> Result<i64, Bad> {
707        let b = self.take(8)?;
708        Ok(i64::from_le_bytes(b.try_into().expect("eight bytes")))
709    }
710
711    /// A length, and whether it was one of the special encodings instead.
712    fn len_or_encoding(&mut self) -> Result<(u64, bool), Bad> {
713        let first = self.byte()?;
714        match first >> 6 {
715            LEN_6BIT => Ok((u64::from(first & 0x3f), false)),
716            LEN_14BIT => {
717                let second = self.byte()?;
718                Ok(((u64::from(first & 0x3f) << 8) | u64::from(second), false))
719            }
720            LEN_ENCODED => Ok((u64::from(first & 0x3f), true)),
721            // The remaining two bit pattern is `10`, where the whole first byte
722            // says which width follows rather than carrying any of the length.
723            _ => match first {
724                LEN_32BIT => {
725                    let b = self.take(4)?;
726                    Ok((
727                        u64::from(u32::from_be_bytes(b.try_into().expect("four bytes"))),
728                        false,
729                    ))
730                }
731                LEN_64BIT => {
732                    let b = self.take(8)?;
733                    Ok((
734                        u64::from_be_bytes(b.try_into().expect("eight bytes")),
735                        false,
736                    ))
737                }
738                _ => Err(Bad::Format),
739            },
740        }
741    }
742
743    /// A string, whichever of the five ways it was written.
744    ///
745    /// Borrowed when the bytes are already there and owned when they had to be
746    /// built, which is the integer encodings and LZF. Most strings in a payload
747    /// are plain, so most of them cost nothing here.
748    fn str(&mut self) -> Result<Cow<'a, [u8]>, Bad> {
749        let (n, encoded) = self.len_or_encoding()?;
750        if !encoded {
751            let n = usize::try_from(n).map_err(|_| Bad::Format)?;
752            return Ok(Cow::Borrowed(self.take(n)?));
753        }
754        let value = match n {
755            ENC_INT8 => i64::from(self.byte()? as i8),
756            ENC_INT16 => {
757                let b = self.take(2)?;
758                i64::from(i16::from_le_bytes(b.try_into().expect("two bytes")))
759            }
760            ENC_INT32 => {
761                let b = self.take(4)?;
762                i64::from(i32::from_le_bytes(b.try_into().expect("four bytes")))
763            }
764            ENC_LZF => {
765                let packed = self.len()?;
766                let plain = self.len()?;
767                let bytes = self.take(packed)?;
768                return unpack(bytes, plain).map(Cow::Owned).ok_or(Bad::Format);
769            }
770            _ => return Err(Bad::Format),
771        };
772        let mut buf = [0u8; DIGITS_MAX];
773        Ok(Cow::Owned(num::i64_digits(&mut buf, value).to_vec()))
774    }
775
776    /// A score in the binary form, which is `ZSET_2` and everything since.
777    fn double(&mut self) -> Result<f64, Bad> {
778        let b = self.take(8)?;
779        Ok(f64::from_le_bytes(b.try_into().expect("eight bytes")))
780    }
781
782    /// A score in the old text form, which only `ZSET` uses.
783    ///
784    /// A length byte and then that many digits, with three of the lengths
785    /// reserved to mean the three values that have no digits.
786    fn double_text(&mut self) -> Result<f64, Bad> {
787        match self.byte()? {
788            255 => Ok(f64::NEG_INFINITY),
789            254 => Ok(f64::INFINITY),
790            253 => Ok(f64::NAN),
791            n => {
792                let digits = self.take(n as usize)?;
793                num::parse_f64(digits).ok_or(Bad::Format)
794            }
795        }
796    }
797
798    /// Whether every byte has been read, which a well formed payload has.
799    const fn done(&self) -> bool {
800        self.at == self.buf.len()
801    }
802}
803
804/// LZF, the one compression Redis puts in an RDB payload.
805///
806/// A control byte either introduces a run of literals or points backwards into
807/// what has already been written. The back reference is allowed to overlap what
808/// it is producing, which is how a long run of one byte compresses, so the copy
809/// has to go one byte at a time rather than through a slice copy.
810///
811/// `plain` is the length the payload claims the result will be, and it is used
812/// as the bound rather than trusted, so a payload claiming four bytes and
813/// describing four gigabytes stops at four.
814fn unpack(packed: &[u8], plain: usize) -> Option<Vec<u8>> {
815    let mut out = Vec::with_capacity(plain.min(1 << 20));
816    let mut i = 0;
817    while i < packed.len() {
818        let ctrl = usize::from(packed[i]);
819        i += 1;
820        if ctrl < 32 {
821            let run = ctrl + 1;
822            let end = i.checked_add(run)?;
823            if end > packed.len() || out.len() + run > plain {
824                return None;
825            }
826            out.extend_from_slice(&packed[i..end]);
827            i = end;
828        } else {
829            let mut run = ctrl >> 5;
830            if run == 7 {
831                run += usize::from(*packed.get(i)?);
832                i += 1;
833            }
834            let back = ((ctrl & 0x1f) << 8) + usize::from(*packed.get(i)?) + 1;
835            i += 1;
836            let run = run + 2;
837            if back > out.len() || out.len() + run > plain {
838                return None;
839            }
840            let from = out.len() - back;
841            for at in from..from + run {
842                out.push(out[at]);
843            }
844        }
845    }
846    (out.len() == plain).then_some(out)
847}
848
849/// Turn a payload back into a value.
850///
851/// `now` is here for one reason: a hash can carry deadlines and a field whose
852/// deadline has already gone is not put back. Restoring it would leave a field
853/// that the very next read would delete, and a count that is wrong until
854/// somebody looks.
855pub(crate) fn load(payload: &[u8], limits: Limits<'_>, now: u64) -> Result<Body, Bad> {
856    let body = unseal(payload)?;
857    let mut r = Reader::new(body);
858    let kind = r.byte()?;
859    let value = read_object(&mut r, kind, limits, now)?;
860    // Trailing bytes mean the payload was not what it said it was, even though
861    // everything read so far parsed. Redis is stricter than it looks here and so
862    // is this, because a payload with something extra on the end is either a
863    // different version's idea of the same type or somebody probing.
864    if !r.done() {
865        return Err(Bad::Format);
866    }
867    Ok(value)
868}
869
870// ---------------------------------------------------------------------------
871// The function payload.
872// ---------------------------------------------------------------------------
873
874/// The opcode in front of each library in a function payload.
875const OP_FUNCTION2: u8 = 245;
876
877/// The opcode the two 7.0 release candidates wrote and nothing since has read.
878///
879/// Named rather than left to fall through with everything else because a client
880/// holding one of these deserves to be told what it is holding. The format
881/// changed between rc2 and the release and no server has ever converted it, so
882/// the only honest answer is that it is not supported.
883const OP_FUNCTION_PRE_GA: u8 = 246;
884
885/// Why a function payload was not accepted.
886///
887/// Four variants and not two, because `FUNCTION RESTORE` has four complaints and
888/// a client can tell them apart. Everything past the framing is the engine's
889/// problem and comes back from the caller instead.
890#[derive(Debug, Clone, Copy, PartialEq, Eq)]
891pub enum BadLibs {
892    /// The version is from the future or the checksum does not match.
893    Footer,
894    /// A library was written in the format the 7.0 release candidates used.
895    PreGa,
896    /// An opcode that does not introduce a library at all.
897    NotFunction,
898    /// A library's code ran off the end of the payload.
899    Truncated,
900}
901
902/// The bytes `FUNCTION DUMP` hands out.
903///
904/// One opcode and one string per library, with the same footer every other
905/// payload gets. The string is the whole library code including its shebang,
906/// which is what makes a restore able to work out the name and the engine
907/// without any of that being written down twice.
908///
909/// Nothing else about a library goes in. The function names, their descriptions
910/// and their flags are all recovered by running the code again on the way back,
911/// which is why a payload survives a change in how a library describes itself
912/// and why restoring one costs a compile per library.
913pub fn functions<'a>(codes: impl IntoIterator<Item = &'a [u8]>) -> Vec<u8> {
914    let mut out = Vec::new();
915    for code in codes {
916        out.push(OP_FUNCTION2);
917        put_str(&mut out, code);
918    }
919    seal(out)
920}
921
922/// The library code out of a payload `FUNCTION DUMP` produced.
923///
924/// Only the framing is checked here. Whether a given piece of code is a library
925/// at all is a question for whoever compiles it, and answering it here would
926/// mean this crate knowing what a shebang is.
927pub fn libraries(payload: &[u8]) -> Result<Vec<Vec<u8>>, BadLibs> {
928    let body = unseal(payload).map_err(|_| BadLibs::Footer)?;
929    let mut r = Reader::new(body);
930    let mut found = Vec::new();
931    while !r.done() {
932        match r.byte().map_err(|_| BadLibs::Truncated)? {
933            OP_FUNCTION2 => {}
934            OP_FUNCTION_PRE_GA => return Err(BadLibs::PreGa),
935            _ => return Err(BadLibs::NotFunction),
936        }
937        let code = r.str().map_err(|_| BadLibs::Truncated)?;
938        found.push(code.into_owned());
939    }
940    Ok(found)
941}
942
943/// Read one value, leaving the reader wherever that value ended.
944///
945/// Split out from [`load`] because a payload holds exactly one value and a file
946/// holds a great many, so the file reader cannot use the check that there is
947/// nothing left over. It is also what says how long a value is: nothing in the
948/// format writes that down, and the only way to find the end of one is to read
949/// it.
950fn read_object(r: &mut Reader<'_>, kind: u8, limits: Limits<'_>, now: u64) -> Result<Body, Bad> {
951    Ok(match kind {
952        T_STRING => Body::String(r.str()?.into_owned()),
953        T_LIST => read_list(r, limits.list)?,
954        T_LIST_QUICKLIST_2 => read_quicklist(r, limits.list)?,
955        T_SET => read_set(r, limits.set)?,
956        T_SET_INTSET => read_intset(r, limits.set)?,
957        T_SET_LISTPACK => read_set_listpack(r, limits.set)?,
958        T_ZSET | T_ZSET_2 => read_zset(r, limits.zset, kind == T_ZSET_2)?,
959        T_ZSET_LISTPACK => read_zset_listpack(r, limits.zset)?,
960        T_HASH => read_hash(r, limits.hash)?,
961        T_HASH_METADATA => read_hash_metadata(r, limits.hash, now)?,
962        T_HASH_LISTPACK => read_hash_listpack(r, limits.hash, false, now)?,
963        T_HASH_LISTPACK_EX => read_hash_listpack(r, limits.hash, true, now)?,
964        T_STREAM_LISTPACKS | T_STREAM_LISTPACKS_2 | T_STREAM_LISTPACKS_3 | T_STREAM_LISTPACKS_4 => {
965            read_stream(r, kind)?
966        }
967        _ => return Err(Bad::Format),
968    })
969}
970
971/// How many bytes a value of type `ty` takes at the front of `bytes`.
972///
973/// Only the tests want this, and what they want it for is to walk a file this
974/// crate wrote without trusting this crate's own idea of where each value ends.
975/// It is here rather than in [`crate::snapshot`] because [`Reader`] is private
976/// and should stay that way.
977#[cfg(test)]
978pub(crate) fn measure(ty: u8, bytes: &[u8]) -> Option<usize> {
979    let (s, h, l, z) = (
980        set::Limits::DEFAULT,
981        hash::Limits::DEFAULT,
982        list::Limits::default(),
983        zset::Limits::DEFAULT,
984    );
985    let limits = Limits {
986        set: &s,
987        hash: &h,
988        list: &l,
989        zset: &z,
990    };
991    let mut r = Reader::new(bytes);
992    read_object(&mut r, ty, limits, 0).ok()?;
993    Some(r.at)
994}
995
996/// An empty collection is not a value, it is a deleted key.
997///
998/// Redis calls this `emptykey` and refuses the payload rather than creating a
999/// key that every command would treat as missing. A zero length collection
1000/// cannot be produced by any command, so a payload holding one was either
1001/// hand written or corrupted in a way the checksum happened to survive.
1002fn non_empty(n: usize) -> Result<usize, Bad> {
1003    if n == 0 { Err(Bad::Format) } else { Ok(n) }
1004}
1005
1006fn read_list(r: &mut Reader<'_>, limits: &list::Limits) -> Result<Body, Bad> {
1007    let n = r.count()?;
1008    let mut list = List::new();
1009    for _ in 0..n {
1010        list.push_back(&r.str()?, limits);
1011    }
1012    Ok(Body::List(list))
1013}
1014
1015/// A quicklist, which is a count of nodes and then a blob each.
1016///
1017/// A packed node is a whole listpack and a plain node is a single value that was
1018/// too long to pack, and both of them are written as one string, so the only
1019/// difference is whether the string is parsed or pushed.
1020fn read_quicklist(r: &mut Reader<'_>, limits: &list::Limits) -> Result<Body, Bad> {
1021    let nodes = r.count()?;
1022    let mut list = List::new();
1023    for _ in 0..nodes {
1024        let container = r.len_or_encoding()?.0;
1025        let blob = r.str()?;
1026        match container {
1027            NODE_PLAIN => list.push_back(&blob, limits),
1028            NODE_PACKED => {
1029                let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
1030                let mut buf = [0u8; DIGITS_MAX];
1031                for entry in lp.iter() {
1032                    list.push_back(text(entry, &mut buf), limits);
1033                }
1034            }
1035            _ => return Err(Bad::Format),
1036        }
1037    }
1038    if list.is_empty() {
1039        return Err(Bad::Format);
1040    }
1041    Ok(Body::List(list))
1042}
1043
1044fn read_set(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
1045    let n = r.count()?;
1046    let first = r.str()?;
1047    // The hint and the first member together are what decide the band, so the
1048    // first member is read before the set is built rather than after.
1049    let mut set = Set::with_hint(&first, n, limits);
1050    set.add(&first, limits);
1051    for _ in 1..n {
1052        set.add(&r.str()?, limits);
1053    }
1054    Ok(Body::Set(set))
1055}
1056
1057fn read_intset(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
1058    let blob = r.str()?;
1059    let ints = Intset::from_bytes(&blob).map_err(|_| Bad::Format)?;
1060    non_empty(ints.len())?;
1061    let mut buf = [0u8; DIGITS_MAX];
1062    let mut set = Set::with_hint(num::i64_digits(&mut buf, ints.at(0)), ints.len(), limits);
1063    for v in ints.iter() {
1064        set.add(num::i64_digits(&mut buf, v), limits);
1065    }
1066    Ok(Body::Set(set))
1067}
1068
1069fn read_set_listpack(r: &mut Reader<'_>, limits: &set::Limits) -> Result<Body, Bad> {
1070    let blob = r.str()?;
1071    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
1072    non_empty(lp.len())?;
1073    let mut buf = [0u8; DIGITS_MAX];
1074    let first = text(lp.get(0).ok_or(Bad::Format)?, &mut buf).to_vec();
1075    let mut set = Set::with_hint(&first, lp.len(), limits);
1076    for entry in lp.iter() {
1077        set.add(text(entry, &mut buf), limits);
1078    }
1079    Ok(Body::Set(set))
1080}
1081
1082fn read_zset(r: &mut Reader<'_>, limits: &zset::Limits, binary: bool) -> Result<Body, Bad> {
1083    let n = r.count()?;
1084    // Sized from the count, the way `read_set` and `read_hash` are. A sorted set
1085    // that is going to end up on the table should start there, rather than fill
1086    // the packed band to its limit at a scan a member and then throw it away.
1087    let mut zset = Zset::with_hint(n, limits);
1088    for _ in 0..n {
1089        let member = r.str()?;
1090        let score = if binary {
1091            r.double()?
1092        } else {
1093            r.double_text()?
1094        };
1095        zset.add(&member, score, limits);
1096    }
1097    Ok(Body::Zset(zset))
1098}
1099
1100fn read_zset_listpack(r: &mut Reader<'_>, limits: &zset::Limits) -> Result<Body, Bad> {
1101    let blob = r.str()?;
1102    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
1103    if lp.is_empty() || lp.len() % 2 != 0 {
1104        return Err(Bad::Format);
1105    }
1106    // The payload is already the layout the packed band uses, so the fast answer
1107    // is to take it whole rather than to add a member at a time. `from_packed`
1108    // hands the blob back when it will not have it, and then the walk below
1109    // rebuilds it, which is what happens to a payload that is out of order or
1110    // past this server's limits.
1111    let lp = match Zset::from_packed(lp, limits) {
1112        Ok(zset) => return Ok(Body::Zset(zset)),
1113        Err(lp) => lp,
1114    };
1115    let mut zset = Zset::with_hint(lp.len() / 2, limits);
1116    let mut member = [0u8; DIGITS_MAX];
1117    let mut score = [0u8; DIGITS_MAX];
1118    // Walked and not indexed. A listpack has no offset table, so asking it for
1119    // element `i` costs a walk from the front and asking it for every element in
1120    // turn costs the square of the count.
1121    let mut walk = lp.iter();
1122    while let Some(entry) = walk.next() {
1123        let name = text(entry, &mut member).to_vec();
1124        let at = text(walk.next().ok_or(Bad::Format)?, &mut score);
1125        let at = num::parse_f64(at).ok_or(Bad::Format)?;
1126        zset.add(&name, at, limits);
1127    }
1128    Ok(Body::Zset(zset))
1129}
1130
1131fn read_hash(r: &mut Reader<'_>, limits: &hash::Limits) -> Result<Body, Bad> {
1132    let n = r.count()?;
1133    let mut hash = Hash::with_hint(n, limits);
1134    for _ in 0..n {
1135        let field = r.str()?;
1136        let value = r.str()?;
1137        hash.set(&field, &value, limits);
1138    }
1139    Ok(Body::Hash(hash))
1140}
1141
1142/// A hash with field deadlines, which is a header, a count and then triples.
1143///
1144/// The header is the earliest deadline in the hash and each field holds its own
1145/// distance from it, plus one so that a zero can mean no deadline at all.
1146fn read_hash_metadata(r: &mut Reader<'_>, limits: &hash::Limits, now: u64) -> Result<Body, Bad> {
1147    let soonest = u64::from_le_bytes(r.take(8)?.try_into().expect("eight bytes"));
1148    let n = r.count()?;
1149    let mut hash = Hash::with_hint(n, limits);
1150    for _ in 0..n {
1151        let ttl = r.len_or_encoding()?.0;
1152        let field = r.str()?;
1153        let value = r.str()?;
1154        put_field(
1155            &mut hash,
1156            &field,
1157            &value,
1158            deadline(soonest, ttl),
1159            limits,
1160            now,
1161        );
1162    }
1163    if hash.is_empty() {
1164        return Err(Bad::Format);
1165    }
1166    Ok(Body::Hash(hash))
1167}
1168
1169/// A hash packed into one listpack, with or without the deadline column.
1170fn read_hash_listpack(
1171    r: &mut Reader<'_>,
1172    limits: &hash::Limits,
1173    with_ttl: bool,
1174    now: u64,
1175) -> Result<Body, Bad> {
1176    let soonest = if with_ttl {
1177        u64::from_le_bytes(r.take(8)?.try_into().expect("eight bytes"))
1178    } else {
1179        0
1180    };
1181    let blob = r.str()?;
1182    let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
1183    let step = if with_ttl { 3 } else { 2 };
1184    if lp.is_empty() || lp.len() % step != 0 {
1185        return Err(Bad::Format);
1186    }
1187    // The payload is already the layout the packed band uses, so take it whole
1188    // rather than set a field at a time. Only the two element form: the deadline
1189    // column is a band this cannot hand over, for the reason `packed_bytes`
1190    // refuses to copy it on the way out.
1191    let lp = if with_ttl {
1192        lp
1193    } else {
1194        match Hash::from_packed(lp, limits) {
1195            Ok(hash) => return Ok(Body::Hash(hash)),
1196            Err(lp) => lp,
1197        }
1198    };
1199    let mut hash = Hash::with_hint(lp.len() / step, limits);
1200    let mut field_buf = [0u8; DIGITS_MAX];
1201    let mut value_buf = [0u8; DIGITS_MAX];
1202    // Walked and not indexed, for the reason `read_zset_listpack` gives: element
1203    // `i` of a listpack costs a walk from the front.
1204    let mut walk = lp.iter();
1205    while let Some(entry) = walk.next() {
1206        let field = text(entry, &mut field_buf).to_vec();
1207        let value = text(walk.next().ok_or(Bad::Format)?, &mut value_buf).to_vec();
1208        // The packed form holds the deadline as an absolute time, not as a
1209        // distance from the header, which is the one place the two hash layouts
1210        // disagree about the same number.
1211        let at = if with_ttl {
1212            match walk.next().ok_or(Bad::Format)? {
1213                Entry::Int(0) => None,
1214                Entry::Int(n) => Some(u64::try_from(n).map_err(|_| Bad::Format)?),
1215                Entry::Str(_) => return Err(Bad::Format),
1216            }
1217        } else {
1218            None
1219        };
1220        put_field(&mut hash, &field, &value, at, limits, now);
1221    }
1222    let _ = soonest;
1223    if hash.is_empty() {
1224        return Err(Bad::Format);
1225    }
1226    Ok(Body::Hash(hash))
1227}
1228
1229/// A stream, in whichever of the four types it arrived as.
1230///
1231/// The nodes go straight in, since a payload's listpack is the blob this holds
1232/// anyway, and the rest of the payload is read into the counters and the groups
1233/// around them. Everything is checked on the way rather than trusted, because a
1234/// `RESTORE` takes these bytes from a client.
1235///
1236/// The four types are the same format with pieces added on the end of it, so
1237/// what varies is which fields are there and not where anything is:
1238///
1239/// ```text
1240/// 15  the nodes, the length and the last ID
1241/// 19  and the first ID, the deleted high water mark, the two read counters
1242/// 21  and a consumer's active time as well as its seen time
1243/// 27  and a per group field and a stream wide idempotency block
1244/// ```
1245///
1246/// The three fields 15 does not carry are worked out rather than left empty,
1247/// which is what Redis does with the same payload. There is nothing above the
1248/// oldest entry that has been deleted, since a type 15 payload has no way to say
1249/// there was, and everything that is there was added, since a stream that has
1250/// been trimmed cannot say so either. Both are the honest reading of a format
1251/// that predates the question.
1252fn read_stream(r: &mut Reader<'_>, kind: u8) -> Result<Body, Bad> {
1253    let edges = kind != T_STREAM_LISTPACKS;
1254    let active = kind == T_STREAM_LISTPACKS_3 || kind == T_STREAM_LISTPACKS_4;
1255    let mut s = Stream::new();
1256
1257    let nodes = r.bounded()?;
1258    for _ in 0..nodes {
1259        let key = r.str()?;
1260        let key: [u8; 16] = key.as_ref().try_into().map_err(|_| Bad::Format)?;
1261        let blob = r.str()?;
1262        let lp = Listpack::from_bytes(&blob).map_err(|_| Bad::Format)?;
1263        // A node with nothing in it has no master entry, so every walk over it
1264        // would stop on the first read and the stream would be holding bytes
1265        // that answer nothing. Redis refuses the same node for the same reason.
1266        if lp.is_empty() || !s.push_raw_node(Id::from_bytes(key), lp) {
1267            return Err(Bad::Format);
1268        }
1269    }
1270
1271    let length = r.num()?;
1272    let last = r.id()?;
1273    let (max_deleted, added) = if edges {
1274        // The first ID is read and dropped. It is the oldest entry that is still
1275        // there, which the nodes already say, and taking the payload's word for
1276        // it would let a wrong one disagree with what a range walk finds.
1277        let _first = r.id()?;
1278        (r.id()?, r.num()?)
1279    } else {
1280        (Id::MIN, length)
1281    };
1282    if !s.set_counters(length, last, max_deleted, added) {
1283        return Err(Bad::Format);
1284    }
1285
1286    let groups = r.bounded()?;
1287    for _ in 0..groups {
1288        let name = r.str()?.into_owned();
1289        let last = r.id()?;
1290        // Minus one is how the format says the count is not known, and it is not
1291        // a count that happens to be enormous.
1292        let read = match edges {
1293            true => match r.num()? {
1294                u64::MAX => None,
1295                n => Some(n),
1296            },
1297            false => None,
1298        };
1299        let mut group = Group::new(last, read);
1300
1301        let pending = r.bounded()?;
1302        for _ in 0..pending {
1303            let id = r.raw_id()?;
1304            let time = r.millis()?.max(0) as u64;
1305            let count = r.num()?;
1306            if !group.restore_nack(id, time, count) {
1307                return Err(Bad::Format);
1308            }
1309        }
1310
1311        let consumers = r.bounded()?;
1312        for _ in 0..consumers {
1313            let name = r.str()?.into_owned();
1314            let seen = r.millis()?.max(0) as u64;
1315            // Before type 21 there was one time and it stood for both, which is
1316            // what Redis fills in when it reads an older payload.
1317            let at = if active { r.millis()? } else { seen as i64 };
1318            let Some(slot) = group.restore_consumer(&name, seen, (at >= 0).then_some(at as u64))
1319            else {
1320                return Err(Bad::Format);
1321            };
1322            let held = r.bounded()?;
1323            for _ in 0..held {
1324                let id = r.raw_id()?;
1325                if !group.restore_owner(id, slot) {
1326                    return Err(Bad::Format);
1327                }
1328            }
1329        }
1330        if kind == T_STREAM_LISTPACKS_4 {
1331            skip_group_idmp(r)?;
1332        }
1333        if !s.push_group(&name, group) {
1334            return Err(Bad::Format);
1335        }
1336    }
1337    if kind == T_STREAM_LISTPACKS_4 {
1338        skip_idmp(r)?;
1339    }
1340    Ok(Body::Stream(s))
1341}
1342
1343/// The group's half of the idempotency block a Redis 8.10 writes.
1344///
1345/// One count, and it is zero in every payload a real server has been seen to
1346/// produce. Read and dropped, for the reason [`skip_idmp`] gives.
1347fn skip_group_idmp(r: &mut Reader<'_>) -> Result<(), Bad> {
1348    if r.num()? != 0 {
1349        return Err(Bad::Format);
1350    }
1351    Ok(())
1352}
1353
1354/// The idempotency block a Redis 8.10 puts on the end of a stream.
1355///
1356/// Producer IDs are a thing this server does not track at all, so what is here
1357/// is read to find the end of the payload and then dropped. Dropping it is a
1358/// divergence and is registered as one, and it is a smaller one than it sounds:
1359/// a payload only ever carries the producer names and never any of the IDs
1360/// recorded against them, so a real Redis loading its own `DUMP` comes back with
1361/// `pids-tracked` and `iids-tracked` both at zero as well. Checked on 8.10.1 by
1362/// recording two IDs under one producer, dumping, restoring the payload under
1363/// another key and asking.
1364///
1365/// The two counts that must be zero are refused rather than skipped when they
1366/// are not. Nothing has been seen to write one, so what follows a nonzero count
1367/// is not something that can be read off a server, and guessing it would be
1368/// inventing a format instead of reading one. Refusing says so out loud, where a
1369/// skip would quietly turn the rest of the payload into nonsense.
1370fn skip_idmp(r: &mut Reader<'_>) -> Result<(), Bad> {
1371    let _duration = r.num()?;
1372    let _maxsize = r.num()?;
1373    let producers = r.bounded()?;
1374    for _ in 0..producers {
1375        let _name = r.str()?;
1376        if r.num()? != 0 {
1377            return Err(Bad::Format);
1378        }
1379    }
1380    let _added = r.num()?;
1381    let _duplicates = r.num()?;
1382    Ok(())
1383}
1384
1385/// A field's absolute deadline from the header and its stored distance.
1386const fn deadline(soonest: u64, ttl: u64) -> Option<u64> {
1387    if ttl == 0 {
1388        None
1389    } else {
1390        Some(soonest + ttl - 1)
1391    }
1392}
1393
1394/// Put one field in, unless its deadline has already gone.
1395fn put_field(
1396    hash: &mut Hash,
1397    field: &[u8],
1398    value: &[u8],
1399    at: Option<u64>,
1400    limits: &hash::Limits,
1401    now: u64,
1402) {
1403    if let Some(at) = at
1404        && at <= now
1405    {
1406        return;
1407    }
1408    hash.set(field, value, limits);
1409    if let Some(at) = at {
1410        hash.expire(field, at, crate::ttl::Cond::Always, now);
1411    }
1412}
1413
1414/// A listpack entry as bytes, formatting an integer into the caller's buffer.
1415fn text<'a>(entry: Entry<'a>, buf: &'a mut [u8; DIGITS_MAX]) -> &'a [u8] {
1416    match entry {
1417        Entry::Int(n) => num::i64_digits(buf, n),
1418        Entry::Str(s) => s,
1419    }
1420}
1421
1422#[cfg(test)]
1423mod tests {
1424    use super::*;
1425    use crate::stream::Retry;
1426    use crate::ttl::{Ask, Cond};
1427
1428    fn limits() -> (set::Limits, hash::Limits, list::Limits, zset::Limits) {
1429        (
1430            set::Limits::DEFAULT,
1431            hash::Limits::DEFAULT,
1432            list::Limits::default(),
1433            zset::Limits::DEFAULT,
1434        )
1435    }
1436
1437    fn round_trip(body: Body) -> Body {
1438        let (s, h, l, z) = limits();
1439        let all = Limits {
1440            set: &s,
1441            hash: &h,
1442            list: &l,
1443            zset: &z,
1444        };
1445        let rec = Record::new(body, None);
1446        let payload = dump(&rec).expect("this type has an RDB shape");
1447        load(&payload, all, 0).expect("what we wrote we can read")
1448    }
1449
1450    fn set_of(members: &[&[u8]]) -> Set {
1451        let l = set::Limits::DEFAULT;
1452        let mut set = Set::with_hint(members[0], members.len(), &l);
1453        for m in members {
1454            set.add(m, &l);
1455        }
1456        set
1457    }
1458
1459    #[test]
1460    fn a_payload_is_ten_bytes_longer_than_the_object() {
1461        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1462        let payload = dump(&rec).expect("a string has an RDB shape");
1463        // One type byte, one length byte, five of text, and the footer.
1464        assert_eq!(payload.len(), 1 + 1 + 5 + FOOTER);
1465        assert_eq!(payload[0], T_STRING);
1466    }
1467
1468    #[test]
1469    fn a_string_that_is_a_number_goes_out_as_one() {
1470        let rec = Record::new(Body::String(b"1234".to_vec()), None);
1471        let payload = dump(&rec).expect("a string has an RDB shape");
1472        // The type byte, the encoding byte, two bytes of integer, and the footer.
1473        assert_eq!(payload.len(), 1 + 1 + 2 + FOOTER);
1474        let Body::String(back) = round_trip(Body::String(b"1234".to_vec())) else {
1475            panic!("a string came back as something else");
1476        };
1477        assert_eq!(back, b"1234");
1478    }
1479
1480    /// A number with a leading zero is not the same string as the number, so it
1481    /// has to stay text or the round trip changes the value.
1482    #[test]
1483    fn a_string_that_only_looks_like_a_number_stays_text() {
1484        for s in [&b"007"[..], b"+7", b"-0", b" 7", b"9223372036854775808"] {
1485            let Body::String(back) = round_trip(Body::String(s.to_vec())) else {
1486                panic!("a string came back as something else");
1487            };
1488            assert_eq!(back, s, "{} did not survive", String::from_utf8_lossy(s));
1489        }
1490    }
1491
1492    #[test]
1493    fn every_integer_width_survives() {
1494        for n in [0i64, 1, -1, 127, -128, 128, -129, 32767, -32768, 32768] {
1495            let mut buf = [0u8; DIGITS_MAX];
1496            let s = num::i64_digits(&mut buf, n).to_vec();
1497            let Body::String(back) = round_trip(Body::String(s.clone())) else {
1498                panic!("a string came back as something else");
1499            };
1500            assert_eq!(back, s, "{n} did not survive");
1501        }
1502        // Past `i32` there is no encoding, so it goes as digits and still has to
1503        // come back the same.
1504        let big = b"2147483648".to_vec();
1505        let Body::String(back) = round_trip(Body::String(big.clone())) else {
1506            panic!("a string came back as something else");
1507        };
1508        assert_eq!(back, big);
1509    }
1510
1511    #[test]
1512    fn a_set_comes_back_with_the_same_members() {
1513        let set = set_of(&[b"alpha", b"beta", b"gamma"]);
1514        let Body::Set(back) = round_trip(Body::Set(set)) else {
1515            panic!("a set came back as something else");
1516        };
1517        assert_eq!(back.len(), 3);
1518        for m in [&b"alpha"[..], b"beta", b"gamma"] {
1519            assert!(
1520                back.contains(m),
1521                "{} went missing",
1522                String::from_utf8_lossy(m)
1523            );
1524        }
1525    }
1526
1527    /// An all integer set is held as an intset here and the round trip has to
1528    /// land it back in the same band, not in a listpack that happens to hold the
1529    /// same members.
1530    #[test]
1531    fn an_integer_set_comes_back_as_an_integer_set() {
1532        let set = set_of(&[b"1", b"2", b"3"]);
1533        let was = set.encoding();
1534        let Body::Set(back) = round_trip(Body::Set(set)) else {
1535            panic!("a set came back as something else");
1536        };
1537        assert_eq!(back.encoding(), was);
1538        assert_eq!(back.len(), 3);
1539        assert!(back.contains(b"2"));
1540    }
1541
1542    #[test]
1543    fn a_list_keeps_its_order() {
1544        let l = list::Limits::default();
1545        let mut list = List::new();
1546        for v in [&b"one"[..], b"two", b"three"] {
1547            list.push_back(v, &l);
1548        }
1549        let Body::List(back) = round_trip(Body::List(list)) else {
1550            panic!("a list came back as something else");
1551        };
1552        let mut seen = Vec::new();
1553        for e in back.iter() {
1554            let mut buf = Vec::new();
1555            e.write_to(&mut buf);
1556            seen.push(buf);
1557        }
1558        assert_eq!(
1559            seen,
1560            vec![b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
1561        );
1562    }
1563
1564    #[test]
1565    fn a_sorted_set_keeps_its_scores() {
1566        let l = zset::Limits::DEFAULT;
1567        let mut zset = Zset::new();
1568        zset.add(b"a", 1.5, &l);
1569        zset.add(b"b", -2.0, &l);
1570        zset.add(b"c", f64::INFINITY, &l);
1571        let Body::Zset(back) = round_trip(Body::Zset(zset)) else {
1572            panic!("a sorted set came back as something else");
1573        };
1574        assert_eq!(back.score(b"a"), Some(1.5));
1575        assert_eq!(back.score(b"b"), Some(-2.0));
1576        assert_eq!(back.score(b"c"), Some(f64::INFINITY));
1577    }
1578
1579    #[test]
1580    fn a_hash_with_no_deadlines_uses_the_plain_type() {
1581        let l = hash::Limits::DEFAULT;
1582        let mut hash = Hash::new();
1583        // Past the packed band, so this is the table and there is no blob to
1584        // copy. The small case is the listpack one and it is tested below.
1585        for i in 0..1000 {
1586            hash.set(format!("f{i}").as_bytes(), b"1", &l);
1587        }
1588        assert_eq!(hash.encoding(), hash::Encoding::Hashtable);
1589        let rec = Record::new(Body::Hash(hash.clone()), None);
1590        assert_eq!(dump(&rec).expect("a hash has an RDB shape")[0], T_HASH);
1591        let Body::Hash(back) = round_trip(Body::Hash(hash)) else {
1592            panic!("a hash came back as something else");
1593        };
1594        assert_eq!(back.len(), 1000);
1595        assert_eq!(back.get(b"f7").map(|v| v.byte_len()), Some(1));
1596    }
1597
1598    /// A value that is one packed blob goes out as the blob.
1599    ///
1600    /// The type byte is the thing being pinned here. Every one of these round
1601    /// trips already, through the walk, and the point of the check is that it is
1602    /// no longer going through the walk.
1603    #[test]
1604    fn a_packed_value_goes_out_as_its_blob() {
1605        let (sl, hl, _, zl) = limits();
1606        let mut hash = Hash::new();
1607        hash.set(b"one", b"1", &hl);
1608        hash.set(b"two", b"2", &hl);
1609        assert_eq!(hash.encoding(), hash::Encoding::Listpack);
1610
1611        let mut set = Set::new();
1612        set.add(b"alpha", &sl);
1613        set.add(b"beta", &sl);
1614        assert_eq!(set.encoding(), set::Encoding::Listpack);
1615
1616        let mut ints = Set::new();
1617        ints.add(b"1", &sl);
1618        ints.add(b"9", &sl);
1619        assert_eq!(ints.encoding(), set::Encoding::Intset);
1620
1621        let mut zset = Zset::new();
1622        zset.add(b"a", 1.5, &zl);
1623        assert_eq!(zset.encoding(), zset::Encoding::Listpack);
1624
1625        for (want, body) in [
1626            (T_HASH_LISTPACK, Body::Hash(hash)),
1627            (T_SET_LISTPACK, Body::Set(set)),
1628            (T_SET_INTSET, Body::Set(ints)),
1629            (T_ZSET_LISTPACK, Body::Zset(zset)),
1630        ] {
1631            let rec = Record::new(body.clone(), None);
1632            let payload = dump(&rec).expect("a packed value has an RDB shape");
1633            assert_eq!(payload[0], want, "wrong type byte for {body:?}");
1634            // And the walk is gone, not merely bypassed: what comes back has to
1635            // be the same value or the copy was of the wrong bytes.
1636            assert_eq!(
1637                format!("{:?}", round_trip(body.clone())),
1638                format!("{body:?}")
1639            );
1640        }
1641    }
1642
1643    /// A hash that has been widened for deadlines is walked even once they have
1644    /// all gone, because the blob it holds still has the third element per field
1645    /// and `HASH_LISTPACK` has no room for it.
1646    #[test]
1647    fn a_widened_hash_is_not_copied() {
1648        let l = hash::Limits::DEFAULT;
1649        let mut hash = Hash::new();
1650        hash.set(b"one", b"1", &l);
1651        hash.expire(b"one", 5_000, Cond::Always, 0);
1652        hash.persist(b"one");
1653        // The bound leans early and only a reap that walks puts it right, so
1654        // this is what it takes to get a hash that is on the wider band and has
1655        // nothing left to say about deadlines.
1656        hash.reap(6_000, |_| {});
1657        assert_eq!(hash.encoding(), hash::Encoding::ListpackEx);
1658        assert_eq!(hash.soonest_deadline(), None);
1659        let rec = Record::new(Body::Hash(hash.clone()), None);
1660        assert_eq!(dump(&rec).expect("a hash has an RDB shape")[0], T_HASH);
1661        let Body::Hash(back) = round_trip(Body::Hash(hash)) else {
1662            panic!("a hash came back as something else");
1663        };
1664        assert_eq!(back.len(), 1);
1665        assert_eq!(back.deadline(b"one"), Ask::NoDeadline);
1666    }
1667
1668    #[test]
1669    fn a_hash_carries_its_field_deadlines_across() {
1670        let l = hash::Limits::DEFAULT;
1671        let mut hash = Hash::new();
1672        hash.set(b"keep", b"1", &l);
1673        hash.set(b"timed", b"2", &l);
1674        hash.expire(b"timed", 5_000, Cond::Always, 1_000);
1675        let rec = Record::new(Body::Hash(hash.clone()), None);
1676        assert_eq!(
1677            dump(&rec).expect("a hash has an RDB shape")[0],
1678            T_HASH_METADATA
1679        );
1680        let (s, h, li, z) = limits();
1681        let all = Limits {
1682            set: &s,
1683            hash: &h,
1684            list: &li,
1685            zset: &z,
1686        };
1687        let payload = dump(&rec).expect("a hash has an RDB shape");
1688        let Body::Hash(back) = load(&payload, all, 1_000).expect("it reads back") else {
1689            panic!("a hash came back as something else");
1690        };
1691        assert_eq!(back.len(), 2);
1692        assert_eq!(back.deadline(b"timed"), crate::ttl::Ask::At(5_000));
1693        assert_eq!(back.deadline(b"keep"), crate::ttl::Ask::NoDeadline);
1694    }
1695
1696    /// A field whose deadline went while the payload was in flight is not put
1697    /// back, because the next read would delete it anyway and a count that is
1698    /// wrong until somebody looks is worse than a field that never arrived.
1699    #[test]
1700    fn a_field_that_expired_in_transit_does_not_come_back() {
1701        let l = hash::Limits::DEFAULT;
1702        let mut hash = Hash::new();
1703        hash.set(b"keep", b"1", &l);
1704        hash.set(b"gone", b"2", &l);
1705        hash.expire(b"gone", 5_000, Cond::Always, 1_000);
1706        let rec = Record::new(Body::Hash(hash), None);
1707        let payload = dump(&rec).expect("a hash has an RDB shape");
1708        let (s, h, li, z) = limits();
1709        let all = Limits {
1710            set: &s,
1711            hash: &h,
1712            list: &li,
1713            zset: &z,
1714        };
1715        let Body::Hash(back) = load(&payload, all, 9_000).expect("it reads back") else {
1716            panic!("a hash came back as something else");
1717        };
1718        assert_eq!(back.len(), 1);
1719        assert!(back.contains(b"keep"));
1720        assert!(!back.contains(b"gone"));
1721    }
1722
1723    #[test]
1724    fn a_flipped_byte_is_caught() {
1725        let rec = Record::new(Body::String(b"hello there".to_vec()), None);
1726        let good = dump(&rec).expect("a string has an RDB shape");
1727        for i in 0..good.len() {
1728            let mut bad = good.clone();
1729            bad[i] ^= 1;
1730            let (s, h, l, z) = limits();
1731            let all = Limits {
1732                set: &s,
1733                hash: &h,
1734                list: &l,
1735                zset: &z,
1736            };
1737            assert!(
1738                load(&bad, all, 0).is_err(),
1739                "byte {i} could be changed without anything noticing"
1740            );
1741        }
1742    }
1743
1744    #[test]
1745    fn a_payload_from_a_newer_server_is_refused() {
1746        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1747        let mut payload = dump(&rec).expect("a string has an RDB shape");
1748        let n = payload.len();
1749        payload[n - 10] = 99;
1750        // The checksum has to be put right, or this would pass for the wrong
1751        // reason and the version check would never be reached.
1752        let crc = crc64(0, &payload[..n - 8]);
1753        payload[n - 8..].copy_from_slice(&crc.to_le_bytes());
1754        let (s, h, l, z) = limits();
1755        let all = Limits {
1756            set: &s,
1757            hash: &h,
1758            list: &l,
1759            zset: &z,
1760        };
1761        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Footer);
1762    }
1763
1764    /// Every version up to the one a current Redis stamps is read, and the one
1765    /// after it is not.
1766    ///
1767    /// This is the check that was missing when `RESTORE` was turning down every
1768    /// payload a real 8.10.1 produced. The old code compared against the version
1769    /// it writes, which is deliberately old so that old servers accept us, so
1770    /// making one number do both jobs meant refusing everything modern.
1771    #[test]
1772    fn a_payload_is_read_up_to_the_version_that_has_been_checked() {
1773        let rec = Record::new(Body::String(b"hello".to_vec()), None);
1774        let (s, h, l, z) = limits();
1775        let all = Limits {
1776            set: &s,
1777            hash: &h,
1778            list: &l,
1779            zset: &z,
1780        };
1781        for stamp in [VERSION, READS_UP_TO, READS_UP_TO + 1] {
1782            let mut payload = dump(&rec).expect("a string has an RDB shape");
1783            let n = payload.len();
1784            payload[n - 10..n - 8].copy_from_slice(&stamp.to_le_bytes());
1785            let crc = crc64(0, &payload[..n - 8]);
1786            payload[n - 8..].copy_from_slice(&crc.to_le_bytes());
1787            let got = load(&payload, all, 0);
1788            if stamp > READS_UP_TO {
1789                assert_eq!(got.unwrap_err(), Bad::Footer, "{stamp} should be refused");
1790            } else {
1791                assert!(got.is_ok(), "{stamp} should be read");
1792            }
1793        }
1794        const {
1795            assert!(
1796                VERSION <= READS_UP_TO,
1797                "a server that cannot read what it writes is no use to anybody"
1798            )
1799        };
1800    }
1801
1802    #[test]
1803    fn a_payload_shorter_than_its_footer_is_refused() {
1804        for n in 0..FOOTER {
1805            assert_eq!(unseal(&vec![0u8; n]), Err(Bad::Footer));
1806        }
1807    }
1808
1809    /// Nothing here should be able to panic on bytes a client made up, so the
1810    /// whole space of short payloads gets tried with a correct footer on it.
1811    #[test]
1812    fn arbitrary_bytes_are_an_error_and_not_a_panic() {
1813        let (s, h, l, z) = limits();
1814        let all = Limits {
1815            set: &s,
1816            hash: &h,
1817            list: &l,
1818            zset: &z,
1819        };
1820        for kind in 0u8..=26 {
1821            for len in 0usize..6 {
1822                for fill in [0u8, 1, 0x40, 0x80, 0x81, 0xc0, 0xc3, 0xff] {
1823                    let mut body = vec![kind];
1824                    body.extend(std::iter::repeat_n(fill, len));
1825                    let payload = seal(body);
1826                    let _ = load(&payload, all, 0);
1827                }
1828            }
1829        }
1830    }
1831
1832    /// A count bigger than the payload is refused before anything is reserved.
1833    ///
1834    /// [`arbitrary_bytes_are_an_error_and_not_a_panic`] already sends these
1835    /// bytes and could not catch this, for two reasons worth writing down. An
1836    /// out of memory abort is not a panic, so a test that only says nothing
1837    /// panics will watch the process die and report nothing. And the dev machine
1838    /// overcommits, so the reservation succeeded there and only ever failed on
1839    /// Linux and Windows, which is to say in CI on the release tag and nowhere a
1840    /// person would see it.
1841    ///
1842    /// The bytes are the ones that did it. `0x80` opens a thirty two bit length
1843    /// and the four after it are the length, so the count comes out as
1844    /// `0x80808080`, and a row sixteen bytes wide makes that a request for
1845    /// thirty four gigabytes from a six byte payload.
1846    #[test]
1847    fn a_count_past_the_payload_is_refused_before_anything_is_reserved() {
1848        let (s, h, l, z) = limits();
1849        let all = Limits {
1850            set: &s,
1851            hash: &h,
1852            list: &l,
1853            zset: &z,
1854        };
1855        for kind in [T_SET, T_HASH, T_ZSET_2, T_ZSET, T_LIST_QUICKLIST_2] {
1856            let payload = seal(vec![kind, 0x80, 0x80, 0x80, 0x80, 0x80]);
1857            assert_eq!(
1858                load(&payload, all, 0).err(),
1859                Some(Bad::Format),
1860                "type {kind} took a count of 0x80808080 from six bytes"
1861            );
1862        }
1863
1864        // The bound is the bytes that are left and not a fixed ceiling, so a
1865        // count that is small in absolute terms is still refused when the
1866        // payload cannot possibly hold it. Ten members and nothing after the
1867        // count to hold them.
1868        let mut body = vec![T_SET];
1869        put_len(&mut body, 10);
1870        assert_eq!(load(&seal(body), all, 0).err(), Some(Bad::Format));
1871
1872        // And a count the payload can hold is read, so the bound is not simply
1873        // refusing everything.
1874        let mut body = vec![T_SET];
1875        put_len(&mut body, 2);
1876        put_str(&mut body, b"a");
1877        put_str(&mut body, b"b");
1878        assert!(load(&seal(body), all, 0).is_ok());
1879    }
1880
1881    #[test]
1882    fn lzf_unpacks_a_literal_run() {
1883        // One control byte saying four literals, then the four.
1884        assert_eq!(
1885            unpack(&[3, b'a', b'b', b'c', b'd'], 4).as_deref(),
1886            Some(&b"abcd"[..])
1887        );
1888    }
1889
1890    /// The case the byte at a time copy exists for: a back reference that reads
1891    /// bytes it is in the middle of writing.
1892    #[test]
1893    fn lzf_unpacks_an_overlapping_reference() {
1894        // One literal `a`, then a reference one byte back for five bytes. The
1895        // low five bits of the control byte and the byte after it are the
1896        // distance, and they are both zero because a distance is stored one
1897        // less than it is.
1898        let packed = [0u8, b'a', 3 << 5, 0];
1899        assert_eq!(unpack(&packed, 6).as_deref(), Some(&b"aaaaaa"[..]));
1900    }
1901
1902    #[test]
1903    fn lzf_refuses_a_reference_to_nothing() {
1904        assert_eq!(unpack(&[(3 << 5), 0], 5), None);
1905        assert_eq!(unpack(&[3, b'a'], 4), None);
1906    }
1907
1908    #[test]
1909    fn an_empty_collection_is_not_a_value() {
1910        let mut body = vec![T_SET];
1911        put_len(&mut body, 0);
1912        let payload = seal(body);
1913        let (s, h, l, z) = limits();
1914        let all = Limits {
1915            set: &s,
1916            hash: &h,
1917            list: &l,
1918            zset: &z,
1919        };
1920        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Format);
1921    }
1922
1923    #[test]
1924    fn trailing_bytes_are_refused() {
1925        let mut body = vec![T_STRING];
1926        put_str(&mut body, b"hello");
1927        body.push(0);
1928        let payload = seal(body);
1929        let (s, h, l, z) = limits();
1930        let all = Limits {
1931            set: &s,
1932            hash: &h,
1933            list: &l,
1934            zset: &z,
1935        };
1936        assert_eq!(load(&payload, all, 0).unwrap_err(), Bad::Format);
1937    }
1938
1939    #[test]
1940    fn every_length_form_round_trips() {
1941        for n in [0u64, 63, 64, 16383, 16384, u64::from(u32::MAX), 1 << 40] {
1942            let mut out = Vec::new();
1943            put_len(&mut out, n);
1944            let mut r = Reader::new(&out);
1945            assert_eq!(r.len_or_encoding(), Ok((n, false)), "{n} did not survive");
1946            assert!(r.done(), "{n} left bytes behind");
1947        }
1948    }
1949
1950    // -----------------------------------------------------------------------
1951    // Streams.
1952    // -----------------------------------------------------------------------
1953
1954    /// The stream `sample()` builds, dumped by a Redis 8.10.1 over a socket.
1955    ///
1956    /// Type 27, which is what a current server writes: `LISTPACKS_3` with a
1957    /// count on the end of every group and an idempotency block on the end of
1958    /// the stream. Both are zero here and are zero on anything this server can
1959    /// hold, since it does not track producer IDs.
1960    ///
1961    /// The times in it are real times from the machine it was taken on, which is
1962    /// the point of hard coding the payload rather than building one: nothing in
1963    /// this file gets to pick what a delivery time looks like.
1964    const FROM_REDIS: &[u8] = &[
1965        0x1b, 0x01, 0x10, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0x40, 0x5f, 0x5f, 0, 0,
1966        0, 0x1f, 0, 3, 1, 1, 1, 2, 1, 0x86, b's', b'e', b'n', b's', b'o', b'r', 7, 0x87, b'r',
1967        b'e', b'a', b'd', b'i', b'n', b'g', 8, 0, 1, 2, 1, 0, 1, 0, 1, 0x81, b'a', 2, 1, 1, 5, 1,
1968        3, 1, 0, 1, 1, 1, 0x81, b'b', 2, 2, 1, 5, 1, 2, 1, 0xc2, 0xb7, 2, 0xdf, 0xff, 2, 0x81,
1969        b'c', 2, 3, 1, 5, 1, 0, 1, 0xc3, 0x7f, 2, 0xdf, 0xff, 2, 1, 1, 0x85, b'o', b't', b'h',
1970        b'e', b'r', 6, 0x81, b'x', 2, 6, 1, 0xff, 3, 0x43, 0x84, 0, 5, 1, 5, 2, 4, 2, 2, b'g',
1971        b'1', 0x42, 0xbc, 0, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 2, 0, 0, 0, 0,
1972        0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0x50, 0xe1, 0x0a, 0x66, 0xa0, 1, 0, 0, 1, 0, 0, 0, 0,
1973        0, 0, 2, 0xbc, 0, 0, 0, 0, 0, 0, 0, 0, 0x50, 0xe1, 0x0a, 0x66, 0xa0, 1, 0, 0, 1, 2, 5,
1974        b'a', b'l', b'i', b'c', b'e', 0x50, 0xe1, 0x0a, 0x66, 0xa0, 1, 0, 0, 0x50, 0xe1, 0x0a,
1975        0x66, 0xa0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
1976        2, 0xbc, 0, 0, 0, 0, 0, 0, 0, 0, 3, b'b', b'o', b'b', 0x70, 0xe1, 0x0a, 0x66, 0xa0, 1, 0,
1977        0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 2, b'g', b'2', 0x43, 0x84, 0,
1978        0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0x40, 0x64, 0x40, 0x64, 0,
1979        0, 0, 0x0f, 0, 0x5c, 0x1f, 0xdc, 0xf0, 0xb9, 0x7e, 0x8b, 0x75,
1980    ];
1981
1982    /// When the two deliveries in [`FROM_REDIS`] happened, and when `bob` was
1983    /// made a few milliseconds later.
1984    const DELIVERED: u64 = 1_788_418_384_208;
1985    const MADE: u64 = 1_788_418_384_240;
1986
1987    /// The stream [`FROM_REDIS`] is a dump of, built here instead.
1988    ///
1989    /// Four entries with one deleted, two groups, and one of them with two
1990    /// consumers where only one has anything. Built by the same commands in the
1991    /// same order, so that the two can be compared as values rather than as
1992    /// bytes.
1993    fn sample() -> Stream {
1994        let l = crate::stream::Limits::default();
1995        let mut s = Stream::new();
1996        s.append(Id::new(5, 1), &[(b"sensor", b"a"), (b"reading", b"1")], l)
1997            .expect("the first entry");
1998        s.append(Id::new(5, 2), &[(b"sensor", b"b"), (b"reading", b"2")], l)
1999            .expect("the second entry");
2000        s.append(Id::new(700, 0), &[(b"sensor", b"c"), (b"reading", b"3")], l)
2001            .expect("the third entry");
2002        s.append(Id::new(900, 0), &[(b"other", b"x")], l)
2003            .expect("the fourth entry");
2004        assert!(s.delete(Id::new(5, 2)), "the second entry was there");
2005
2006        s.create_group(b"g1", Id::MIN, None);
2007        let g = s.group_mut(b"g1").expect("the group just made");
2008        let alice = g.consumer_or_create(b"alice", DELIVERED);
2009        g.deliver(alice, Id::new(5, 1), DELIVERED);
2010        g.deliver(alice, Id::new(700, 0), DELIVERED);
2011        // What the read path does once it knows the read handed something over,
2012        // and the reason alice has an active time in the payload while bob,
2013        // which was only ever declared, does not.
2014        g.touch(alice, DELIVERED, true);
2015        // A group over a stream something has been deleted from cannot work out
2016        // how far it has read, which is what a real server reports too.
2017        g.set_read(None);
2018        g.create_consumer(b"bob", MADE);
2019
2020        s.create_group(b"g2", Id::new(900, 0), None);
2021        s.group_mut(b"g2")
2022            .expect("the group just made")
2023            .set_read(None);
2024        s
2025    }
2026
2027    fn loaded(payload: &[u8]) -> Body {
2028        let (s, h, l, z) = limits();
2029        let all = Limits {
2030            set: &s,
2031            hash: &h,
2032            list: &l,
2033            zset: &z,
2034        };
2035        load(payload, all, 0).expect("a payload that should have loaded")
2036    }
2037
2038    /// The one that matters, because it is the only test here that did not get
2039    /// to choose both sides of the comparison.
2040    #[test]
2041    fn a_stream_a_real_redis_dumped_loads_the_same_stream() {
2042        let Body::Stream(back) = loaded(FROM_REDIS) else {
2043            panic!("a stream came back as something else");
2044        };
2045        assert_eq!(back, sample());
2046    }
2047
2048    /// The other direction, and the thing `MIGRATE` needs: what this writes has
2049    /// to be what that reads.
2050    ///
2051    /// Not byte for byte against `FROM_REDIS`, because that is type 27 and this
2052    /// writes type 21 on purpose. What is compared is the payload this writes
2053    /// against the same payload read back by the reader that already agrees with
2054    /// a real server.
2055    #[test]
2056    fn a_stream_this_wrote_reads_back_as_itself() {
2057        let s = sample();
2058        let rec = Record::new(Body::Stream(s.clone()), None);
2059        let payload = dump(&rec).expect("a stream has an RDB shape");
2060        assert_eq!(payload[0], T_STREAM_LISTPACKS_3);
2061        let Body::Stream(back) = loaded(&payload) else {
2062            panic!("a stream came back as something else");
2063        };
2064        assert_eq!(back, s);
2065    }
2066
2067    /// A stream everything has been deleted from is still a stream, and a real
2068    /// server dumps one and restores it rather than treating it as a deleted
2069    /// key the way it does an empty set.
2070    #[test]
2071    fn an_empty_stream_survives_the_round_trip() {
2072        let l = crate::stream::Limits::default();
2073        let mut s = Stream::new();
2074        s.append(Id::new(1, 1), &[(b"a", b"1")], l)
2075            .expect("the only entry");
2076        assert!(s.delete(Id::new(1, 1)), "the only entry was there");
2077
2078        let rec = Record::new(Body::Stream(s.clone()), None);
2079        let payload = dump(&rec).expect("a stream has an RDB shape");
2080        let Body::Stream(back) = loaded(&payload) else {
2081            panic!("a stream came back as something else");
2082        };
2083        assert_eq!(back, s);
2084        assert_eq!(back.len(), 0);
2085        assert_eq!(back.last_id(), Id::new(1, 1));
2086        assert_eq!(back.max_deleted_id(), Id::new(1, 1));
2087        assert_eq!(back.added(), 1);
2088    }
2089
2090    /// The same payload a real server writes for one, checked against the bytes
2091    /// rather than against our own writer, since an empty stream is the one
2092    /// shape where a count being allowed to be zero could quietly be wrong.
2093    #[test]
2094    fn an_empty_stream_from_a_real_redis_loads() {
2095        let payload: &[u8] = &[
2096            0x1b, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0x40, 0x64, 0x40, 0x64, 0, 0, 0, 0x0f, 0, 0x30,
2097            0xd1, 0x23, 0xc3, 0x38, 0xd9, 0xd6, 0x40,
2098        ];
2099        let Body::Stream(back) = loaded(payload) else {
2100            panic!("a stream came back as something else");
2101        };
2102        assert_eq!(back.len(), 0);
2103        assert_eq!(back.nodes(), 0);
2104        assert_eq!(back.last_id(), Id::new(1, 1));
2105        assert_eq!(back.added(), 1);
2106    }
2107
2108    /// An entry `XNACK` handed back sits in the ledger with nobody against it,
2109    /// and the format has room for that: the group's pending list is written
2110    /// before its consumers, so an entry no consumer claims is simply never
2111    /// claimed.
2112    #[test]
2113    fn a_pending_entry_nobody_holds_survives() {
2114        let l = crate::stream::Limits::default();
2115        let mut s = Stream::new();
2116        s.append(Id::new(1, 1), &[(b"a", b"1")], l)
2117            .expect("the only entry");
2118        s.create_group(b"g", Id::MIN, Some(0));
2119        let g = s.group_mut(b"g").expect("the group just made");
2120        let who = g.consumer_or_create(b"c", 100);
2121        g.deliver(who, Id::new(1, 1), 100);
2122        assert!(g.release(Id::new(1, 1), Retry::Keep), "it was delivered");
2123        assert_eq!(g.nacked_len(), 1);
2124
2125        let rec = Record::new(Body::Stream(s.clone()), None);
2126        let payload = dump(&rec).expect("a stream has an RDB shape");
2127        let Body::Stream(back) = loaded(&payload) else {
2128            panic!("a stream came back as something else");
2129        };
2130        assert_eq!(back, s);
2131        let g = back.group(b"g").expect("the group came back");
2132        assert_eq!(g.nacked_len(), 1);
2133        assert!(
2134            g.nack(Id::new(1, 1))
2135                .expect("still pending")
2136                .owner()
2137                .is_none()
2138        );
2139    }
2140
2141    /// Every way a stream payload can be wrong that the reader is meant to
2142    /// notice, each one made by breaking a payload that was fine.
2143    #[test]
2144    fn a_stream_payload_that_does_not_add_up_is_refused() {
2145        let rec = Record::new(Body::Stream(sample()), None);
2146        let good = dump(&rec).expect("a stream has an RDB shape");
2147        let body = &good[..good.len() - FOOTER];
2148        let (s, h, l, z) = limits();
2149        let all = Limits {
2150            set: &s,
2151            hash: &h,
2152            list: &l,
2153            zset: &z,
2154        };
2155
2156        // The type byte, changed to one nothing writes.
2157        let mut bent = body.to_vec();
2158        bent[0] = 26;
2159        assert_eq!(load(&seal(bent), all, 0).unwrap_err(), Bad::Format);
2160
2161        // A node key that is not sixteen bytes.
2162        let mut bent = body.to_vec();
2163        bent[2] = 15;
2164        assert_eq!(load(&seal(bent), all, 0).unwrap_err(), Bad::Format);
2165
2166        // Cut short anywhere is short everywhere.
2167        for cut in [1, 3, 20, 60, 120, 180] {
2168            let bent = body[..cut.min(body.len())].to_vec();
2169            assert_eq!(
2170                load(&seal(bent), all, 0).unwrap_err(),
2171                Bad::Format,
2172                "a payload cut at {cut} was accepted"
2173            );
2174        }
2175
2176        // Anything extra on the end, which is what a type 27 payload read as a
2177        // 21 would look like.
2178        let mut bent = body.to_vec();
2179        bent.push(0);
2180        assert_eq!(load(&seal(bent), all, 0).unwrap_err(), Bad::Format);
2181    }
2182
2183    /// The idempotency block is dropped rather than read, and the payload it is
2184    /// on still has to end exactly where it says it does.
2185    #[test]
2186    fn the_idempotency_block_is_read_past_and_not_kept() {
2187        let Body::Stream(back) = loaded(FROM_REDIS) else {
2188            panic!("a stream came back as something else");
2189        };
2190        // Nothing here holds a producer ID, so there is nothing to check on the
2191        // value. What the test is for is that the payload was consumed to the
2192        // last byte, which `load` only accepts when it was.
2193        assert_eq!(back.len(), 3);
2194
2195        // A producer with an ID recorded against it is a shape no `DUMP` has
2196        // been seen to write, and reading past it would be guessing.
2197        let body = &FROM_REDIS[..FROM_REDIS.len() - FOOTER];
2198        let at = body.len() - 3;
2199        assert_eq!(
2200            &body[at..],
2201            &[0, 0, 0],
2202            "the producer count and the two totals"
2203        );
2204        let mut bent = body.to_vec();
2205        bent[at] = 1;
2206        let (s, h, l, z) = limits();
2207        let all = Limits {
2208            set: &s,
2209            hash: &h,
2210            list: &l,
2211            zset: &z,
2212        };
2213        assert_eq!(load(&seal(bent), all, 0).unwrap_err(), Bad::Format);
2214    }
2215}