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