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