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