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}