yo_kv/listpack.rs
1//! The inline band: one packed blob, walked linearly.
2//!
3//! A collection under a hundred and twenty eight elements is stored as one blob
4//! with no index, walked from the front. This is Redis's listpack, in Redis's
5//! bytes, and it is the bottom rung of the size ladder in `05` section 4.
6//!
7//! # Why, and it is not the reason the spec gives
8//!
9//! `05` section 4.1 argues for this band on speed, citing L6: a dense positional
10//! structure probes in about 70 ns where a listpack walk costs 1 to 2 ns, a fifty
11//! times gap. Half of that reproduces and half of it does not. A walk here does
12//! cost 1 to 2 ns an element, which is L6's number. A probe in our element table
13//! costs 8 ns and not 70, so the gap it was being compared against is not there,
14//! and at eight members the blob is ahead on both of them: 8.2 ns against 7.0 to
15//! find a member that is present, 4.2 against 8.5 to find one that is not, 0.4 ns
16//! an element against 1.3 to walk the whole thing, and 268 ns against 261 to
17//! build it. At a hundred and twenty eight the table is six times faster to probe
18//! and the gap only widens from there. `benches/listpack.rs` is where those come
19//! from.
20//!
21//! The find numbers used to be much worse for the blob and have been re-measured
22//! twice, once when the scan stopped decoding every element it walked past and
23//! again when it stopped waiting for the header byte to work out where the next
24//! element starts. Both are written up on `scan_for` below. Neither changes the
25//! conclusion, because the conclusion never rested on them.
26//!
27//! What the band is actually for is memory, and there the gap is real and the
28//! other way round. With an eleven byte member the blob costs 13.1 bytes an
29//! element and the table costs 31.0, because the table pays twelve bytes of row
30//! and about eight of slot on top of the name while the blob pays an encoding
31//! byte and a back length. G8 asks for a set member to cost under three bytes
32//! plus its payload. The blob comes in at 2.1 and the table at 20. A server
33//! holding a million small hashes is holding them here or it is not holding them.
34//!
35//! The threshold is not ours to move anyway. `OBJECT ENCODING` has to say
36//! `listpack` for exactly the collections Redis says it for, so the promotion
37//! points are `hash-max-listpack-entries` and its neighbours whatever we would
38//! have picked. Worth knowing which argument is load bearing, though, because the
39//! speed one would have sent us looking for a faster walk and the real one sends
40//! us to the arena.
41//!
42//! It is byte compatible with a Redis listpack, not merely similar in spirit.
43//! `05` section 4.1 asks for that so an RDB export is a copy rather than a
44//! transcode, and it means the encodings, the header, the terminator and the
45//! back length are all Redis's. Every boundary here was read off `listpack.c`
46//! from the 8.10.1 tarball, which is the same version `yo-compat` pins, and the
47//! ones that are easy to get a byte wrong are pinned in the tests with Redis's
48//! own numbers written out.
49//!
50//! ```text
51//! +---------+--------+---------+-----+---------+------+
52//! | u32 len | u16 n | entry 0 | ... | entry k | 0xFF |
53//! +---------+--------+---------+-----+---------+------+
54//! total bytes, header included terminator
55//! ```
56//!
57//! An entry is an encoding byte, then its payload, then a back length, and the
58//! back length is what makes the walk work in both directions. A forward walk
59//! reads the encoding and steps over the payload. A backward walk reads the back
60//! length from its last byte leftward and steps over the whole entry, which is
61//! how `SPOP` reaches the end of a blob without walking it from the front, and
62//! how the downward scan cursor in [`crate::scan`] works in this band.
63//!
64//! # What is here and what is not
65//!
66//! Everything a collection needs to hold its elements: append, read by position,
67//! find, replace, insert and delete, all of them working on the blob in place.
68//! Every one of them is linear in the number of elements, on purpose, because
69//! the band is bounded and an index would cost more than it saved.
70//!
71//! Not the promotion policy. When a collection stops being small is a decision
72//! for the collection, since Redis makes it configurable per type and the
73//! thresholds have to keep matching `hash-max-listpack-entries` and its
74//! neighbours. This module holds elements and says how many bytes they cost.
75//!
76//! # The element codec is shared
77//!
78//! [`crate::chunk`] holds the same entries in a run with a cursor at each end
79//! rather than in a blob with a header, so it needs the encoding and not the
80//! container. `entry_len`, `write_entry`, `decode` and `read_backlen`
81//! are `pub(crate)` for that, and they are the only things a second holder of
82//! these bytes needs. Two copies of the fourteen encodings is how a list and a
83//! set end up disagreeing about what `SADD s 1` stored.
84
85use yo_common::{parse_i64, push_i64};
86
87/// Header is four bytes of total length and two of element count.
88const HDR: usize = 6;
89
90/// The terminator, which is also the encoding byte that means end.
91const END: u8 = 0xFF;
92
93/// What the element count field holds when the real count does not fit.
94///
95/// A collection in this band holds a hundred and twenty eight elements, so this
96/// never comes from us. It comes from a listpack somebody else wrote, and the
97/// answer to it is to walk and count.
98const COUNT_UNKNOWN: u16 = 65535;
99
100/// The largest count that fits in the field.
101const COUNT_MAX: usize = 65534;
102
103/// A packed blob of elements.
104///
105/// Owns its bytes today. When the arena lands under it the bytes move there and
106/// this becomes a view, which is why nothing here hands out a `Vec` or takes
107/// one back.
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Listpack {
110 bytes: Vec<u8>,
111}
112
113/// One element, as it is stored.
114///
115/// Redis stores a member that looks like an integer as an integer, so `SADD s 1`
116/// and `SADD s 01` are two different members that are stored two different ways.
117/// Handing back which one it was is what lets a caller answer `OBJECT ENCODING`
118/// and write an RDB without re-deciding it.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum Entry<'a> {
121 /// Stored as an integer, in one of the six integer encodings.
122 Int(i64),
123 /// Stored as bytes.
124 Str(&'a [u8]),
125}
126
127impl Entry<'_> {
128 /// The element as a client would see it, appended to `out`.
129 ///
130 /// An integer entry is formatted here, which is the same round trip Redis
131 /// does on the way out, because the client asked for a member and members
132 /// are strings on the wire.
133 pub fn write_to(&self, out: &mut Vec<u8>) {
134 match self {
135 Entry::Int(n) => push_i64(out, *n),
136 Entry::Str(s) => out.extend_from_slice(s),
137 }
138 }
139
140 /// How many bytes a client would see, without formatting anything.
141 ///
142 /// This is `HSTRLEN` and `STRLEN`, which both have to answer for a value
143 /// stored as an integer, and neither of them should have to write the digits
144 /// out to count them.
145 #[must_use]
146 #[inline]
147 pub fn byte_len(&self) -> usize {
148 match self {
149 Entry::Int(n) => yo_common::num::i64_len(*n),
150 Entry::Str(s) => s.len(),
151 }
152 }
153
154 /// The element as bytes, allocating only for an integer.
155 #[must_use]
156 pub fn to_vec(&self) -> Vec<u8> {
157 let mut out = Vec::new();
158 self.write_to(&mut out);
159 out
160 }
161}
162
163impl Default for Listpack {
164 fn default() -> Listpack {
165 Listpack::new()
166 }
167}
168
169impl Listpack {
170 /// An empty blob, which is a header and a terminator and nothing else.
171 #[must_use]
172 pub fn new() -> Listpack {
173 let mut bytes = Vec::with_capacity(HDR + 1 + 64);
174 bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, END]);
175 let mut lp = Listpack { bytes };
176 lp.set_total(HDR + 1);
177 lp
178 }
179
180 /// Take bytes somebody else wrote, after checking them.
181 ///
182 /// An RDB, a `.yo` file and a `RESTORE` all arrive this way, so the walk is
183 /// not optional. A blob that does not check out is refused whole rather than
184 /// read up to the bad entry, because half a collection is worse than none.
185 pub fn from_bytes(bytes: &[u8]) -> Result<Listpack, Malformed> {
186 if bytes.len() < HDR + 1 {
187 return Err(Malformed::Short);
188 }
189 let total = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
190 if total != bytes.len() {
191 return Err(Malformed::Length);
192 }
193 if bytes[total - 1] != END {
194 return Err(Malformed::Terminator);
195 }
196 // Walk it. Every entry has to decode, its back length has to agree with
197 // how long the entry actually was, and the last one has to land exactly
198 // on the terminator rather than past it.
199 let mut at = HDR;
200 let mut seen = 0usize;
201 while at < total - 1 {
202 let (_, len) = decode(&bytes[at..total - 1]).ok_or(Malformed::Entry)?;
203 let back = backlen_len(len);
204 if at + len + back > total - 1 {
205 return Err(Malformed::Entry);
206 }
207 if read_backlen(&bytes[..at + len + back]) != Some(len) {
208 return Err(Malformed::BackLength);
209 }
210 at += len + back;
211 seen += 1;
212 }
213 let count = u16::from_le_bytes([bytes[4], bytes[5]]);
214 if count != COUNT_UNKNOWN && count as usize != seen {
215 return Err(Malformed::Count);
216 }
217 Ok(Listpack {
218 bytes: bytes.to_vec(),
219 })
220 }
221
222 /// The bytes, ready to be written to a file or an RDB unchanged.
223 #[inline]
224 #[must_use]
225 pub fn as_bytes(&self) -> &[u8] {
226 &self.bytes
227 }
228
229 /// The entry region on its own, without the header or the terminator.
230 ///
231 /// [`crate::chunk`] holds entries in exactly this encoding, so promoting a
232 /// list out of the packed band is one copy of this slice rather than a walk
233 /// that re-encodes every element.
234 #[inline]
235 #[must_use]
236 pub(crate) fn entries(&self) -> &[u8] {
237 &self.bytes[HDR..self.bytes.len() - 1]
238 }
239
240 /// How many elements.
241 ///
242 /// The header answers this, which is why it is here and why it is kept
243 /// right on every edit. A blob from elsewhere with an unknown count is
244 /// walked instead, once, rather than being rejected.
245 #[must_use]
246 pub fn len(&self) -> usize {
247 let count = u16::from_le_bytes([self.bytes[4], self.bytes[5]]);
248 if count == COUNT_UNKNOWN {
249 self.iter().count()
250 } else {
251 count as usize
252 }
253 }
254
255 /// Whether there is nothing in it, which for Redis means it does not exist.
256 #[inline]
257 #[must_use]
258 pub fn is_empty(&self) -> bool {
259 self.bytes.len() == HDR + 1
260 }
261
262 /// What the blob costs, which is what it costs on disk too.
263 #[inline]
264 #[must_use]
265 pub fn byte_len(&self) -> usize {
266 self.bytes.len()
267 }
268
269 /// Every element, front to back.
270 pub fn iter(&self) -> Iter<'_> {
271 Iter {
272 bytes: &self.bytes,
273 at: HDR,
274 }
275 }
276
277 /// The element at a position, counting from the front.
278 ///
279 /// Linear, because the blob is linear. That is the whole design: at a
280 /// hundred and twenty eight elements the walk is cheaper than the index that
281 /// would have avoided it. From whichever end is nearer, though, because a
282 /// list in this band holds eight kilobytes and that is four hundred odd
283 /// entries rather than a hundred and twenty eight, and `LINDEX key -1` on
284 /// one of those should not read all of it.
285 #[must_use]
286 pub fn get(&self, index: usize) -> Option<Entry<'_>> {
287 let at = self.offset_of(index)?;
288 decode(&self.bytes[at..self.bytes.len() - 1]).map(|(e, _)| e)
289 }
290
291 /// A forward walk that starts at a byte offset a previous walk reported.
292 ///
293 /// The offset has to come from [`Iter::offset`] on a walk of this same blob,
294 /// taken while nothing has moved the bytes since. A stream node uses it to
295 /// resume a group read where the last one stopped instead of decoding the
296 /// whole node again, and it checks the entry it lands on before believing
297 /// it. An offset that is past the end gives an empty walk rather than
298 /// nonsense, and one that lands in the middle of an entry gives whatever
299 /// those bytes decode as, which is why the caller checks.
300 #[must_use]
301 pub fn iter_at(&self, byte: usize) -> Iter<'_> {
302 Iter {
303 bytes: &self.bytes,
304 at: byte.clamp(HDR, self.bytes.len().saturating_sub(1)),
305 }
306 }
307
308 /// A forward walk that starts at `index` rather than at the front.
309 ///
310 /// `LRANGE key 300 320` on a packed list would otherwise decode three
311 /// hundred entries and throw them away, which is what a `skip` on the walk
312 /// does.
313 pub fn iter_from(&self, index: usize) -> Iter<'_> {
314 Iter {
315 bytes: &self.bytes,
316 at: self
317 .offset_of(index)
318 .unwrap_or(self.bytes.len().saturating_sub(1)),
319 }
320 }
321
322 /// Every element, back to front.
323 ///
324 /// The trailing length on each entry is what makes this cost the same per
325 /// element as the forward walk. `LPOS` with a negative rank counts matches
326 /// from the tail and stops when it has enough, so walking forward and
327 /// keeping the answers would be the wrong shape as well as the wrong cost.
328 pub fn iter_back(&self) -> RevIter<'_> {
329 let entries = self.entries();
330 RevIter {
331 bytes: entries,
332 at: entries.len(),
333 }
334 }
335
336 /// The element at a position, counting from the back.
337 ///
338 /// Reads the back length of the last entry and steps left, which is what the
339 /// trailing length field is for and why a `RPOP` on a small list does not
340 /// walk the whole blob.
341 #[must_use]
342 pub fn get_back(&self, from_end: usize) -> Option<Entry<'_>> {
343 let mut end = self.bytes.len() - 1;
344 for _ in 0..=from_end {
345 // Stepping onto the header means the walk asked for more elements
346 // than are here. Without this the header's own bytes decode as an
347 // entry and the answer is nonsense rather than nothing.
348 if end <= HDR {
349 return None;
350 }
351 let len = read_backlen(&self.bytes[..end])?;
352 end = end.checked_sub(len + backlen_len(len))?;
353 }
354 if end < HDR {
355 return None;
356 }
357 decode(&self.bytes[end..self.bytes.len() - 1]).map(|(e, _)| e)
358 }
359
360 /// Where an element is, or nothing.
361 ///
362 /// `step` is what makes this work for a hash. A hash in this band is field,
363 /// value, field, value, so a field lookup is a find with a step of two, which
364 /// is the same trick Redis's `lpFind` plays and the reason a hash does not
365 /// need a second structure down here.
366 #[must_use]
367 pub fn find(&self, needle: &[u8], step: usize) -> Option<usize> {
368 self.find_parsed(needle, parse_i64(needle), step)
369 }
370
371 /// The same walk with the needle already parsed.
372 ///
373 /// Set algebra asks one member of one set about every other set, so the
374 /// parse would otherwise happen once per question about the same bytes. It
375 /// is also the only form that can answer about a member which was never
376 /// text: an intset holds the number and the digits do not exist anywhere
377 /// until somebody writes them.
378 #[must_use]
379 pub fn find_parsed(&self, needle: &[u8], as_int: Option<i64>, step: usize) -> Option<usize> {
380 scan_for(self.entries(), needle, as_int, step)
381 }
382
383 /// Every place an element is, front to back, handed over as they are found.
384 ///
385 /// `limit` is how many elements may be looked at with 0 meaning all of them,
386 /// `hit` says whether to carry on, and what comes back is how many elements
387 /// were looked at. The walk itself is `scan_each` below.
388 pub fn find_each(
389 &self,
390 needle: &[u8],
391 as_int: Option<i64>,
392 limit: usize,
393 hit: &mut dyn FnMut(usize) -> bool,
394 ) -> usize {
395 scan_each(self.entries(), needle, as_int, limit, hit)
396 }
397
398 /// The same from the back, with indexes counted from the last element.
399 pub fn find_each_back(
400 &self,
401 needle: &[u8],
402 as_int: Option<i64>,
403 limit: usize,
404 hit: &mut dyn FnMut(usize) -> bool,
405 ) -> usize {
406 scan_each_back(self.entries(), needle, as_int, limit, hit)
407 }
408
409 /// Add an element at the end.
410 pub fn push(&mut self, value: &[u8]) {
411 let at = self.bytes.len() - 1;
412 self.splice(at, 0, Some(value), 1);
413 }
414
415 /// Put an element in front of the one at `index`.
416 ///
417 /// An index at or past the end appends, which is what a sorted insert wants
418 /// when the new element sorts last and saves the caller a branch.
419 pub fn insert(&mut self, index: usize, value: &[u8]) {
420 let at = self.offset_of(index).unwrap_or(self.bytes.len() - 1);
421 self.splice(at, 0, Some(value), 1);
422 }
423
424 /// Overwrite the element at `index`, keeping its position.
425 ///
426 /// `HSET` on a field that is already there, and `ZADD` on a member whose
427 /// score has changed but whose place has not.
428 pub fn replace(&mut self, index: usize, value: &[u8]) -> bool {
429 let Some(at) = self.offset_of(index) else {
430 return false;
431 };
432 let old = self.entry_bytes(at);
433 self.splice(at, old, Some(value), 0);
434 true
435 }
436
437 /// Take out `count` elements starting at `index`.
438 ///
439 /// `HDEL` takes two, a field and its value, and it has to take them as one
440 /// edit or the blob is briefly a hash with an odd number of entries.
441 pub fn delete(&mut self, index: usize, count: usize) -> bool {
442 let Some(at) = self.offset_of(index) else {
443 return false;
444 };
445 let mut end = at;
446 let mut gone = 0usize;
447 while gone < count && end < self.bytes.len() - 1 {
448 end += self.entry_bytes(end);
449 gone += 1;
450 }
451 if gone == 0 {
452 return false;
453 }
454 self.splice(at, end - at, None, -(gone as i32));
455 true
456 }
457
458 /// Byte offset of the element at `index`, or nothing if it is past the end.
459 ///
460 /// Forward from the header or backward from the terminator, whichever is
461 /// the shorter walk. Going backward reads the length each entry carries
462 /// behind it, which is the same field [`Listpack::get_back`] reads and the
463 /// reason that field is there. A list in the packed band is eight kilobytes
464 /// and four hundred odd entries, not a hundred and twenty eight like the
465 /// other packed bands, so the half that this saves is worth having.
466 ///
467 /// [`Listpack::len`] is a header read at every size this crate builds: the
468 /// count field only stops being the count past sixty five thousand entries
469 /// and no band here comes close.
470 fn offset_of(&self, index: usize) -> Option<usize> {
471 let n = self.len();
472 if index >= n {
473 return None;
474 }
475 if index * 2 <= n {
476 let mut at = HDR;
477 for _ in 0..index {
478 at += self.entry_bytes(at);
479 }
480 return Some(at);
481 }
482 let mut end = self.bytes.len() - 1;
483 for _ in index..n {
484 let len = read_backlen(&self.bytes[..end])?;
485 end = end.checked_sub(len + backlen_len(len))?;
486 }
487 Some(end)
488 }
489
490 /// How many bytes the entry at `at` occupies, back length included.
491 fn entry_bytes(&self, at: usize) -> usize {
492 let (_, len) = decode(&self.bytes[at..self.bytes.len() - 1]).expect("our own blob decodes");
493 len + backlen_len(len)
494 }
495
496 /// The one edit primitive: drop some bytes, put some back, fix the header.
497 ///
498 /// Everything that changes the blob goes through here, so there is one place
499 /// that can leave the length or the count wrong.
500 ///
501 /// It writes the new entry into the blob rather than building it in a `Vec`
502 /// and handing that to `Vec::splice`. The `Vec` was a malloc and a free on
503 /// every `RPUSH`, `LPUSH`, `HSET`, `SADD` and `ZADD` that landed in the
504 /// packed band, which is most of them, for a buffer of a few dozen bytes
505 /// that never outlived the call. Making the hole first and then writing the
506 /// head, the payload and the back length straight into it costs one
507 /// `copy_within` of the tail, which `Vec::splice` was doing as well as the
508 /// allocation.
509 fn splice(&mut self, at: usize, remove: usize, insert: Option<&[u8]>, delta: i32) {
510 let mut buf = [0u8; 16];
511 // Both halves of the new entry, measured before anything moves. An
512 // integer entry is entirely in its head and has no payload, which is
513 // what `encode` says with its second answer.
514 let (head, body) = match insert {
515 Some(v) => {
516 let (head, payload) = encode(v, &mut buf);
517 (head, if payload { v } else { &[][..] })
518 }
519 None => (&[][..], &[][..]),
520 };
521 let entry = head.len() + body.len();
522 // A pure removal puts nothing back, so it has no back length either.
523 let add = if entry == 0 {
524 0
525 } else {
526 entry + backlen_len(entry)
527 };
528
529 // Size the hole before writing into it. Growing moves the tail rightward
530 // and shrinking moves it leftward, and `copy_within` is a `memmove` in
531 // both directions, so an overlap reads what it should either way.
532 let old = self.bytes.len();
533 match add.cmp(&remove) {
534 std::cmp::Ordering::Greater => {
535 self.bytes.resize(old + (add - remove), 0);
536 self.bytes.copy_within(at + remove..old, at + add);
537 }
538 std::cmp::Ordering::Less => {
539 self.bytes.copy_within(at + remove..old, at + add);
540 self.bytes.truncate(old - (remove - add));
541 }
542 std::cmp::Ordering::Equal => {}
543 }
544 if add > 0 {
545 let hole = &mut self.bytes[at..at + add];
546 hole[..head.len()].copy_from_slice(head);
547 hole[head.len()..entry].copy_from_slice(body);
548 write_backlen_into(&mut hole[entry..], entry);
549 }
550
551 let total = self.bytes.len();
552 self.set_total(total);
553 let count = i64::from(u16::from_le_bytes([self.bytes[4], self.bytes[5]]));
554 let count = usize::try_from(count + i64::from(delta)).unwrap_or(0);
555 let count = u16::try_from(count.min(COUNT_MAX)).expect("clamped to the field");
556 self.bytes[4..6].copy_from_slice(&count.to_le_bytes());
557 }
558
559 /// Write the total length into the header.
560 fn set_total(&mut self, total: usize) {
561 let total = u32::try_from(total).expect("the inline band is far under 4 GiB");
562 self.bytes[0..4].copy_from_slice(&total.to_le_bytes());
563 }
564}
565
566/// A forward walk.
567///
568/// Cloneable because a stream node holds several logical records inside one
569/// blob and a reader has to be able to keep a mark on where a record's fields
570/// started while it walks on to find where the record ends. Cloning one is
571/// copying a slice and an offset.
572#[derive(Debug, Clone)]
573pub struct Iter<'a> {
574 bytes: &'a [u8],
575 at: usize,
576}
577
578impl Iter<'_> {
579 /// Where in the blob the next element starts.
580 ///
581 /// Hand it back to [`Listpack::iter_at`] to carry on from here later.
582 #[inline]
583 #[must_use]
584 pub const fn offset(&self) -> usize {
585 self.at
586 }
587}
588
589impl<'a> Iterator for Iter<'a> {
590 type Item = Entry<'a>;
591
592 #[inline]
593 fn next(&mut self) -> Option<Entry<'a>> {
594 if self.at >= self.bytes.len() - 1 {
595 return None;
596 }
597 let (entry, len) = decode(&self.bytes[self.at..self.bytes.len() - 1])?;
598 self.at += len + backlen_len(len);
599 Some(entry)
600 }
601}
602
603/// A backward walk.
604#[derive(Debug, Clone)]
605pub struct RevIter<'a> {
606 bytes: &'a [u8],
607 at: usize,
608}
609
610impl<'a> Iterator for RevIter<'a> {
611 type Item = Entry<'a>;
612
613 #[inline]
614 fn next(&mut self) -> Option<Entry<'a>> {
615 if self.at == 0 {
616 return None;
617 }
618 let len = read_backlen(&self.bytes[..self.at])?;
619 let start = self.at.checked_sub(len + backlen_len(len))?;
620 let (entry, _) = decode(&self.bytes[start..self.at])?;
621 self.at = start;
622 Some(entry)
623 }
624}
625
626/// Why a blob from somewhere else was refused.
627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
628pub enum Malformed {
629 /// Shorter than an empty listpack.
630 Short,
631 /// The header's total length is not the length of what arrived.
632 Length,
633 /// It does not end in a terminator.
634 Terminator,
635 /// An entry's encoding is not one of the fourteen.
636 Entry,
637 /// An entry's back length disagrees with how long the entry is.
638 BackLength,
639 /// The header's element count is not how many elements are in it.
640 Count,
641}
642
643/// The encoding bytes for an element, written into `buf`.
644///
645/// Redis's fourteen encodings, and the choice between them is the same one
646/// `lpEncodeGetType` makes: an element that parses as an integer is stored as
647/// one, in the narrowest form that holds it, and everything else is stored as
648/// bytes with a length that is six, twelve or thirty two bits wide.
649///
650/// The flag says whether the element's own bytes follow the encoding. An integer
651/// is entirely inside its encoding, and that is where the memory target in G8
652/// comes from: a set of small integers costs two bytes an element, encoding and
653/// back length, with nothing else stored at all.
654fn encode<'b>(v: &[u8], buf: &'b mut [u8; 16]) -> (&'b [u8], bool) {
655 if let Some(n) = parse_i64(v) {
656 let head: &[u8] = match n {
657 0..=127 => {
658 buf[0] = n as u8;
659 &buf[..1]
660 }
661 -4096..=4095 => {
662 let u = (n as u16) & 0x1FFF;
663 buf[0] = 0xC0 | (u >> 8) as u8;
664 buf[1] = (u & 0xFF) as u8;
665 &buf[..2]
666 }
667 -32768..=32767 => {
668 buf[0] = 0xF1;
669 buf[1..3].copy_from_slice(&(n as i16).to_le_bytes());
670 &buf[..3]
671 }
672 -8_388_608..=8_388_607 => {
673 buf[0] = 0xF2;
674 buf[1..4].copy_from_slice(&(n as i32).to_le_bytes()[..3]);
675 &buf[..4]
676 }
677 -2_147_483_648..=2_147_483_647 => {
678 buf[0] = 0xF3;
679 buf[1..5].copy_from_slice(&(n as i32).to_le_bytes());
680 &buf[..5]
681 }
682 _ => {
683 buf[0] = 0xF4;
684 buf[1..9].copy_from_slice(&n.to_le_bytes());
685 &buf[..9]
686 }
687 };
688 return (head, false);
689 }
690 let head: &[u8] = match v.len() {
691 0..=63 => {
692 buf[0] = 0x80 | v.len() as u8;
693 &buf[..1]
694 }
695 64..=4095 => {
696 buf[0] = 0xE0 | (v.len() >> 8) as u8;
697 buf[1] = (v.len() & 0xFF) as u8;
698 &buf[..2]
699 }
700 _ => {
701 buf[0] = 0xF0;
702 buf[1..5].copy_from_slice(&(v.len() as u32).to_le_bytes());
703 &buf[..5]
704 }
705 };
706 (head, true)
707}
708
709/// How many bytes an entry for `v` takes, its back length included.
710///
711/// The chunk has to know before it writes, because a chunk that runs out of
712/// room halfway through an entry has no way to put itself back.
713#[inline]
714pub(crate) fn entry_len(v: &[u8]) -> usize {
715 let mut buf = [0u8; 16];
716 let (head, payload) = encode(v, &mut buf);
717 let len = head.len() + if payload { v.len() } else { 0 };
718 len + backlen_len(len)
719}
720
721/// Write one entry into `dst`, and say how many bytes it took.
722///
723/// `dst` must be at least [`entry_len`] long, which every caller knows because
724/// it asked first.
725#[inline]
726pub(crate) fn write_entry(dst: &mut [u8], v: &[u8]) -> usize {
727 let mut buf = [0u8; 16];
728 let (head, payload) = encode(v, &mut buf);
729 dst[..head.len()].copy_from_slice(head);
730 let mut at = head.len();
731 if payload {
732 dst[at..at + v.len()].copy_from_slice(v);
733 at += v.len();
734 }
735 at + write_backlen_into(&mut dst[at..], at)
736}
737
738/// Read one entry, and say how many bytes it took before its back length.
739///
740/// Inlined on purpose. It hands back a fat enum and a length, and left out of
741/// line that pair goes through memory once per element, which is most of what a
742/// walk costs.
743#[inline]
744pub(crate) fn decode(b: &[u8]) -> Option<(Entry<'_>, usize)> {
745 let first = *b.first()?;
746 // A string encoding's payload starts after its length, so both arms below
747 // hand back the same pair and the caller does not care which it was.
748 let (at, len) = match first {
749 0x00..=0x7F => return Some((Entry::Int(i64::from(first)), 1)),
750 0x80..=0xBF => (1, (first & 0x3F) as usize),
751 0xC0..=0xDF => {
752 let raw = (u16::from(first & 0x1F) << 8) | u16::from(*b.get(1)?);
753 // Thirteen bits, signed, so the top bit of the thirteen is the sign.
754 let n = if raw & 0x1000 != 0 {
755 i64::from(raw) - 8192
756 } else {
757 i64::from(raw)
758 };
759 return Some((Entry::Int(n), 2));
760 }
761 0xE0..=0xEF => {
762 let lo = *b.get(1)?;
763 (2, (usize::from(first & 0x0F) << 8) | usize::from(lo))
764 }
765 0xF0 => {
766 let n = u32::from_le_bytes([*b.get(1)?, *b.get(2)?, *b.get(3)?, *b.get(4)?]);
767 (5, n as usize)
768 }
769 0xF1 => {
770 let n = i16::from_le_bytes([*b.get(1)?, *b.get(2)?]);
771 return Some((Entry::Int(i64::from(n)), 3));
772 }
773 0xF2 => {
774 // Twenty four bits, sign extended by putting them in the top of a
775 // thirty two bit word and shifting back down.
776 let n = i32::from_le_bytes([0, *b.get(1)?, *b.get(2)?, *b.get(3)?]) >> 8;
777 return Some((Entry::Int(i64::from(n)), 4));
778 }
779 0xF3 => {
780 let n = i32::from_le_bytes([*b.get(1)?, *b.get(2)?, *b.get(3)?, *b.get(4)?]);
781 return Some((Entry::Int(i64::from(n)), 5));
782 }
783 0xF4 => {
784 let mut w = [0u8; 8];
785 w.copy_from_slice(b.get(1..9)?);
786 return Some((Entry::Int(i64::from_le_bytes(w)), 9));
787 }
788 // 0xF5 to 0xFE are unused by Redis, and 0xFF is the terminator, which
789 // the caller has already stopped before.
790 _ => return None,
791 };
792 let s = b.get(at..at + len)?;
793 Some((Entry::Str(s), at + len))
794}
795
796/// Eight bytes of `s` starting at `at`, as a number.
797///
798/// The caller has already checked that they are there, so the `try_into` cannot
799/// fail and the compiler knows it, which is what keeps this to one unaligned
800/// load on every target this runs on.
801#[inline(always)]
802fn word(s: &[u8], at: usize) -> u64 {
803 u64::from_le_bytes(s[at..at + 8].try_into().expect("eight bytes"))
804}
805
806/// The needle of a scan, with everything that does not change worked out once.
807///
808/// This exists because of what the generated code looked like without it. The
809/// comparison started as a length check, a first byte check and then `a == b`,
810/// which is a call to `memcmp`, and on the workload that actually matters none
811/// of the first two filter anything: list elements are overwhelmingly a fixed
812/// shape with a varying tail, so a million of them called `element:00000000`
813/// through `element:00999999` are all the same length and all start with the
814/// same letter. Every element paid for the call.
815///
816/// Comparing whole words instead fixes that, and eight bytes from each end
817/// covers any length up to sixteen exactly, because at that length the two
818/// windows overlap and between them cover the whole value. Longer than sixteen
819/// and the two words are a filter in front of the call rather than a
820/// replacement for it, which is fine, since a value agreeing on both ends and
821/// differing in the middle is rare enough to be worth a `memcmp` when it turns
822/// up.
823///
824/// Working the two words out here rather than in the loop is not a
825/// micro-optimisation, it is two loads an element. Left inline the compiler
826/// reloads them from the needle every time round, because the calls further down
827/// the body could have written to it as far as it knows, and it has no way to
828/// prove otherwise.
829struct Needle<'a> {
830 bytes: &'a [u8],
831 /// The length, which is the first thing every element is rejected on.
832 len: usize,
833 /// The first and last eight bytes, both zero and never looked at when the
834 /// value is shorter than eight bytes.
835 head: u64,
836 tail: u64,
837 /// What the value is as a number, if it is one. An element stored under an
838 /// integer encoding can only match this.
839 num: Option<i64>,
840}
841
842impl<'a> Needle<'a> {
843 fn new(bytes: &'a [u8], num: Option<i64>) -> Needle<'a> {
844 let len = bytes.len();
845 let wide = len >= 8;
846 Needle {
847 bytes,
848 len,
849 head: if wide { word(bytes, 0) } else { 0 },
850 tail: if wide { word(bytes, len - 8) } else { 0 },
851 num,
852 }
853 }
854
855 /// Whether a string payload of the same length is this value.
856 ///
857 /// `always` rather than `inline`, and it is worth saying why, because this
858 /// is the difference between three and a half nanoseconds an element and one
859 /// and a half. Left to its own judgement the compiler kept this out of line,
860 /// so the scan below made a call per element, spilled around it, and paid
861 /// more to set the arguments up than the comparison itself costs.
862 ///
863 /// Every length here is taken from `p` and not from the needle, which reads
864 /// like a pointless difference and is not. The caller has already checked
865 /// that they are equal, but nothing in the types says so, so a bound written
866 /// in terms of the needle is a bound the compiler has to check against `p`
867 /// all over again, and it emitted a second comparison and a panic landing pad
868 /// on the hot path to do it. Written this way the check that lets the first
869 /// word be read is the same check that says the value is long enough to have
870 /// two words at all.
871 #[inline(always)]
872 fn is(&self, p: &[u8]) -> bool {
873 debug_assert_eq!(p.len(), self.len, "the caller checks the length first");
874 let n = p.len();
875 if n < 8 {
876 return p == self.bytes;
877 }
878 word(p, 0) == self.head
879 && word(p, n - 8) == self.tail
880 && (n <= 16 || p[8..n - 8] == self.bytes[8..n - 8])
881 }
882}
883
884/// Walk a run of entries looking for `needle`, and say which one it was.
885///
886/// `b` is entries and nothing else, which is what a chunk holds and what a
887/// listpack holds between its header and its terminator, so both callers get the
888/// same walk. `step` is the hash trick: a field lookup over field, value, field,
889/// value is a find with a step of two.
890///
891/// # Why this is not the obvious walk
892///
893/// The obvious walk is `self.iter().position(|e| e.is(needle, as_int))`, which
894/// is what this was, and it costs about 6.7 nanoseconds an element. That is
895/// twenty seven cycles to answer "are these bytes those bytes", and almost none
896/// of it is the comparison. Every step ran the whole of [`decode`], which is a
897/// fourteen way match with a bounds checked read per header byte, built an
898/// [`Entry`] out of what it found, handed that back through the iterator, and
899/// only then compared. On a million element `LINSERT` that is three and a third
900/// milliseconds of work to reach an insert that takes two hundred and eighty
901/// five nanoseconds.
902///
903/// So the walk below never builds an `Entry` and never reads a payload it is not
904/// about to compare. The header alone says how long the entry is, the length
905/// rejects most elements with one comparison, and what gets past that is
906/// compared as two words rather than as a call. See [`Needle`] for that half of
907/// it.
908///
909/// The integer arm is the rare one and it is deliberately left to `decode`. It
910/// is four sign extensions of different widths, getting one of them subtly wrong
911/// is exactly the kind of bug that hides for a year, and having a second copy of
912/// them here to save a call on a path that is cold in every list workload is a
913/// bad trade.
914///
915/// # What was left on the table by the first version of that
916///
917/// The walk above got to about 2.3 nanoseconds an element and stopped, and it
918/// stopped there because of one instruction. Stepping to the next element is
919/// `at += len + 2`, `len` comes out of the encoding byte that was just loaded,
920/// and the loaded value is therefore in the way of working out where the next
921/// load goes. Every element paid a load latency plus the arithmetic stacked on
922/// it before the element after it could start, which is about eight cycles, and
923/// nothing else in the loop mattered because everything else could run while that
924/// chain was resolving. It is not a throughput problem and no amount of removing
925/// instructions from the body would have touched it.
926///
927/// The way out is that on the path that matters the length is already known. An
928/// element is only compared when its length equals the needle's, and the needle's
929/// length has been in a register since before the loop started, so on that path
930/// the step can be written in terms of the needle instead of in terms of the
931/// byte that was just read. The next element's address is then worked out while
932/// this one is still being compared, and the loop halves to about four cycles an
933/// element. That is why the length test is a branch of its own below rather than
934/// the first half of the comparison, which is the shape it had and reads more
935/// naturally.
936///
937/// It only pays when the lengths do match, and on a list they either all match
938/// or none of them do, which is the same property the two word comparison in
939/// [`Needle`] leans on. A million element `LINSERT` went from 1.17 ms to 456 us
940/// on it and the bare search from 2.37 ms to 904 us, both on an M4.
941#[inline]
942pub(crate) fn scan_for(b: &[u8], needle: &[u8], as_int: Option<i64>, step: usize) -> Option<usize> {
943 let mut got = None;
944 let needle = Needle::new(needle, as_int);
945 // Four walks out of one body, and both const parameters earn their keep the
946 // same way. A list finds by stepping over every element and a hash steps
947 // over every other one, and carrying a counter for a step that is always one
948 // costs four instructions and a branch on the hottest loop in `LINSERT`.
949 // `LIMITED` is the same argument for `LPOS`'s `MAXLEN`, which every other
950 // caller passes as no limit at all. As constants both fold away entirely.
951 if step <= 1 {
952 walk::<true, false, _>(b, &needle, 1, 0, &mut |at| {
953 got = Some(at);
954 false
955 });
956 } else {
957 walk::<false, false, _>(b, &needle, step, 0, &mut |at| {
958 got = Some(at);
959 false
960 });
961 }
962 got
963}
964
965/// Walk a run of entries handing back every `needle` in it, front to back.
966///
967/// This is [`scan_for`] without the stop on the first one, which is `LPOS` and
968/// `LREM` rather than `LINSERT`. `hit` is given each index as it is found and
969/// says whether to keep going, so a `COUNT` stops the walk where it is reached
970/// rather than after reading the rest of the list. `limit` is how many elements
971/// may be looked at, with 0 meaning no limit, which is `MAXLEN`.
972///
973/// What comes back is how many elements were looked at, which is what a caller
974/// spanning several runs of entries needs in order to carry one `MAXLEN` budget
975/// across all of them.
976#[inline]
977pub(crate) fn scan_each(
978 b: &[u8],
979 needle: &[u8],
980 as_int: Option<i64>,
981 limit: usize,
982 mut hit: &mut dyn FnMut(usize) -> bool,
983) -> usize {
984 let needle = Needle::new(needle, as_int);
985 if limit == 0 {
986 walk::<true, false, _>(b, &needle, 1, 0, &mut hit)
987 } else {
988 walk::<true, true, _>(b, &needle, 1, limit, &mut hit)
989 }
990}
991
992/// The same walk from the other end, with indexes counted from the back.
993///
994/// The index handed to `hit` is 0 for the last element, 1 for the one before
995/// it and so on, because this does not know how many elements are in front of
996/// the run it was given and the caller does. `LPOS` with a negative rank is the
997/// only thing that wants this, and it wants it because a rank of -1 has to find
998/// the last match without reading past it.
999#[inline]
1000pub(crate) fn scan_each_back(
1001 b: &[u8],
1002 needle: &[u8],
1003 as_int: Option<i64>,
1004 limit: usize,
1005 mut hit: &mut dyn FnMut(usize) -> bool,
1006) -> usize {
1007 let needle = Needle::new(needle, as_int);
1008 if limit == 0 {
1009 walk_back::<false, _>(b, &needle, 0, &mut hit)
1010 } else {
1011 walk_back::<true, _>(b, &needle, limit, &mut hit)
1012 }
1013}
1014
1015/// How long the entry at `at` is, split into its header and its payload, and
1016/// whether the payload is text.
1017///
1018/// Every encoding except the short string, which the walk below handles itself.
1019/// An integer encoding is entirely header, so its payload length is zero and it
1020/// can never match a string of any length.
1021///
1022/// Out of line on purpose, and it is not because this is rare in general, it is
1023/// because of what having it inline did to the loop it was in. Thirteen arms
1024/// need registers, and the register allocator paid for them by spilling the
1025/// needle's two comparison words to the stack and reloading them on every single
1026/// element, including the overwhelming majority that never reach this function
1027/// at all. A call on the encodings a list does not use is a good trade for two
1028/// loads on the ones it does.
1029#[inline(never)]
1030fn head_at(b: &[u8], at: usize) -> Option<(usize, usize, bool)> {
1031 let tag = *b.get(at)?;
1032 Some(match tag {
1033 0x00..=0x7F => (1, 0, false),
1034 0x80..=0xBF => (1, (tag & 0x3F) as usize, true),
1035 0xC0..=0xDF => (2, 0, false),
1036 0xE0..=0xEF => (
1037 2,
1038 (usize::from(tag & 0x0F) << 8) | usize::from(*b.get(at + 1)?),
1039 true,
1040 ),
1041 0xF0 => (
1042 5,
1043 u32::from_le_bytes([
1044 *b.get(at + 1)?,
1045 *b.get(at + 2)?,
1046 *b.get(at + 3)?,
1047 *b.get(at + 4)?,
1048 ]) as usize,
1049 true,
1050 ),
1051 0xF1 => (3, 0, false),
1052 0xF2 => (4, 0, false),
1053 0xF3 => (5, 0, false),
1054 0xF4 => (9, 0, false),
1055 // 0xF5 to 0xFE are unused by Redis and 0xFF is the terminator, which is
1056 // not in this run. Either way there is nothing after it that can be read
1057 // as an entry.
1058 _ => return None,
1059 })
1060}
1061
1062/// The scan itself, with `EVERY` saying whether `step` is worth carrying and
1063/// `LIMITED` whether `limit` is.
1064///
1065/// `hit` is a generic and it was a `&mut dyn` first, on the reasoning that it is
1066/// only reached on a match and so is called a handful of times against a million
1067/// iterations of the body around it. That reasoning is right about a long list
1068/// and wrong about a short one. A blob holds at most a hundred and twenty eight
1069/// elements and the thing it does all day is find one that is there, so the call
1070/// is not one in a million, it is one in four, and it cost 12 percent on the
1071/// eight member row in `benches/listpack.rs`. As a generic the sink for a find
1072/// inlines back to a store and a break, which is what the loop had before it took
1073/// a sink at all. It is two copies of this function and not more, because every
1074/// caller that wants every match already goes through a `&mut dyn` of its own.
1075///
1076/// What comes back is how many elements were looked at, which for the single
1077/// answer case is not interesting and folds away with everything else.
1078#[inline]
1079fn walk<const EVERY: bool, const LIMITED: bool, F: FnMut(usize) -> bool>(
1080 b: &[u8],
1081 needle: &Needle<'_>,
1082 step: usize,
1083 limit: usize,
1084 hit: &mut F,
1085) -> usize {
1086 let want = needle.len;
1087 let mut at = 0usize;
1088 let mut idx = 0usize;
1089 // Counted down rather than `idx % step`, because `step` is a runtime value
1090 // and the remainder compiles to a real division on every element. That is
1091 // twenty cycles to answer a question about a two element cycle, and it cost
1092 // more than the comparison it was guarding.
1093 let mut until = 0usize;
1094 while at < b.len() {
1095 if LIMITED && idx == limit {
1096 break;
1097 }
1098 let tag = b[at];
1099 // The one encoding a list is actually made of, given a path with nothing
1100 // in it. A string of sixty three bytes or less is a one byte header, and
1101 // its total is at most sixty four so its back length is one byte too,
1102 // which means stepping to the next element is an add of a number that
1103 // came straight out of the tag. No second read, no table, and one branch
1104 // instead of the five the general match below needs.
1105 //
1106 // The general walk is not slow because of how many instructions it runs.
1107 // It is slow because of how many of its branches are taken: a core
1108 // retires about one taken branch a cycle, and a five way tag match that
1109 // ends in a four way back length match spends more time being fetched
1110 // than being executed. Straightening the common case out is worth more
1111 // than anything done to the comparison inside it.
1112 if EVERY && tag & 0xC0 == 0x80 {
1113 let len = (tag & 0x3F) as usize;
1114 if len != want {
1115 at += len + 2;
1116 idx += 1;
1117 continue;
1118 }
1119 let Some(p) = b.get(at + 1..at + 1 + want) else {
1120 break;
1121 };
1122 let matched = needle.is(p);
1123 // `want` rather than `len`, which are the same number here and are
1124 // not the same instruction: one of them is in the way of the next
1125 // load and the other has been in a register all along. It halves the
1126 // loop. `scan_for` above has the long version.
1127 at += want + 2;
1128 idx += 1;
1129 if matched && !hit(idx - 1) {
1130 break;
1131 }
1132 continue;
1133 }
1134 let Some((hdr, len, text)) = head_at(b, at) else {
1135 break;
1136 };
1137 let total = hdr + len;
1138 let mut matched = false;
1139 if EVERY || until == 0 {
1140 matched = if text {
1141 let Some(p) = b.get(at + hdr..at + total) else {
1142 break;
1143 };
1144 len == want && needle.is(p)
1145 } else {
1146 // `at` is inside `b`, which is the loop condition, so this
1147 // slice is always there.
1148 needle
1149 .num
1150 .is_some_and(|v| matches!(decode(&b[at..]), Some((Entry::Int(n), _)) if n == v))
1151 };
1152 if !EVERY {
1153 until = step;
1154 }
1155 }
1156 if !EVERY {
1157 until -= 1;
1158 }
1159 at += total + backlen_len(total);
1160 idx += 1;
1161 if matched && !hit(idx - 1) {
1162 break;
1163 }
1164 }
1165 idx
1166}
1167
1168/// The same walk from the back, which only a list ever asks for.
1169///
1170/// No `EVERY`, because the caller that steps is a hash and a hash has no back to
1171/// walk from. The step from one entry to the one in front of it is the back
1172/// length that ends where this entry starts, and reading it is a byte and a
1173/// branch in the case that covers everything up to a hundred and twenty seven
1174/// bytes, which is every element the fast path in [`walk`] handles and then
1175/// some. Longer than that and it falls back to [`read_backlen`], which is the
1176/// leftward walk.
1177#[inline]
1178fn walk_back<const LIMITED: bool, F: FnMut(usize) -> bool>(
1179 b: &[u8],
1180 needle: &Needle<'_>,
1181 limit: usize,
1182 hit: &mut F,
1183) -> usize {
1184 let want = needle.len;
1185 let mut at = b.len();
1186 let mut idx = 0usize;
1187 while at > 0 {
1188 if LIMITED && idx == limit {
1189 break;
1190 }
1191 // A back length of one byte has its top bit clear and holds the whole
1192 // value, which is what `write_backlen_into` writes and what makes this
1193 // one load rather than a loop.
1194 let last = b[at - 1];
1195 let (total, blen) = if last < 128 {
1196 (usize::from(last), 1)
1197 } else {
1198 let Some(t) = read_backlen(&b[..at]) else {
1199 break;
1200 };
1201 (t, backlen_len(t))
1202 };
1203 let Some(start) = at.checked_sub(total + blen) else {
1204 break;
1205 };
1206 let tag = b[start];
1207 let matched = if tag & 0xC0 == 0x80 {
1208 let len = usize::from(tag & 0x3F);
1209 match b.get(start + 1..start + 1 + len) {
1210 Some(p) => len == want && needle.is(p),
1211 None => break,
1212 }
1213 } else {
1214 match head_at(b, start) {
1215 Some((hdr, len, true)) => match b.get(start + hdr..start + hdr + len) {
1216 Some(p) => len == want && needle.is(p),
1217 None => break,
1218 },
1219 Some((_, _, false)) => needle.num.is_some_and(
1220 |v| matches!(decode(&b[start..]), Some((Entry::Int(n), _)) if n == v),
1221 ),
1222 None => break,
1223 }
1224 };
1225 at = start;
1226 idx += 1;
1227 if matched && !hit(idx - 1) {
1228 break;
1229 }
1230 }
1231 idx
1232}
1233
1234/// How many bytes the back length of an entry of `len` bytes takes.
1235///
1236/// Seven bits a byte, so the boundaries are `2^7 - 1`, `2^14 - 1` and so on, and
1237/// they are Redis's `lpEncodeBacklenBytes` boundaries exactly. They are checked
1238/// against the real ones in the tests rather than taken on trust, because a
1239/// listpack whose back lengths are a byte out is one Redis walks off the end of.
1240#[inline]
1241pub(crate) const fn backlen_len(len: usize) -> usize {
1242 if len <= 127 {
1243 1
1244 } else if len <= 16383 {
1245 2
1246 } else if len <= 2_097_151 {
1247 3
1248 } else if len <= 268_435_455 {
1249 4
1250 } else {
1251 5
1252 }
1253}
1254
1255/// Write the back length for an entry of `len` bytes, and say how long it was.
1256///
1257/// The first byte holds the high seven bits and every later byte has its top bit
1258/// set, which is what lets it be read from the right hand end leftward.
1259#[inline]
1260fn write_backlen_into(dst: &mut [u8], len: usize) -> usize {
1261 let n = backlen_len(len);
1262 // High seven bits first, then seven at a time downward, and every byte
1263 // after the first carries the continuation bit that stops the leftward
1264 // walk from running past the front of the entry.
1265 for (i, b) in dst[..n].iter_mut().enumerate() {
1266 let shift = 7 * (n - 1 - i);
1267 *b = ((len >> shift) & 127) as u8 | if i == 0 { 0 } else { 128 };
1268 }
1269 n
1270}
1271
1272/// Read a back length that ends at the last byte of `upto`.
1273///
1274/// Walks left while the top bit is set, seven bits at a time, which is the
1275/// mirror image of how it was written.
1276pub(crate) fn read_backlen(upto: &[u8]) -> Option<usize> {
1277 let mut val = 0usize;
1278 let mut shift = 0u32;
1279 let mut at = upto.len().checked_sub(1)?;
1280 loop {
1281 let b = *upto.get(at)?;
1282 val |= usize::from(b & 127) << shift;
1283 if b & 128 == 0 {
1284 return Some(val);
1285 }
1286 shift += 7;
1287 if shift > 28 {
1288 return None;
1289 }
1290 at = at.checked_sub(1)?;
1291 }
1292}
1293
1294#[cfg(test)]
1295mod tests {
1296 use super::*;
1297
1298 fn of(members: &[&[u8]]) -> Listpack {
1299 let mut lp = Listpack::new();
1300 for m in members {
1301 lp.push(m);
1302 }
1303 lp
1304 }
1305
1306 fn all(lp: &Listpack) -> Vec<Vec<u8>> {
1307 lp.iter().map(|e| e.to_vec()).collect()
1308 }
1309
1310 /// Every edit used to build the new entry in a `Vec` and throw it away, so
1311 /// a blob with room in it still paid a malloc and a free per write. These
1312 /// three shapes are the whole of `splice`: an edit that grows the blob, one
1313 /// that shrinks it, and one that leaves it the same size. None of them may
1314 /// touch the allocator once the blob's own buffer is big enough.
1315 #[test]
1316 fn editing_a_blob_that_has_room_does_not_allocate() {
1317 let mut lp = Listpack::new();
1318 for i in 0..200 {
1319 lp.push(format!("member:{i:04}").as_bytes());
1320 }
1321 // Down to a hundred and back up, so the buffer is at its high water
1322 // mark and nothing below measures growth.
1323 lp.delete(100, 100);
1324 for i in 0..100 {
1325 lp.push(format!("member:{i:04}").as_bytes());
1326 }
1327
1328 // Built up here rather than inside the count, because `format!` is an
1329 // allocation of the test's own and would drown out what is measured.
1330 let names: Vec<(Vec<u8>, Vec<u8>)> = (0..100)
1331 .map(|i| {
1332 (
1333 format!("other:{i:05}").into_bytes(),
1334 format!("member:{i:04}").into_bytes(),
1335 )
1336 })
1337 .collect();
1338
1339 let (_, allocs) = crate::tally::counted(|| {
1340 for (i, (other, original)) in names.iter().enumerate() {
1341 // Same length, so the blob does not change size at all.
1342 lp.replace(i, other);
1343 // Shorter, then back to the original length.
1344 lp.replace(i, b"x");
1345 lp.replace(i, original);
1346 }
1347 });
1348 assert_eq!(allocs, 0, "editing allocated {allocs} times");
1349 assert_eq!(lp.len(), 200);
1350 assert_eq!(lp.get(0), Some(Entry::Str(b"member:0000")));
1351 }
1352
1353 #[test]
1354 fn an_empty_blob_is_a_header_and_a_terminator() {
1355 let lp = Listpack::new();
1356 assert!(lp.is_empty());
1357 assert_eq!(lp.len(), 0);
1358 assert_eq!(lp.byte_len(), 7);
1359 assert_eq!(lp.as_bytes(), &[7, 0, 0, 0, 0, 0, 0xFF]);
1360 assert_eq!(lp.get(0), None);
1361 assert_eq!(lp.iter().count(), 0);
1362 }
1363
1364 #[test]
1365 fn what_goes_in_comes_out_in_order() {
1366 let lp = of(&[b"one", b"two", b"three"]);
1367 assert_eq!(lp.len(), 3);
1368 assert_eq!(
1369 all(&lp),
1370 vec![b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]
1371 );
1372 assert_eq!(lp.get(1), Some(Entry::Str(b"two")));
1373 assert_eq!(lp.get(3), None);
1374 }
1375
1376 /// Redis stores a member that parses as an integer as an integer, and the
1377 /// narrowest one that holds it. Every boundary is here because every one of
1378 /// them is a different encoding byte, and a byte wrong is an RDB Redis will
1379 /// not read.
1380 #[test]
1381 fn an_integer_takes_the_narrowest_encoding_that_holds_it() {
1382 for (text, first, len) in [
1383 (&b"0"[..], 0x00u8, 1usize),
1384 (b"127", 0x7F, 1),
1385 (b"128", 0xC0, 2),
1386 (b"4095", 0xCF, 2),
1387 (b"-4096", 0xD0, 2),
1388 (b"-1", 0xDF, 2),
1389 (b"4096", 0xF1, 3),
1390 (b"-4097", 0xF1, 3),
1391 (b"32767", 0xF1, 3),
1392 (b"32768", 0xF2, 4),
1393 (b"8388607", 0xF2, 4),
1394 (b"8388608", 0xF3, 5),
1395 (b"2147483647", 0xF3, 5),
1396 (b"2147483648", 0xF4, 9),
1397 (b"-9223372036854775808", 0xF4, 9),
1398 ] {
1399 let lp = of(&[text]);
1400 let at = HDR;
1401 assert_eq!(
1402 lp.as_bytes()[at],
1403 first,
1404 "{} took the wrong encoding",
1405 String::from_utf8_lossy(text)
1406 );
1407 assert_eq!(lp.byte_len(), HDR + len + 1 + 1, "{first:#x}");
1408 assert_eq!(
1409 lp.get(0),
1410 Some(Entry::Int(parse_i64(text).expect("a number"))),
1411 "{first:#x}"
1412 );
1413 assert_eq!(all(&lp), vec![text.to_vec()], "and it formats back");
1414 }
1415 }
1416
1417 /// What `string2ll` refuses is a string, and it has to stay one, because
1418 /// `SADD s 01` and `SADD s 1` are two different members to Redis.
1419 #[test]
1420 fn something_that_only_looks_like_a_number_stays_a_string() {
1421 for text in [&b"01"[..], b"+1", b"1 ", b" 1", b"1.0", b"-0", b""] {
1422 let lp = of(&[text]);
1423 assert_eq!(
1424 lp.get(0),
1425 Some(Entry::Str(text)),
1426 "{}",
1427 String::from_utf8_lossy(text)
1428 );
1429 }
1430 }
1431
1432 #[test]
1433 fn a_string_takes_the_narrowest_length_field() {
1434 for (len, first, head) in [(1usize, 0x81u8, 1usize), (63, 0xBF, 1), (64, 0xE0, 2)] {
1435 let s = vec![b'x'; len];
1436 let lp = of(&[&s]);
1437 assert_eq!(lp.as_bytes()[HDR], first, "length {len}");
1438 assert_eq!(
1439 lp.byte_len(),
1440 HDR + head + len + backlen_len(head + len) + 1
1441 );
1442 assert_eq!(lp.get(0), Some(Entry::Str(&s[..])));
1443 }
1444 }
1445
1446 /// Over 4095 bytes the length field is the thirty two bit one, and the entry
1447 /// is long enough that its own back length needs two bytes, which is the
1448 /// other boundary in the same test.
1449 #[test]
1450 fn a_long_string_takes_the_wide_length_and_a_wide_back_length() {
1451 let s = vec![b'y'; 5000];
1452 let lp = of(&[&s, b"after"]);
1453 assert_eq!(lp.as_bytes()[HDR], 0xF0);
1454 assert_eq!(backlen_len(5005), 2);
1455 assert_eq!(lp.get(0), Some(Entry::Str(&s[..])));
1456 assert_eq!(lp.get(1), Some(Entry::Str(b"after")));
1457 assert_eq!(lp.get_back(0), Some(Entry::Str(b"after")));
1458 assert_eq!(lp.get_back(1), Some(Entry::Str(&s[..])));
1459 }
1460
1461 #[test]
1462 fn the_back_length_reads_the_same_as_it_was_written() {
1463 for len in [1usize, 127, 128, 16382, 16383, 16384, 2_097_150, 2_097_151] {
1464 let mut buf = [0u8; 5];
1465 let n = write_backlen_into(&mut buf, len);
1466 let out = &buf[..n];
1467 assert_eq!(out.len(), backlen_len(len), "length {len}");
1468 assert_eq!(read_backlen(out), Some(len), "length {len}");
1469 }
1470 }
1471
1472 /// Reading itself back is not enough, because a wrong boundary is wrong
1473 /// consistently. These are Redis's `lpEncodeBacklenBytes` boundaries written
1474 /// out from `listpack.c` at 8.10.1, which is the version `yo-compat` pins,
1475 /// and they are what a listpack from an RDB will have been written with.
1476 #[test]
1477 fn the_back_length_boundaries_are_the_ones_redis_uses() {
1478 for (len, want) in [
1479 (0usize, 1usize),
1480 (127, 1),
1481 (128, 2),
1482 (16383, 2),
1483 (16384, 3),
1484 (2_097_151, 3),
1485 (2_097_152, 4),
1486 (268_435_455, 4),
1487 (268_435_456, 5),
1488 ] {
1489 assert_eq!(backlen_len(len), want, "an entry of {len} bytes");
1490 }
1491 }
1492
1493 #[test]
1494 fn a_walk_backward_reaches_every_element() {
1495 let lp = of(&[b"a", b"bb", b"1", b"999999", b"dddd"]);
1496 let back: Vec<Vec<u8>> = (0..5)
1497 .map(|i| lp.get_back(i).expect("in range").to_vec())
1498 .collect();
1499 let mut forward = all(&lp);
1500 forward.reverse();
1501 assert_eq!(back, forward);
1502 assert_eq!(lp.get_back(5), None);
1503 }
1504
1505 #[test]
1506 fn find_locates_an_element_however_it_is_stored() {
1507 let lp = of(&[b"alpha", b"42", b"01", b"beta"]);
1508 assert_eq!(lp.find(b"alpha", 1), Some(0));
1509 assert_eq!(lp.find(b"42", 1), Some(1), "stored as an integer");
1510 assert_eq!(lp.find(b"01", 1), Some(2), "stored as a string");
1511 assert_eq!(lp.find(b"beta", 1), Some(3));
1512 assert_eq!(lp.find(b"gamma", 1), None);
1513 assert_eq!(lp.find(b"1", 1), None, "01 is not 1");
1514 }
1515
1516 /// One of every encoding, at every length that changes which path through
1517 /// the scan an element takes.
1518 ///
1519 /// The lengths are the ones that matter to the comparison rather than
1520 /// arbitrary: nothing, under a word, exactly a word, between one and two
1521 /// words where the two window compare covers the whole value, exactly two,
1522 /// and past two where it stops being a whole answer and becomes a filter in
1523 /// front of `memcmp`. Sixty three and sixty four are the last length with a
1524 /// one byte header and the first with two, which is the boundary the short
1525 /// path is drawn on, and a hundred and twenty seven and a hundred and twenty
1526 /// eight are where the back length grows a second byte, which is the
1527 /// boundary the backward walk is drawn on.
1528 fn every_encoding() -> Vec<Vec<u8>> {
1529 let mut members: Vec<Vec<u8>> = Vec::new();
1530 // One of each integer encoding, including the boundaries where Redis
1531 // steps up to a wider one and both signs of each.
1532 for n in [
1533 0i64,
1534 127,
1535 -1,
1536 4095,
1537 -4096,
1538 32767,
1539 -32768,
1540 8_388_607,
1541 -8_388_608,
1542 2_147_483_647,
1543 -2_147_483_648,
1544 i64::MAX,
1545 i64::MIN,
1546 ] {
1547 members.push(n.to_string().into_bytes());
1548 }
1549 for len in [
1550 0usize, 1, 7, 8, 9, 15, 16, 17, 31, 63, 64, 100, 125, 126, 127, 128, 200,
1551 ] {
1552 let mut v = vec![b'a'; len];
1553 // A varying tail, so that two different lengths are not two
1554 // prefixes of each other and the length check is doing work rather
1555 // than being the only thing that separates them.
1556 if len > 0 {
1557 v[len - 1] = b'0' + (len % 10) as u8;
1558 }
1559 members.push(v);
1560 }
1561 members
1562 }
1563
1564 /// The scan has a short path for the one encoding a list is made of and a
1565 /// general one for the other thirteen, and the danger with two paths is that
1566 /// they disagree about some element neither author had in mind. So this
1567 /// builds a blob holding every encoding, at every length that changes which
1568 /// path an element takes, and asks for each of them in turn.
1569 ///
1570 /// The lengths are the ones that matter to the comparison rather than
1571 /// arbitrary: nothing, under a word, exactly a word, between one and two
1572 /// words where the two window compare covers the whole value, exactly two,
1573 /// and past two where it stops being a whole answer and becomes a filter in
1574 /// front of `memcmp`. Sixty three and sixty four are the last length with a
1575 /// one byte header and the first with two, which is the boundary the short
1576 /// path is drawn on.
1577 #[test]
1578 fn both_paths_through_the_scan_agree_about_every_encoding() {
1579 let members = every_encoding();
1580 let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
1581 assert_eq!(lp.len(), members.len());
1582 for (at, m) in members.iter().enumerate() {
1583 assert_eq!(lp.find(m, 1), Some(at), "member {at} went missing");
1584 }
1585 // And a handful that are not there, each one a near miss of something
1586 // that is: a different length, the same length with a different first
1587 // byte, and the same length with a different last byte.
1588 for miss in [
1589 b"aaaaaaaaaaaa".as_slice(),
1590 b"baaaaaa7".as_slice(),
1591 b"aaaaaaa9".as_slice(),
1592 b"128".as_slice(),
1593 b"-2".as_slice(),
1594 ] {
1595 assert_eq!(lp.find(miss, 1), None, "{miss:?} is not in here");
1596 }
1597 }
1598
1599 /// The same blob under a step of two, which is the other instantiation of
1600 /// the walk and the one a hash uses. Nothing at an odd position may be
1601 /// found, whatever it is encoded as.
1602 #[test]
1603 fn a_stepped_scan_agrees_with_itself_about_every_encoding() {
1604 let members: Vec<Vec<u8>> = (0..40i32)
1605 .map(|i| {
1606 if i % 3 == 0 {
1607 (i64::from(i) * 1000 - 20_000).to_string().into_bytes()
1608 } else {
1609 format!("field:{i:0width$}", width = (i % 20) as usize).into_bytes()
1610 }
1611 })
1612 .collect();
1613 let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
1614 for (at, m) in members.iter().enumerate() {
1615 let want = if at % 2 == 0 {
1616 // Every member here is distinct, so an even one is found where
1617 // it is and an odd one is not found at all.
1618 Some(at)
1619 } else {
1620 None
1621 };
1622 assert_eq!(lp.find(m, 2), want, "member {at} under a step of two");
1623 }
1624 }
1625
1626 /// A walk stopped anywhere and started again from the offset it reported
1627 /// has to give the rest of the blob, whatever the elements are encoded as.
1628 /// This is what a stream group read leans on to pick up where the last one
1629 /// stopped instead of decoding the node from the front every time.
1630 #[test]
1631 fn a_walk_started_again_at_an_offset_gives_the_rest() {
1632 let members = every_encoding();
1633 let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
1634 for stop in 0..members.len() {
1635 let mut it = lp.iter();
1636 for _ in 0..stop {
1637 it.next().expect("an element");
1638 }
1639 let at = it.offset();
1640 let rest: Vec<Vec<u8>> = lp.iter_at(at).map(|e| e.to_vec()).collect();
1641 assert_eq!(rest, members[stop..], "picking up at element {stop}");
1642 }
1643 assert_eq!(lp.iter_at(usize::MAX).count(), 0, "past the end is empty");
1644 assert_eq!(lp.iter_at(0).count(), members.len(), "before the first");
1645 }
1646
1647 /// The backward walk finds its way from one entry to the one in front of it
1648 /// through the back length rather than through a header, so it is a third
1649 /// path over the same bytes and it has to agree with the other two about all
1650 /// of them. Every member is asked for from the back and has to come back at
1651 /// the position the forward walk gives it.
1652 #[test]
1653 fn the_backward_scan_agrees_with_the_forward_one_about_every_encoding() {
1654 let members = every_encoding();
1655 let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
1656 let n = members.len();
1657 for (at, m) in members.iter().enumerate() {
1658 let mut got = Vec::new();
1659 lp.find_each_back(m, parse_i64(m), 0, &mut |back| {
1660 got.push(n - back - 1);
1661 true
1662 });
1663 assert_eq!(got, vec![at], "member {at} from the back");
1664 }
1665 for miss in [
1666 b"aaaaaaaaaaaa".as_slice(),
1667 b"baaaaaa7".as_slice(),
1668 b"aaaaaaa9".as_slice(),
1669 b"128".as_slice(),
1670 b"-2".as_slice(),
1671 ] {
1672 let mut got = 0usize;
1673 lp.find_each_back(miss, parse_i64(miss), 0, &mut |_| {
1674 got += 1;
1675 true
1676 });
1677 assert_eq!(got, 0, "{miss:?} is not in here");
1678 }
1679 }
1680
1681 /// Both walks over a blob where the same value is in it several times, which
1682 /// is what `LPOS` and `LREM` are actually for and what the single answer
1683 /// scan never exercises. The stop and the budget are checked here too, since
1684 /// they are the two things the walk carries that a find does not.
1685 #[test]
1686 fn a_walk_over_every_match_gives_them_all_in_order_from_either_end() {
1687 let members: Vec<Vec<u8>> = (0..30)
1688 .map(|i| {
1689 if i % 4 == 0 {
1690 b"x".to_vec()
1691 } else {
1692 format!("element:{i:08}").into_bytes()
1693 }
1694 })
1695 .collect();
1696 let lp = of(&members.iter().map(Vec::as_slice).collect::<Vec<_>>());
1697 let want: Vec<usize> = (0..30).filter(|i| i % 4 == 0).collect();
1698
1699 let mut got = Vec::new();
1700 let looked = lp.find_each(b"x", None, 0, &mut |at| {
1701 got.push(at);
1702 true
1703 });
1704 assert_eq!(got, want);
1705 assert_eq!(looked, 30, "no budget means the whole thing is read");
1706
1707 let mut got = Vec::new();
1708 lp.find_each_back(b"x", None, 0, &mut |back| {
1709 got.push(29 - back);
1710 true
1711 });
1712 got.reverse();
1713 assert_eq!(got, want, "the same matches, found the other way round");
1714
1715 // A stop after two, which must not read the rest.
1716 let mut got = Vec::new();
1717 let looked = lp.find_each(b"x", None, 0, &mut |at| {
1718 got.push(at);
1719 got.len() < 2
1720 });
1721 assert_eq!(got, vec![0, 4]);
1722 assert_eq!(looked, 5, "the walk stopped where the second match was");
1723
1724 // And a budget, which is `MAXLEN`: ten elements looked at reaches the
1725 // matches at 0, 4 and 8 and nothing after them.
1726 let mut got = Vec::new();
1727 let looked = lp.find_each(b"x", None, 10, &mut |at| {
1728 got.push(at);
1729 true
1730 });
1731 assert_eq!(got, vec![0, 4, 8]);
1732 assert_eq!(looked, 10);
1733
1734 let mut got = Vec::new();
1735 let looked = lp.find_each_back(b"x", None, 10, &mut |back| {
1736 got.push(29 - back);
1737 true
1738 });
1739 assert_eq!(got, vec![28, 24, 20], "ten from the back is 20 up");
1740 assert_eq!(looked, 10);
1741 }
1742
1743 /// A hash in this band is field, value, field, value, and a field lookup has
1744 /// to skip the values or a value that happens to equal a field name comes
1745 /// back as one.
1746 #[test]
1747 fn find_with_a_step_only_looks_at_the_fields() {
1748 let lp = of(&[b"name", b"age", b"age", b"41"]);
1749 assert_eq!(lp.find(b"name", 2), Some(0));
1750 assert_eq!(lp.find(b"age", 2), Some(2), "the value at 1 is not a field");
1751 assert_eq!(lp.find(b"41", 2), None);
1752 assert_eq!(lp.get(3), Some(Entry::Int(41)));
1753 }
1754
1755 #[test]
1756 fn inserting_puts_an_element_in_front_of_another() {
1757 let mut lp = of(&[b"a", b"c"]);
1758 lp.insert(1, b"b");
1759 assert_eq!(all(&lp), vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
1760 lp.insert(0, b"start");
1761 lp.insert(99, b"end");
1762 assert_eq!(lp.len(), 5);
1763 assert_eq!(all(&lp)[0], b"start".to_vec());
1764 assert_eq!(all(&lp)[4], b"end".to_vec());
1765 }
1766
1767 #[test]
1768 fn replacing_keeps_the_position_and_can_change_the_size() {
1769 let mut lp = of(&[b"a", b"b", b"c"]);
1770 assert!(lp.replace(1, b"a much longer value than before"));
1771 assert_eq!(lp.len(), 3);
1772 assert_eq!(all(&lp)[1], b"a much longer value than before".to_vec());
1773 assert!(lp.replace(1, b"7"));
1774 assert_eq!(lp.get(1), Some(Entry::Int(7)), "and can shrink to an int");
1775 assert_eq!(all(&lp), vec![b"a".to_vec(), b"7".to_vec(), b"c".to_vec()]);
1776 assert!(!lp.replace(9, b"nothing there"));
1777 }
1778
1779 #[test]
1780 fn deleting_takes_out_a_run_in_one_edit() {
1781 let mut lp = of(&[b"f1", b"v1", b"f2", b"v2", b"f3", b"v3"]);
1782 assert!(lp.delete(2, 2), "a field and its value together");
1783 assert_eq!(lp.len(), 4);
1784 assert_eq!(
1785 all(&lp),
1786 vec![
1787 b"f1".to_vec(),
1788 b"v1".to_vec(),
1789 b"f3".to_vec(),
1790 b"v3".to_vec()
1791 ]
1792 );
1793 assert!(!lp.delete(9, 1));
1794 assert!(
1795 lp.delete(0, 99),
1796 "asking for more than is there takes the rest"
1797 );
1798 assert!(lp.is_empty());
1799 assert_eq!(lp.len(), 0);
1800 }
1801
1802 /// Every edit has to leave the header right, because the header is what
1803 /// `from_bytes` checks and what a reader trusts.
1804 #[test]
1805 fn the_header_survives_every_edit() {
1806 let mut lp = Listpack::new();
1807 for i in 0..64u32 {
1808 lp.push(format!("member-{i}").as_bytes());
1809 }
1810 for i in 0..20 {
1811 lp.delete(i, 1);
1812 lp.replace(i, b"replaced");
1813 lp.insert(i, b"1234567");
1814 }
1815 let total = u32::from_le_bytes([lp.bytes[0], lp.bytes[1], lp.bytes[2], lp.bytes[3]]);
1816 assert_eq!(total as usize, lp.byte_len());
1817 assert_eq!(lp.len(), lp.iter().count());
1818 assert_eq!(Listpack::from_bytes(lp.as_bytes()), Ok(lp.clone()));
1819 }
1820
1821 #[test]
1822 fn a_blob_round_trips_through_its_bytes() {
1823 let lp = of(&[b"a", b"12345", b"", &[b'z'; 200]]);
1824 let back = Listpack::from_bytes(lp.as_bytes()).expect("our own bytes check out");
1825 assert_eq!(back, lp);
1826 assert_eq!(all(&back), all(&lp));
1827 }
1828
1829 /// Bytes from an RDB or a `RESTORE` are somebody else's, so every way they
1830 /// can be wrong is a refusal and not a panic.
1831 #[test]
1832 fn a_blob_that_does_not_check_out_is_refused() {
1833 assert_eq!(Listpack::from_bytes(&[]), Err(Malformed::Short));
1834 assert_eq!(Listpack::from_bytes(&[0; 4]), Err(Malformed::Short));
1835
1836 let good = of(&[b"alpha", b"beta"]);
1837
1838 let mut wrong_len = good.as_bytes().to_vec();
1839 wrong_len[0] = 99;
1840 assert_eq!(Listpack::from_bytes(&wrong_len), Err(Malformed::Length));
1841
1842 let mut no_end = good.as_bytes().to_vec();
1843 let last = no_end.len() - 1;
1844 no_end[last] = 0x00;
1845 assert_eq!(Listpack::from_bytes(&no_end), Err(Malformed::Terminator));
1846
1847 let mut wrong_count = good.as_bytes().to_vec();
1848 wrong_count[4] = 7;
1849 assert_eq!(Listpack::from_bytes(&wrong_count), Err(Malformed::Count));
1850
1851 let mut bad_entry = good.as_bytes().to_vec();
1852 bad_entry[HDR] = 0xF7;
1853 assert_eq!(Listpack::from_bytes(&bad_entry), Err(Malformed::Entry));
1854
1855 let mut bad_back = good.as_bytes().to_vec();
1856 bad_back[HDR + 6] = 3;
1857 assert_eq!(Listpack::from_bytes(&bad_back), Err(Malformed::BackLength));
1858 }
1859
1860 /// A blob written by somebody who did not keep the count, which Redis does
1861 /// above 65534 elements, is walked rather than rejected.
1862 #[test]
1863 fn an_unknown_count_is_walked_and_not_refused() {
1864 let mut lp = of(&[b"a", b"b", b"c"]);
1865 lp.bytes[4..6].copy_from_slice(&COUNT_UNKNOWN.to_le_bytes());
1866 let back = Listpack::from_bytes(lp.as_bytes()).expect("unknown is allowed");
1867 assert_eq!(back.len(), 3);
1868 }
1869
1870 fn hex(lp: &Listpack) -> String {
1871 lp.as_bytes().iter().map(|b| format!("{b:02x}")).collect()
1872 }
1873
1874 /// The claim this module makes is that the bytes are Redis's bytes, and the
1875 /// only way to check that is against Redis's bytes.
1876 ///
1877 /// These came out of `lpAppend` in `listpack.c` from the 8.10.1 tarball,
1878 /// compiled and run, not out of reading the source and working out what it
1879 /// would do. The vectors are the boundaries: every integer encoding and both
1880 /// ends of each, the strings that look like integers and must not become
1881 /// them, the empty string, and a hash shaped pack where a value is an
1882 /// integer and the fields around it are not.
1883 #[test]
1884 fn the_bytes_are_the_ones_redis_writes() {
1885 assert_eq!(hex(&Listpack::new()), "070000000000ff");
1886
1887 assert_eq!(
1888 hex(&of(&[b"one", b"two", b"three"])),
1889 "180000000300836f6e65048374776f0485746872656506ff"
1890 );
1891
1892 let ints: Vec<&[u8]> = vec![
1893 b"0",
1894 b"127",
1895 b"128",
1896 b"4095",
1897 b"-4096",
1898 b"-1",
1899 b"4096",
1900 b"-4097",
1901 b"32767",
1902 b"32768",
1903 b"8388607",
1904 b"8388608",
1905 b"2147483647",
1906 b"2147483648",
1907 b"-9223372036854775808",
1908 ];
1909 assert_eq!(
1910 hex(&of(&ints)),
1911 "4d0000000f0000017f01c08002cfff02d00002dfff02f1001003f1ffef03f1ff7f\
1912 03f200800004f2ffff7f04f30000800005f3ffffff7f05f4000000800000000009\
1913 f4000000000000008009ff"
1914 );
1915
1916 let not_ints: Vec<&[u8]> = vec![b"01", b"+1", b"1 ", b" 1", b"1.0", b"-0", b""];
1917 assert_eq!(
1918 hex(&of(¬_ints)),
1919 "22000000070082303103822b3103823120038220310383312e3004822d30038001ff"
1920 );
1921
1922 assert_eq!(
1923 hex(&of(&[b"name", b"age", b"age", b"41"])),
1924 "190000000400846e616d6505836167650483616765042901ff"
1925 );
1926 }
1927
1928 /// The same check for the length boundaries, where the whole middle of the
1929 /// blob is one repeated byte and only the ends carry any information.
1930 #[test]
1931 fn a_long_element_is_framed_the_way_redis_frames_it() {
1932 for (len, total, head, tail) in [
1933 (
1934 63usize,
1935 72usize,
1936 "480000000100bf7878787878",
1937 "78787878787840ff",
1938 ),
1939 (64, 74, "4a0000000100e04078787878", "78787878787842ff"),
1940 (4095, 4106, "0a1000000100efff78787878", "78787878782081ff"),
1941 (4096, 4110, "0e1000000100f00010000078", "78787878782085ff"),
1942 ] {
1943 let lp = of(&[&vec![b'x'; len]]);
1944 let h = hex(&lp);
1945 assert_eq!(lp.byte_len(), total, "a {len} byte element");
1946 assert_eq!(&h[..head.len()], head, "a {len} byte element");
1947 assert_eq!(&h[h.len() - tail.len()..], tail, "a {len} byte element");
1948 }
1949
1950 let lp = of(&[&vec![b'y'; 5000], b"after"]);
1951 let h = hex(&lp);
1952 assert_eq!(lp.byte_len(), 5021);
1953 assert_eq!(&h[..24], "9d1300000200f08813000079");
1954 assert_eq!(&h[h.len() - 16..], "85616674657206ff");
1955 }
1956
1957 /// The band it exists for. A hundred and twenty eight elements, every one of
1958 /// them findable, and the whole thing inside a few cache lines.
1959 #[test]
1960 fn a_full_inline_band_still_reads_correctly() {
1961 let members: Vec<Vec<u8>> = (0..128u32).map(|i| format!("m{i}").into_bytes()).collect();
1962 let mut lp = Listpack::new();
1963 for m in &members {
1964 lp.push(m);
1965 }
1966 assert_eq!(lp.len(), 128);
1967 for (i, m) in members.iter().enumerate() {
1968 assert_eq!(lp.find(m, 1), Some(i));
1969 }
1970 assert!(lp.byte_len() < 1024, "{} bytes", lp.byte_len());
1971 }
1972}