Skip to main content

yo_kv/
stream.rs

1//! A stream, as a log of listpack nodes in ID order (`08` section 7).
2//!
3//! A stream is the one collection here that is only ever appended to at one end
4//! and only ever trimmed at the other. Nothing is inserted in the middle, ever,
5//! because the ID of a new entry has to be greater than the last one there. That
6//! is a much stronger promise than any other type makes, and the structure is
7//! built on it rather than around it.
8//!
9//! ```text
10//! +--------------+--------------+--------------+
11//! | node         | node         | node         |
12//! | master 5-0   | master 812-0 | master 990-0 |
13//! | 100 entries  | 100 entries  | 12 entries   |
14//! +--------------+--------------+--------------+
15//!   ^ trimmed from here          appended here ^
16//! ```
17//!
18//! # Why a node holds a hundred entries and not one
19//!
20//! One entry per allocation is what a naive log does and it is wrong twice over.
21//! It costs an allocator header and a pointer per entry, which for a stream of
22//! short entries is most of the memory, and it puts the entries in whatever
23//! order the allocator felt like, which is the opposite of what a range scan
24//! wants. A node is one blob holding a hundred consecutive entries, so a scan is
25//! a walk through memory the prefetcher can see coming, and the per entry
26//! overhead is a few bytes rather than a few dozen.
27//!
28//! Two tricks inside the node take it further down, and both are Redis's:
29//!
30//! The **ID is stored as a difference** from the node's first ID rather than
31//! whole. Entries arrive milliseconds apart and a difference of a few hundred is
32//! two bytes where a pair of 64 bit integers is sixteen.
33//!
34//! The **field names are stored once** for the node rather than once per entry.
35//! A stream is almost always the same shape repeated, `sensor` and `reading`
36//! over and over, so the first entry's field names become the node's master
37//! fields and every later entry with exactly those names, in that order, stores
38//! only its values. An entry with different fields stores its own names and
39//! costs what it would have cost anyway.
40//!
41//! # Why the nodes are a deque and not a radix tree
42//!
43//! Redis keeps its nodes in a rax keyed by the sixteen byte big endian ID, and
44//! `08` says radix log, so this is the place to say why there is no radix tree
45//! here. The keys are appended in sorted order and never inserted between, which
46//! means the index is a sorted array and stays one for free. Finding the node a
47//! range starts in is then a binary search over the node count, which for a
48//! million entries is fourteen steps over an array that is a few pages long. A
49//! radix tree over the same keys is four or five levels of pointer chasing, each
50//! one a cache miss, and it exists to solve the insertion problem that this
51//! structure does not have.
52//!
53//! Trimming is what a plain `Vec` would get wrong, since it takes from the front,
54//! so the nodes live in a `VecDeque` and dropping the oldest node is a pop.
55//!
56//! # The bytes are Redis's
57//!
58//! A node is a [`Listpack`] laid out exactly as `t_stream.c` lays one out, and
59//! the node's first ID is held beside it exactly as the rax key holds it there.
60//! That is not deference, it is the cheapest route to `DUMP` and `RESTORE` and
61//! an RDB that Redis can read, since the node is already the thing that goes on
62//! the wire and no conversion has to exist at all.
63//!
64//! ```text
65//! master entry
66//! +-------+---------+------------+---------+-----+---------+---+
67//! | count | deleted | num-fields | field-1 | ... | field-N | 0 |
68//! +-------+---------+------------+---------+-----+---------+---+
69//!
70//! an entry with the master's fields
71//! +-------+---------+----------+---------+-----+---------+----------+
72//! | flags | ms-diff | seq-diff | value-1 | ... | value-N | lp-count |
73//! +-------+---------+----------+---------+-----+---------+----------+
74//!
75//! an entry with its own
76//! +-------+---------+----------+------------+---------+---------+-----+----------+
77//! | flags | ms-diff | seq-diff | num-fields | field-1 | value-1 | ... | lp-count |
78//! +-------+---------+----------+------------+---------+---------+-----+----------+
79//! ```
80//!
81//! `lp-count` is how many listpack elements the entry occupies before it, which
82//! is what makes the node walkable backwards. It is written because Redis writes
83//! it and the bytes have to match, and nothing here reads it yet: `XREVRANGE`
84//! buffers a node's marks and hands them back reversed instead. That costs, and
85//! the benchmark says how much, 6.83 microseconds against 1.95 for the same
86//! hundred entries forwards. Stepping back over `lp-count` is the fix and it is
87//! its own change, because it needs its own before and after.
88//!
89//! Both ID halves are a plain wrapping difference from the master, which is what
90//! Redis writes and what it adds back. The sequence usually goes down when the
91//! millisecond goes up, so the second difference is usually negative, and
92//! wrapping arithmetic is the exact inverse either way.
93//!
94//! That claim is checked rather than asserted. A `DUMP` taken from Redis 8.10.1
95//! is hard coded in the tests, and two of them run it in both directions: one
96//! builds the same stream here and compares the node's bytes to Redis's byte for
97//! byte, the other takes Redis's node and reads the entries back out of it.
98//!
99//! # Deleting does not move anything
100//!
101//! `XDEL` sets a bit in the entry's flags and leaves the bytes where they are.
102//! Compacting the node would move every entry behind it, which on a node of a
103//! hundred is a memmove per delete, and a stream is not a structure people
104//! delete from in bulk. The node's master entry counts how many of its entries
105//! are dead, and a node whose last live entry goes is dropped whole.
106
107use std::cmp::Ordering;
108use std::collections::VecDeque;
109
110use yo_common::num::{DIGITS_MAX, i64_digits, push_u64, u64_digits};
111
112use crate::frozen::{self, Broken};
113use crate::listpack::{self, Entry, Listpack};
114
115pub mod groups;
116
117pub use groups::{Consumer, Filter, Group, Nack, Retry};
118
119/// How many bytes a node holds before the next entry starts a new one.
120///
121/// `stream-node-max-bytes`, which is 4096 in Redis and is here for the same
122/// reason: a node is rewritten in place when an entry is deleted from it and is
123/// copied whole when it is written out, so a node that grows without limit
124/// turns both of those into a problem.
125pub const NODE_BYTES: usize = 4096;
126
127/// How many entries a node holds before the next one starts a new node.
128///
129/// `stream-node-max-entries`, which is 100 in Redis.
130pub const NODE_ENTRIES: usize = 100;
131
132/// The entry is live.
133const LIVE: i64 = 0;
134
135/// The entry has been deleted and its bytes are still here.
136const DELETED: i64 = 1;
137
138/// The entry's field names are the node's master fields.
139const SAME_FIELDS: i64 = 2;
140
141/// Where the master entry's field names start.
142///
143/// After the count, the deleted count and the number of fields.
144const MASTER_FIELDS: usize = 3;
145
146/// An entry ID, which is a millisecond and a sequence number inside it.
147///
148/// Two 64 bit halves rather than one 128 bit number, because both halves are
149/// addressable on the wire: `XADD key 5-*` asks for the next sequence inside
150/// millisecond five, and `XRANGE key 5 5` is every sequence inside it.
151#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
152pub struct Id {
153    /// The millisecond, which is a unix timestamp for an ID the server made up.
154    pub ms: u64,
155    /// Which entry within that millisecond.
156    pub seq: u64,
157}
158
159impl Id {
160    /// The lowest ID there is, which is what `-` means in a range.
161    pub const MIN: Id = Id { ms: 0, seq: 0 };
162
163    /// The highest, which is what `+` means.
164    pub const MAX: Id = Id {
165        ms: u64::MAX,
166        seq: u64::MAX,
167    };
168
169    /// An ID from its two halves.
170    #[must_use]
171    #[inline]
172    pub const fn new(ms: u64, seq: u64) -> Id {
173        Id { ms, seq }
174    }
175
176    /// The next ID after this one, or `None` at [`Id::MAX`].
177    ///
178    /// What an exclusive range start turns into, and what `XADD key ms-*`
179    /// resolves to when the millisecond is already the last one used.
180    #[must_use]
181    pub const fn next(self) -> Option<Id> {
182        if self.seq != u64::MAX {
183            Some(Id {
184                ms: self.ms,
185                seq: self.seq + 1,
186            })
187        } else if self.ms != u64::MAX {
188            Some(Id {
189                ms: self.ms + 1,
190                seq: 0,
191            })
192        } else {
193            None
194        }
195    }
196
197    /// The ID before this one, or `None` at [`Id::MIN`].
198    #[must_use]
199    pub const fn prev(self) -> Option<Id> {
200        if self.seq != 0 {
201            Some(Id {
202                ms: self.ms,
203                seq: self.seq - 1,
204            })
205        } else if self.ms != 0 {
206            Some(Id {
207                ms: self.ms - 1,
208                seq: u64::MAX,
209            })
210        } else {
211            None
212        }
213    }
214
215    /// The sixteen big endian bytes Redis keys a node by.
216    ///
217    /// Big endian because that is the order that sorts, which is the whole
218    /// reason the format picked it.
219    #[must_use]
220    pub fn to_bytes(self) -> [u8; 16] {
221        let mut out = [0u8; 16];
222        out[..8].copy_from_slice(&self.ms.to_be_bytes());
223        out[8..].copy_from_slice(&self.seq.to_be_bytes());
224        out
225    }
226
227    /// The ID those bytes hold.
228    #[must_use]
229    pub fn from_bytes(bytes: [u8; 16]) -> Id {
230        let mut ms = [0u8; 8];
231        let mut seq = [0u8; 8];
232        ms.copy_from_slice(&bytes[..8]);
233        seq.copy_from_slice(&bytes[8..]);
234        Id {
235            ms: u64::from_be_bytes(ms),
236            seq: u64::from_be_bytes(seq),
237        }
238    }
239
240    /// `ms-seq`, which is how an ID looks everywhere a client can see one.
241    pub fn write_to(self, out: &mut Vec<u8>) {
242        push_u64(out, self.ms);
243        out.push(b'-');
244        push_u64(out, self.seq);
245    }
246
247    /// The same as a fresh `Vec`, for a caller that is not building a reply.
248    #[must_use]
249    pub fn to_vec(self) -> Vec<u8> {
250        let mut out = Vec::with_capacity(41);
251        self.write_to(&mut out);
252        out
253    }
254
255    /// `ms` or `ms-seq`, with a missing sequence read as `default`.
256    ///
257    /// `XRANGE key 5 5` means every entry in millisecond five, so the start
258    /// defaults its sequence to zero and the end to the largest there is. The
259    /// special forms a client can send, `-`, `+`, `$`, `*` and `ms-*`, are the
260    /// command layer's business and not this one's.
261    #[must_use]
262    pub fn parse(s: &[u8], default: u64) -> Option<Id> {
263        let (ms, seq) = match s.iter().position(|c| *c == b'-') {
264            Some(at) => (&s[..at], Some(&s[at + 1..])),
265            None => (s, None),
266        };
267        Some(Id {
268            ms: digits(ms)?,
269            seq: match seq {
270                Some(seq) => digits(seq)?,
271                None => default,
272            },
273        })
274    }
275}
276
277/// A run of digits as a `u64`, refusing a sign, a space or an empty string.
278///
279/// `parse_i64` would take `-1` and `+5` and this must not: an ID is unsigned
280/// and the minus is the separator.
281fn digits(s: &[u8]) -> Option<u64> {
282    if s.is_empty() || s.len() > 20 {
283        return None;
284    }
285    let mut n = 0u64;
286    for c in s {
287        let d = c.wrapping_sub(b'0');
288        if d > 9 {
289            return None;
290        }
291        n = n.checked_mul(10)?.checked_add(u64::from(d))?;
292    }
293    Some(n)
294}
295
296/// Why an append was refused.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum Refused {
299    /// The ID is not greater than the last one in the stream.
300    NotGreater,
301    /// The ID is zero, which no entry can have because nothing sorts below it.
302    Zero,
303    /// The stream is at [`Id::MAX`] and there is no next ID to hand out.
304    Full,
305}
306
307/// Where a node stops taking entries.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct Limits {
310    /// The most bytes a node holds.
311    pub max_node_bytes: usize,
312    /// The most entries a node holds, live and deleted together.
313    pub max_node_entries: usize,
314}
315
316impl Default for Limits {
317    /// `stream-node-max-bytes 4096` and `stream-node-max-entries 100`.
318    fn default() -> Limits {
319        Limits {
320            max_node_bytes: NODE_BYTES,
321            max_node_entries: NODE_ENTRIES,
322        }
323    }
324}
325
326/// What a delete does about the consumer groups still pointing at the entry.
327///
328/// `XDEL` takes an entry out from under whoever was handed it and leaves the
329/// pending list holding an ID that can never be read, which is a state `XCLAIM`
330/// then has to clean up. The 8.2 commands let a caller say what it wants
331/// instead, and the three answers are the three words here.
332#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
333pub enum Refs {
334    /// `KEEPREF`: take the entry and leave every pending list alone, which is
335    /// what `XDEL` has always done and is still the default.
336    #[default]
337    Keep,
338    /// `DELREF`: take the entry and take it out of every pending list with it.
339    Drop,
340    /// `ACKED`: only take the entry when no group could still be handed it.
341    Acked,
342}
343
344/// What one ID a delete was asked about came to.
345///
346/// The numbers are Redis's and they are not a success flag: a caller sending a
347/// list of IDs gets one of these each and has to be able to tell an entry that
348/// was never there from one that is still there on purpose.
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub enum Fate {
351    /// There was nothing here to do.
352    Missing,
353    /// It went.
354    Gone,
355    /// Somebody could still be handed it, so it stayed.
356    Held,
357}
358
359impl Fate {
360    /// The integer this is on the wire.
361    #[must_use]
362    #[inline]
363    pub fn code(self) -> i64 {
364        match self {
365            Fate::Missing => -1,
366            Fate::Gone => 1,
367            Fate::Held => 2,
368        }
369    }
370}
371
372/// One node: a run of consecutive entries and the ID the run starts at.
373#[derive(Debug, Clone, PartialEq, Eq)]
374struct Node {
375    /// The ID every entry in this node stores its own as a difference from.
376    ///
377    /// It is the first entry's ID, and it stays that even after the first entry
378    /// is deleted, because the differences behind it are relative to it.
379    master: Id,
380    lp: Listpack,
381}
382
383/// What a stream looks like from the outside, for working out a group's
384/// counters.
385///
386/// Five numbers taken together at one moment. The reason they travel as a
387/// bundle is that both rules below read several of them and the answers have to
388/// come from the same instant, and the reason it is a private type is that
389/// nobody outside wants five loose numbers, they want [`Stream::lag`].
390#[derive(Debug, Clone, Copy)]
391struct Edges {
392    added: u64,
393    length: u64,
394    first: Option<Id>,
395    last: Id,
396    max_deleted: Id,
397}
398
399impl Edges {
400    /// How many entries a reader sitting at `id` must have passed, when that is
401    /// exactly knowable, which is the three positions where it is.
402    ///
403    /// At or past the last ID ever handed out, everything that was ever added
404    /// has gone by. Below the oldest entry left, with nothing deleted inside
405    /// what is left, everything that has gone is behind and the length is what
406    /// is in front. Exactly on the oldest entry left, the same plus one.
407    ///
408    /// Anywhere else the answer is genuinely unknown rather than approximate,
409    /// because working it out would mean counting the entries between here and
410    /// there, and that is the walk the whole counter exists to avoid.
411    fn estimate(&self, id: Id) -> Option<u64> {
412        if self.added == 0 {
413            return Some(0);
414        }
415        if id >= self.last {
416            return Some(self.added);
417        }
418        let first = self.first?;
419        // A hole below the oldest entry left is not a hole in what is left, so
420        // the subtraction below still holds. That is the case a trim makes, and
421        // it is why a trim does not cost a group its lag.
422        if self.max_deleted != Id::MIN && self.max_deleted >= first {
423            return None;
424        }
425        let behind = self.added - self.length;
426        match id.cmp(&first) {
427            Ordering::Less => Some(behind),
428            Ordering::Equal => Some(behind + 1),
429            Ordering::Greater => None,
430        }
431    }
432
433    /// Whether anything has been deleted at or above `id`.
434    ///
435    /// Only [`Stream::delete`] moves `max_deleted`, so a trim does not count,
436    /// and a stream with nothing left in it does not either, since there is no
437    /// gap in an empty stream for a reader to fall into.
438    fn holed_from(&self, id: Id) -> bool {
439        if self.length == 0 || self.max_deleted == Id::MIN {
440            return false;
441        }
442        if self.first.is_some_and(|first| first > self.max_deleted) {
443            return false;
444        }
445        id <= self.max_deleted
446    }
447
448    /// A group's lag, the good way first and the subtraction second.
449    fn lag(&self, group: &Group) -> Option<u64> {
450        if let Some(read) = self.estimate(group.last_id()) {
451            return Some(self.added.saturating_sub(read));
452        }
453        let read = group.entries_read()?;
454        if self.holed_from(group.last_id()) {
455            return None;
456        }
457        Some(self.added.saturating_sub(read))
458    }
459
460    /// What a group's read counter becomes once `id` has been handed out.
461    ///
462    /// One more than it was, while it is known and nothing has been deleted
463    /// ahead of the entry just delivered. Otherwise the estimate above gets a
464    /// go, which is what lets a group that has read all the way to the end come
465    /// back from not knowing.
466    fn on_deliver(&self, group: &Group, id: Id) -> Option<u64> {
467        match group.entries_read() {
468            Some(read) if !self.holed_from(id) => Some(read + 1),
469            _ => self.estimate(id),
470        }
471    }
472}
473
474/// The only frozen form there is: the nodes as they stand, then the groups.
475///
476/// There is no packed form to sit beside it the way the other collections have
477/// one, because a stream is a listpack per node from the first entry and never
478/// changes shape.
479const FORM_NODES: u8 = 1;
480
481/// Where a group read stopped, so the next one does not start from the front.
482///
483/// `XREADGROUP GROUP g c COUNT 1 STREAMS key >` hands over one entry and moves
484/// the group's bookmark one along. Without a mark the read after it walks the
485/// node's blob from the first entry to find the one after the bookmark, and at
486/// the default hundred entries a node that is fifty entries of decoding to hand
487/// back one of them, which makes a consumer draining a stream quadratic in the
488/// node rather than linear.
489///
490/// The mark is only believed when the stream has not moved any bytes since it
491/// was taken and the read is asking for exactly the ID the walk that left it
492/// would be asked for next. Anything else, an `XGROUP SETID` most of all, walks
493/// from the front the way it always did.
494#[derive(Debug, Clone, Copy)]
495pub(crate) struct Cursor {
496    /// The stream's mutation count when this was taken.
497    epoch: u64,
498    /// The ID the walk that left this mark would be asked for next, which is
499    /// one past the last entry it handed over. A read starting anywhere else
500    /// cannot use it, because the mark says nothing about what is behind it.
501    next: Id,
502    /// Which node, by its master ID, which is unique and never reused.
503    master: Id,
504    /// Where in that node's blob to pick up.
505    byte: usize,
506}
507
508/// A log of entries in ID order.
509#[derive(Debug, Clone, Default)]
510pub struct Stream {
511    nodes: VecDeque<Node>,
512    /// Live entries, which is what `XLEN` answers.
513    length: u64,
514    /// The greatest ID ever appended, which does not go down when it is deleted.
515    last: Id,
516    /// The greatest ID ever deleted, which `XINFO` reports.
517    max_deleted: Id,
518    /// How many entries have ever been appended.
519    ///
520    /// Not the length. It only goes up, and it is what a consumer group uses to
521    /// work out how far behind it is without walking anything.
522    added: u64,
523    /// The consumer groups, by name.
524    ///
525    /// A vector because a stream has a handful of groups and the name is looked
526    /// up once a command, so a linear scan beats hashing and brings nothing
527    /// with it. The same argument the group makes about its consumers.
528    groups: Vec<(Vec<u8>, Group)>,
529    /// How many times something has moved bytes inside a node.
530    ///
531    /// A [`Cursor`] is a byte offset into a node's blob and it is only good for
532    /// as long as the bytes it counted past are where they were. An append is
533    /// not one of these events, because it writes at the end and the header
534    /// fields it rewrites are the same width either side of it almost always,
535    /// and the almost is what this counts.
536    epoch: u64,
537}
538
539/// Two streams are the same when they hold the same entries and the same
540/// groups. The mutation count is not part of that. It is a number the resume
541/// cursors compare themselves against, nothing outside this file can see it,
542/// and a stream frozen and thawed is the same stream even though its count
543/// starts again at zero.
544impl PartialEq for Stream {
545    fn eq(&self, other: &Stream) -> bool {
546        self.nodes == other.nodes
547            && self.length == other.length
548            && self.last == other.last
549            && self.max_deleted == other.max_deleted
550            && self.added == other.added
551            && self.groups == other.groups
552    }
553}
554
555impl Eq for Stream {}
556
557impl Stream {
558    /// An empty stream.
559    #[must_use]
560    pub fn new() -> Stream {
561        Stream::default()
562    }
563
564    /// The nodes as they stand, each one its master ID and its blob.
565    ///
566    /// This is what an RDB payload for a stream is made of, and it is why the
567    /// node layout was copied from Redis in the first place. The master ID is
568    /// the sixteen byte rax key a real server writes and the blob is the
569    /// listpack it writes after it, so [`crate::rdb`] puts both on the wire with
570    /// nothing converted on the way.
571    pub(crate) fn raw_nodes(&self) -> impl Iterator<Item = (Id, &[u8])> + '_ {
572        self.nodes.iter().map(|n| (n.master, n.lp.as_bytes()))
573    }
574
575    /// Put a node on the end of a stream that is being built from a payload.
576    ///
577    /// Refuses a master that does not beat the one before it, for the reason
578    /// [`Stream::thaw`] gives: the nodes are searched by binary search over
579    /// their masters, so a run that is not in order would leave a lookup unable
580    /// to say which node an ID belongs in.
581    pub(crate) fn push_raw_node(&mut self, master: Id, lp: Listpack) -> bool {
582        if self.nodes.back().is_some_and(|n| n.master >= master) {
583            return false;
584        }
585        self.nodes.push_back(Node { master, lp });
586        true
587    }
588
589    /// Put the counters on a stream that is being built from a payload.
590    ///
591    /// The same three checks [`Stream::thaw`] makes, because a payload from a
592    /// client has even less claim to be believed than bytes off our own disk.
593    pub(crate) fn set_counters(
594        &mut self,
595        length: u64,
596        last: Id,
597        max_deleted: Id,
598        added: u64,
599    ) -> bool {
600        if length > added || max_deleted > last || (self.nodes.is_empty() && length != 0) {
601            return false;
602        }
603        self.length = length;
604        self.last = last;
605        self.max_deleted = max_deleted;
606        self.added = added;
607        true
608    }
609
610    /// Put a group on a stream that is being built from a payload.
611    pub(crate) fn push_group(&mut self, name: &[u8], group: Group) -> bool {
612        if self.groups.iter().any(|(had, _)| had == name) {
613            return false;
614        }
615        self.groups.push((name.to_vec(), group));
616        true
617    }
618
619    /// Write the stream out as the bytes a tier can hold, for
620    /// [`crate::keyspace::Keyspace`] to hand back to [`Stream::thaw`].
621    ///
622    /// The nodes go out as the listpacks they already are, one master ID and one
623    /// blob each. That is the whole point of the node layout: a run of entries
624    /// is already a flat sequence of bytes with no pointers in it, so freezing
625    /// one is a copy and thawing it is a length check. Only the counters and the
626    /// consumer groups need a form of their own.
627    pub fn freeze(&self, out: &mut Vec<u8>) {
628        out.push(FORM_NODES);
629        frozen::put_uint(out, self.length);
630        frozen::put_uint(out, self.last.ms);
631        frozen::put_uint(out, self.last.seq);
632        frozen::put_uint(out, self.max_deleted.ms);
633        frozen::put_uint(out, self.max_deleted.seq);
634        frozen::put_uint(out, self.added);
635
636        frozen::put_uint(out, self.nodes.len() as u64);
637        for node in &self.nodes {
638            frozen::put_uint(out, node.master.ms);
639            frozen::put_uint(out, node.master.seq);
640            frozen::put_bytes(out, node.lp.as_bytes());
641        }
642
643        frozen::put_uint(out, self.groups.len() as u64);
644        for (name, group) in &self.groups {
645            frozen::put_bytes(out, name);
646            group.freeze(out);
647        }
648    }
649
650    /// Read back a stream [`Stream::freeze`] wrote.
651    ///
652    /// What is inside a node is checked as far as [`Listpack::from_bytes`]
653    /// checks it, which is that the header, the lengths and the terminator all
654    /// agree, and no further. Every walk over a node's contents already returns
655    /// early on anything it does not understand rather than trusting what it
656    /// finds, so a structurally sound listpack full of nonsense answers an empty
657    /// range instead of panicking. That is the same trust a node written by
658    /// Redis and loaded from an RDB file gets today.
659    pub fn thaw(bytes: &[u8]) -> Result<Stream, Broken> {
660        let mut cut = frozen::Cut::new(bytes);
661        if cut.byte()? != FORM_NODES {
662            return Err(Broken::Form);
663        }
664        let length = cut.uint()?;
665        let last = Id::new(cut.uint()?, cut.uint()?);
666        let max_deleted = Id::new(cut.uint()?, cut.uint()?);
667        let added = cut.uint()?;
668        // Deleting an entry needs an entry, and so does reading one, so a stream
669        // that has lost more than it ever took or is holding more than it was
670        // ever given did not come from `freeze`.
671        if length > added || max_deleted > last {
672            return Err(Broken::Body);
673        }
674
675        let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
676        // A node is a master ID and a listpack, so it cannot be under a byte and
677        // a count past what is left is short rather than a reservation to make.
678        if n > cut.rest().len() {
679            return Err(Broken::Short);
680        }
681        let mut nodes = VecDeque::with_capacity(n);
682        let mut prev: Option<Id> = None;
683        for _ in 0..n {
684            let master = Id::new(cut.uint()?, cut.uint()?);
685            // Nodes are consecutive runs in ID order, so a master that does not
686            // beat the one before it would leave a lookup unable to pick the
687            // node an ID belongs in.
688            if prev.is_some_and(|p| p >= master) {
689                return Err(Broken::Body);
690            }
691            prev = Some(master);
692            let lp = Listpack::from_bytes(cut.bytes()?).map_err(|_| Broken::Body)?;
693            nodes.push_back(Node { master, lp });
694        }
695        if nodes.is_empty() && length != 0 {
696            return Err(Broken::Body);
697        }
698
699        let n = usize::try_from(cut.uint()?).map_err(|_| Broken::Short)?;
700        if n > cut.rest().len() {
701            return Err(Broken::Short);
702        }
703        let mut groups: Vec<(Vec<u8>, Group)> = Vec::with_capacity(n);
704        for _ in 0..n {
705            let name = cut.bytes()?;
706            // Groups are found by a scan for the name, so a repeat would leave
707            // the second one holding entries that nothing could acknowledge.
708            if groups.iter().any(|(had, _)| had == name) {
709                return Err(Broken::Body);
710            }
711            groups.push((name.to_vec(), Group::thaw(&mut cut)?));
712        }
713
714        Ok(Stream {
715            nodes,
716            length,
717            last,
718            max_deleted,
719            added,
720            groups,
721            epoch: 0,
722        })
723    }
724
725    /// How many live entries there are, which is `XLEN`.
726    #[must_use]
727    #[inline]
728    pub fn len(&self) -> u64 {
729        self.length
730    }
731
732    /// Whether there are no live entries.
733    ///
734    /// A stream can be empty and still exist, unlike every other collection
735    /// here, because `XADD` followed by `XDEL` leaves a key whose last ID a new
736    /// entry still has to beat.
737    #[must_use]
738    #[inline]
739    pub fn is_empty(&self) -> bool {
740        self.length == 0
741    }
742
743    /// The greatest ID ever appended, whether or not it is still here.
744    #[must_use]
745    #[inline]
746    pub fn last_id(&self) -> Id {
747        self.last
748    }
749
750    /// The greatest ID ever deleted, or [`Id::MIN`] if none ever was.
751    #[must_use]
752    #[inline]
753    pub fn max_deleted_id(&self) -> Id {
754        self.max_deleted
755    }
756
757    /// How many entries have ever been appended.
758    #[must_use]
759    #[inline]
760    pub fn added(&self) -> u64 {
761        self.added
762    }
763
764    /// The lowest live ID, or `None` when there are none.
765    ///
766    /// Walks the first node, because the first entry in it may have been
767    /// deleted and its bytes are still there. That is at most a node's worth of
768    /// steps and it is only asked for by `XINFO`.
769    #[must_use]
770    pub fn first_id(&self) -> Option<Id> {
771        let mut found = None;
772        self.walk(Id::MIN, Id::MAX, Some(1), &mut |id, _| {
773            found = Some(id);
774            false
775        });
776        found
777    }
778
779    /// The highest live ID, or `None` when there are none.
780    ///
781    /// Not the same as [`Stream::last_id`], which is the last ID handed out and
782    /// stays where it is when that entry is deleted. This is the greatest ID a
783    /// reader can still find, and `XSETID` is the one caller that needs the
784    /// difference, because it refuses to move the bookmark below an entry that
785    /// is still there.
786    #[must_use]
787    pub fn top_id(&self) -> Option<Id> {
788        let mut found = None;
789        self.rev_range(Id::MIN, Id::MAX, Some(1), |id, _| {
790            found = Some(id);
791            false
792        });
793        found
794    }
795
796    /// `XSETID`, which moves the bookmark and the two counters behind it.
797    ///
798    /// The bookmark decides what the next `XADD *` hands out and what a group
799    /// created at `$` starts from, so moving it is how a replica is made to
800    /// agree with a primary and how a stream is rebuilt from a log. The two
801    /// counters are optional because Redis added them later and a caller that
802    /// does not name them leaves them alone.
803    ///
804    /// # Errors
805    ///
806    /// [`Refused::NotGreater`] when `last` is below an entry that is still in
807    /// the stream, since a reader holding that entry's ID would then be reading
808    /// past the end of a stream that has not ended.
809    pub fn set_id(
810        &mut self,
811        last: Id,
812        added: Option<u64>,
813        max_deleted: Option<Id>,
814    ) -> Result<(), Refused> {
815        if self.top_id().is_some_and(|top| last < top) {
816            return Err(Refused::NotGreater);
817        }
818        self.last = last;
819        if let Some(added) = added {
820            self.added = added;
821        }
822        if let Some(id) = max_deleted {
823            self.max_deleted = id;
824        }
825        Ok(())
826    }
827
828    /// What the ID would be if `XADD key *` ran now with clock reading `now`.
829    ///
830    /// The clock unless the clock has not moved, or has gone backwards, in which
831    /// case it is the last ID with one added. A stream never goes back on its
832    /// word about ordering just because the machine's clock did.
833    #[must_use]
834    pub fn auto_id(&self, now: u64) -> Option<Id> {
835        if now > self.last.ms {
836            Some(Id { ms: now, seq: 0 })
837        } else {
838            self.last.next()
839        }
840    }
841
842    /// The next sequence inside `ms`, which is what `XADD key ms-*` asks for.
843    #[must_use]
844    pub fn auto_seq(&self, ms: u64) -> Option<Id> {
845        if ms > self.last.ms {
846            Some(Id { ms, seq: 0 })
847        } else if ms == self.last.ms {
848            self.last.next().filter(|id| id.ms == ms)
849        } else {
850            None
851        }
852    }
853
854    /// Append an entry, which is `XADD` once the ID has been settled.
855    ///
856    /// # Errors
857    ///
858    /// [`Refused`] when the ID is zero or is not greater than [`Stream::last_id`].
859    /// Nothing else can fail: an append never needs to move an entry that is
860    /// already here.
861    pub fn append(
862        &mut self,
863        id: Id,
864        fields: &[(&[u8], &[u8])],
865        limits: Limits,
866    ) -> Result<(), Refused> {
867        if id == Id::MIN {
868            return Err(Refused::Zero);
869        }
870        if id <= self.last {
871            return Err(Refused::NotGreater);
872        }
873
874        // Everything but the field names and values, which is why it is a guess
875        // rather than a measurement: the point is to start a new node before the
876        // current one goes past its limit, not to predict its size exactly.
877        let size: usize = fields
878            .iter()
879            .map(|(f, v)| f.len() + v.len() + 11)
880            .sum::<usize>()
881            + 32;
882
883        let fits = match self.nodes.back() {
884            Some(node) => {
885                let (count, deleted) = counts(&node.lp);
886                node.lp.byte_len() + size < limits.max_node_bytes
887                    && (count + deleted) < limits.max_node_entries as u64
888            }
889            None => false,
890        };
891
892        if !fits {
893            self.nodes.push_back(Node {
894                master: id,
895                lp: master_of(fields),
896            });
897        }
898        let node = self.nodes.back_mut().expect("a node was just made sure of");
899        let same = same_fields(&node.lp, fields);
900        write_entry(&mut node.lp, node.master, id, fields, same);
901        // The entry itself goes on the end and moves nothing, so a group's mark
902        // into this node survives an append and lands on what was just written,
903        // which is the whole point. The counts in front of it are the one part
904        // that can move, and this is where that gets noticed.
905        if bump(&mut node.lp, 1, 0) {
906            self.epoch = self.epoch.wrapping_add(1);
907        }
908
909        self.length += 1;
910        self.added += 1;
911        self.last = id;
912        Ok(())
913    }
914
915    /// Delete the entry with that ID, answering whether there was one.
916    ///
917    /// The bytes stay where they are and a bit says the entry is gone, unless it
918    /// was the last live entry in its node, in which case the node goes.
919    pub fn delete(&mut self, id: Id) -> bool {
920        if !self.remove(id) {
921            return false;
922        }
923        self.max_deleted = self.max_deleted.max(id);
924        true
925    }
926
927    /// Delete an entry, saying what to do about the groups, which is `XDELEX`.
928    ///
929    /// [`Refs::Acked`] is the interesting one and it asks a wider question than
930    /// its name does. An entry is safe to take when no group is holding it in a
931    /// pending list and no group's bookmark is still behind it, because a group
932    /// that has not reached the entry yet has not had its chance at it. So a
933    /// stream with one group sitting at `0-0` refuses every `ACKED` delete, and
934    /// that is a real server's answer and not an over careful reading of it.
935    pub fn delete_ref(&mut self, id: Id, refs: Refs) -> Fate {
936        if refs == Refs::Acked && self.still_wanted(id) {
937            return Fate::Held;
938        }
939        if !self.delete(id) {
940            return Fate::Missing;
941        }
942        if refs == Refs::Drop {
943            self.drop_refs(id);
944        }
945        Fate::Gone
946    }
947
948    /// Acknowledge an entry for one group and then delete it, which is `XACKDEL`.
949    ///
950    /// The acknowledgement is the part that decides the answer. An ID this group
951    /// was not holding is [`Fate::Missing`] whether or not the entry is in the
952    /// stream, and an ID it was holding is never `Missing`, so a caller reading
953    /// the reply is being told about its own pending list and not about the log.
954    ///
955    /// The flag beside the answer is whether an entry really left the log, which
956    /// is not the same question. A group can be holding an ID that has since
957    /// been deleted from under it, and acknowledging that one answers
958    /// [`Fate::Gone`] because the pending list did lose it, while the log lost
959    /// nothing. Only the flag is worth telling a keyspace subscriber about.
960    pub fn ack_delete(&mut self, group: &[u8], id: Id, refs: Refs) -> (Fate, bool) {
961        let Some(g) = self.group_mut(group) else {
962            return (Fate::Missing, false);
963        };
964        if !g.ack(id) {
965            return (Fate::Missing, false);
966        }
967        if refs == Refs::Acked && self.still_wanted(id) {
968            return (Fate::Held, false);
969        }
970        let gone = self.delete(id);
971        if refs == Refs::Drop {
972            self.drop_refs(id);
973        }
974        (Fate::Gone, gone)
975    }
976
977    /// Hand an entry back to a group without acknowledging it, which is `XNACK`.
978    ///
979    /// `force` makes a pending entry out of one that was not pending, and like
980    /// [`Stream::claim`]'s `FORCE` it only works on an entry that is really in
981    /// the stream. Answers whether anything happened, and `None` when there is
982    /// no such group.
983    pub fn nack(&mut self, group: &[u8], id: Id, retry: Retry, force: bool) -> Option<bool> {
984        let here = self.contains(id);
985        let g = self.group_mut(group)?;
986        if g.release(id, retry) {
987            return Some(true);
988        }
989        if force && here {
990            g.force_release(id, retry);
991            return Some(true);
992        }
993        Some(false)
994    }
995
996    /// Whether any group could still be handed `id`, which is what `ACKED` asks.
997    fn still_wanted(&self, id: Id) -> bool {
998        self.groups
999            .iter()
1000            .any(|(_, g)| g.nack(id).is_some() || id > g.last_id())
1001    }
1002
1003    /// Take an ID out of every group's pending list, which is what `DELREF` does.
1004    fn drop_refs(&mut self, id: Id) {
1005        for (_, g) in &mut self.groups {
1006            g.forget(id);
1007        }
1008    }
1009
1010    /// The same without recording it.
1011    ///
1012    /// `XDEL` moves `max-deleted-entry-id` and trimming does not, which is
1013    /// Redis's rule and a reasonable one: that field is there so a reader can
1014    /// tell whether an ID it is holding was taken out from under it, and a
1015    /// trim that took the oldest entries says nothing about that.
1016    fn remove(&mut self, id: Id) -> bool {
1017        let Some(at) = self.node_of(id) else {
1018            return false;
1019        };
1020        let node = &self.nodes[at];
1021        let Some((offset, flags)) = find(&node.lp, node.master, id) else {
1022            return false;
1023        };
1024        if flags & DELETED != 0 {
1025            return false;
1026        }
1027
1028        let (count, _) = counts(&node.lp);
1029        if count == 1 {
1030            self.nodes.remove(at);
1031        } else {
1032            let node = &mut self.nodes[at];
1033            set_int(&mut node.lp, offset, flags | DELETED);
1034            bump(&mut node.lp, -1, 1);
1035        }
1036        // Marking an entry dead writes into the middle of the node, so anything
1037        // behind it has moved and every group's mark is now a byte offset to
1038        // nowhere. Deletes are rare next to appends and reads, so this throws
1039        // all of them away rather than working out which ones survived.
1040        self.epoch = self.epoch.wrapping_add(1);
1041        self.length -= 1;
1042        true
1043    }
1044
1045    /// The five facts every counter a group keeps is worked out from.
1046    ///
1047    /// Read once and carried, rather than asked for again per entry, because
1048    /// [`Stream::first_id`] walks a node and a delivery cannot change any of the
1049    /// five: handing an entry to a consumer neither adds one nor removes one.
1050    fn edges(&self) -> Edges {
1051        Edges {
1052            added: self.added,
1053            length: self.length,
1054            first: self.first_id(),
1055            last: self.last,
1056            max_deleted: self.max_deleted,
1057        }
1058    }
1059
1060    /// How far behind a group is, or `None` when that cannot be worked out.
1061    ///
1062    /// Two ways of answering and the good one is tried first. If the group's
1063    /// bookmark is somewhere the distance from the start of time is exactly
1064    /// known, which is the last ID, past it, or before the first entry left,
1065    /// that distance is the answer. Otherwise the group's own counter will do,
1066    /// but only while nothing has been deleted at or above the bookmark, since
1067    /// a hole ahead of the group means it will read fewer entries than the
1068    /// subtraction is expecting.
1069    ///
1070    /// Both paths and their order were read off Redis 8.10.1 rather than worked
1071    /// out, because reasoning gives the wrong answer on the case that matters:
1072    /// a group sitting at `0-0` on a stream trimmed from five entries to two
1073    /// reports a lag of two and not five, which is the estimate winning over a
1074    /// subtraction that is valid and is further from the truth.
1075    #[must_use]
1076    pub fn lag(&self, group: &Group) -> Option<u64> {
1077        self.edges().lag(group)
1078    }
1079
1080    /// Cut the stream down to `len` entries, dropping the oldest, which is
1081    /// `XTRIM key MAXLEN len`. Answers how many went.
1082    ///
1083    /// `exact` is Redis's `=` against `~`. Without it only whole nodes are
1084    /// dropped, so the stream is left at `len` or a little over and no node is
1085    /// ever rewritten. That is the form to use, and it is why `~` exists.
1086    ///
1087    /// `limit` is Redis's `LIMIT`, which stops the trim once that many entries
1088    /// have gone rather than once the stream is short enough. It exists because
1089    /// a capped stream that has fallen a long way behind would otherwise spend
1090    /// one command dropping millions of entries with the shard doing nothing
1091    /// else, and the next write will carry on where this one stopped.
1092    pub fn trim_maxlen(&mut self, len: u64, exact: bool, limit: Option<u64>) -> u64 {
1093        let mut gone = 0;
1094        while self.length > len && !limit.is_some_and(|cap| gone >= cap) {
1095            let Some(node) = self.nodes.front() else {
1096                break;
1097            };
1098            let (count, _) = counts(&node.lp);
1099            if self.length - count >= len {
1100                self.length -= count;
1101                gone += count;
1102                self.nodes.pop_front();
1103                continue;
1104            }
1105            if !exact {
1106                break;
1107            }
1108            let Some(id) = self.first_id() else { break };
1109            self.remove(id);
1110            gone += 1;
1111        }
1112        gone
1113    }
1114
1115    /// Drop every entry below `id`, which is `XTRIM key MINID id`. Answers how
1116    /// many went.
1117    ///
1118    /// `exact` and `limit` mean what they do for [`Stream::trim_maxlen`].
1119    pub fn trim_minid(&mut self, id: Id, exact: bool, limit: Option<u64>) -> u64 {
1120        let mut gone = 0;
1121        while let Some(node) = self.nodes.front() {
1122            if limit.is_some_and(|cap| gone >= cap) {
1123                break;
1124            }
1125            let (count, _) = counts(&node.lp);
1126            if last_of(node) < id {
1127                self.length -= count;
1128                gone += count;
1129                self.nodes.pop_front();
1130                continue;
1131            }
1132            if !exact {
1133                break;
1134            }
1135            let Some(first) = self.first_id() else { break };
1136            if first >= id {
1137                break;
1138            }
1139            self.remove(first);
1140            gone += 1;
1141        }
1142        gone
1143    }
1144
1145    /// Every live entry from `start` to `end`, both ends included, oldest first.
1146    ///
1147    /// `count` stops the walk early, which is `XRANGE ... COUNT n`. The callback
1148    /// answers whether to carry on, so a caller filling a fixed reply can stop
1149    /// without knowing how many it wanted up front. Answers how many entries the
1150    /// callback saw.
1151    pub fn range<F>(&self, start: Id, end: Id, count: Option<usize>, mut f: F) -> usize
1152    where
1153        F: FnMut(Id, Fields<'_>) -> bool,
1154    {
1155        self.walk(start, end, count, &mut f)
1156    }
1157
1158    /// The same, newest first, which is `XREVRANGE`.
1159    ///
1160    /// `start` and `end` are still the low and the high end of the range, so a
1161    /// caller does not have to swap them and the command layer does, once, where
1162    /// the argument order is Redis's problem.
1163    pub fn rev_range<'s, F>(&'s self, start: Id, end: Id, count: Option<usize>, mut f: F) -> usize
1164    where
1165        F: FnMut(Id, Fields<'_>) -> bool,
1166    {
1167        let mut seen = 0;
1168        // A node is a hundred entries, so buffering one node's worth of marks
1169        // and handing them back in reverse is cheaper and a great deal clearer
1170        // than walking the blob backwards over the entry lengths. The buffer is
1171        // reused across nodes, so the whole reverse scan allocates once.
1172        let mut buf: Vec<(Id, Fields<'s>)> = Vec::new();
1173        // Straight to the node the high end falls in, the same binary search the
1174        // forward walk starts with. Walking back from the newest node instead
1175        // would skip over every node above `end` one at a time, which for a
1176        // window in the middle of a million entries is five thousand nodes
1177        // touched to read a hundred.
1178        let last = self.node_from(end);
1179        for node in self.nodes.iter().take(last + 1).rev() {
1180            // Only reachable when every node is above `end`, since the search
1181            // clamps to the front rather than saying there is nothing.
1182            if node.master > end {
1183                continue;
1184            }
1185            if last_of(node) < start {
1186                break;
1187            }
1188            buf.clear();
1189            each(&node.lp, node.master, None, &mut |id, _, fields| {
1190                if id >= start && id <= end {
1191                    buf.push((id, fields));
1192                }
1193                id <= end
1194            });
1195            for (id, fields) in buf.drain(..).rev() {
1196                if count.is_some_and(|want| seen >= want) {
1197                    return seen;
1198                }
1199                seen += 1;
1200                if !f(id, fields) {
1201                    return seen;
1202                }
1203            }
1204        }
1205        seen
1206    }
1207
1208    /// Whether an entry with this ID is there and live.
1209    ///
1210    /// What `XCLAIM` asks before it hands a pending entry to somebody, since an
1211    /// entry that has been deleted or trimmed away is work nobody can do.
1212    #[must_use]
1213    pub fn contains(&self, id: Id) -> bool {
1214        let Some(at) = self.node_of(id) else {
1215            return false;
1216        };
1217        let node = &self.nodes[at];
1218        find(&node.lp, node.master, id).is_some_and(|(_, flags)| flags & DELETED == 0)
1219    }
1220
1221    /// Make a consumer group, and say whether it was not already there.
1222    ///
1223    /// `XGROUP CREATE`. `last` is where it starts reading after, which is
1224    /// [`Stream::last_id`] for `$` and [`Id::MIN`] for `0`.
1225    pub fn create_group(&mut self, name: &[u8], last: Id, read: Option<u64>) -> bool {
1226        if self.group(name).is_some() {
1227            return false;
1228        }
1229        self.groups.push((name.to_vec(), Group::new(last, read)));
1230        true
1231    }
1232
1233    /// Take a group out, and say whether it was there.
1234    pub fn destroy_group(&mut self, name: &[u8]) -> bool {
1235        let Some(at) = self.groups.iter().position(|(n, _)| n == name) else {
1236            return false;
1237        };
1238        self.groups.remove(at);
1239        true
1240    }
1241
1242    /// One group by name.
1243    #[must_use]
1244    pub fn group(&self, name: &[u8]) -> Option<&Group> {
1245        self.groups
1246            .iter()
1247            .find(|(n, _)| n.as_slice() == name)
1248            .map(|(_, g)| g)
1249    }
1250
1251    /// One group by name, to change.
1252    pub fn group_mut(&mut self, name: &[u8]) -> Option<&mut Group> {
1253        self.groups
1254            .iter_mut()
1255            .find(|(n, _)| n.as_slice() == name)
1256            .map(|(_, g)| g)
1257    }
1258
1259    /// Every group, with its name.
1260    pub fn groups(&self) -> impl Iterator<Item = (&[u8], &Group)> + '_ {
1261        self.groups.iter().map(|(n, g)| (n.as_slice(), g))
1262    }
1263
1264    /// Hand new entries to a consumer, which is `XREADGROUP ... >`.
1265    ///
1266    /// Every entry after the group's bookmark, up to `count`, delivered to
1267    /// `consumer` and written into the pending list as it goes. The consumer is
1268    /// created if it is not there, because a consumer exists by turning up.
1269    ///
1270    /// `noack` is Redis's `NOACK`, which hands the entries over without writing
1271    /// them into the pending list at all. The group still counts them as read,
1272    /// so the lag is the same either way, and the consumer is on its own if it
1273    /// dies holding one.
1274    ///
1275    /// Answers how many entries the callback saw, or `None` when there is no
1276    /// such group.
1277    pub fn read_group<F>(
1278        &mut self,
1279        group: &[u8],
1280        consumer: &[u8],
1281        count: Option<usize>,
1282        noack: bool,
1283        now: u64,
1284        mut f: F,
1285    ) -> Option<usize>
1286    where
1287        F: FnMut(Id, Fields<'_>) -> bool,
1288    {
1289        // Before the split borrow, because it walks a node and the walk below
1290        // holds the nodes. Nothing a delivery does can change any of it.
1291        let edges = self.edges();
1292        // Field by field, so that walking the nodes and writing the pending list
1293        // are two borrows the compiler can see are disjoint.
1294        let Stream {
1295            nodes,
1296            groups,
1297            epoch,
1298            ..
1299        } = self;
1300        let epoch = *epoch;
1301        let (_, g) = groups.iter_mut().find(|(n, _)| n.as_slice() == group)?;
1302        let slot = g.consumer_or_create(consumer, now);
1303        let Some(from) = g.last_id().next() else {
1304            // The bookmark is at the very last ID there is, so there is nothing
1305            // after it and never will be.
1306            g.touch(slot, now, false);
1307            return Some(0);
1308        };
1309        // Where the last read of this group stopped, when it is still good for
1310        // this one. A consumer draining a stream one entry at a time asks for
1311        // the entry right after the one it just got, and this is what saves it
1312        // decoding the node from the front to find it.
1313        let resume = g.resume(epoch, from);
1314        let (seen, mark) = walk_nodes(nodes, from, Id::MAX, count, resume, &mut |id, fields| {
1315            // Worked out before the bookmark moves, because the rule asks where
1316            // the group was when the entry was handed over.
1317            let read = edges.on_deliver(g, id);
1318            if noack {
1319                g.skip(id);
1320            } else {
1321                g.deliver(slot, id, now);
1322            }
1323            g.set_read(read);
1324            f(id, fields)
1325        });
1326        let next = g.last_id().next();
1327        g.set_resume(match (mark, next) {
1328            (Some((master, byte)), Some(next)) => Some(Cursor {
1329                epoch,
1330                next,
1331                master,
1332                byte,
1333            }),
1334            _ => None,
1335        });
1336        g.touch(slot, now, seen > 0);
1337        Some(seen)
1338    }
1339
1340    /// Re-read what a consumer is already holding, which is `XREADGROUP` with an
1341    /// ID rather than `>`.
1342    ///
1343    /// Every pending entry of that consumer after `after`, oldest first. Each
1344    /// one counts as handed out again, so its delivery time is reset and its
1345    /// count goes up. That is Redis's behaviour, checked rather than assumed,
1346    /// and it is the right one: the count is how many times a consumer has been
1347    /// told to do this work, and a consumer re-reading its backlog after a
1348    /// restart has been told again.
1349    ///
1350    /// An entry that has since been deleted or trimmed is still in the pending
1351    /// list and is handed to the callback with no fields, which is the null
1352    /// Redis puts in the reply. Clearing those out is [`Stream::claim`]'s job
1353    /// and not this one.
1354    pub fn read_group_pending<F>(
1355        &mut self,
1356        group: &[u8],
1357        consumer: &[u8],
1358        after: Id,
1359        count: Option<usize>,
1360        now: u64,
1361        mut f: F,
1362    ) -> Option<usize>
1363    where
1364        F: FnMut(Id, Option<Fields<'_>>) -> bool,
1365    {
1366        // Which IDs, decided before anything is touched, so that the redelivery
1367        // and the walk are two passes over a small list rather than one pass
1368        // holding the group and the nodes at the same time.
1369        //
1370        // The consumer is created rather than looked up, because a history read
1371        // by a name nobody has used is an empty list and not a missing group. A
1372        // worker that restarts under a new name and asks for its own backlog
1373        // first is exactly that case, and Redis answers it with an empty list
1374        // and the consumer left behind.
1375        let g = self.group_mut(group)?;
1376        let slot = g.consumer_or_create(consumer, now);
1377        let ids: Vec<Id> = g
1378            .consumer(slot)
1379            .expect("the slot that was just made")
1380            .pending()
1381            .filter(|&id| id > after)
1382            .take(count.unwrap_or(usize::MAX))
1383            .collect();
1384        for &id in &ids {
1385            g.redeliver(id, now);
1386        }
1387        g.touch(slot, now, !ids.is_empty());
1388
1389        let mut seen = 0;
1390        for &id in &ids {
1391            seen += 1;
1392            let mut go = true;
1393            let mut found = false;
1394            self.walk(id, id, Some(1), &mut |got, fields| {
1395                found = true;
1396                go = f(got, Some(fields));
1397                false
1398            });
1399            if !found {
1400                go = f(id, None);
1401            }
1402            if !go {
1403                break;
1404            }
1405        }
1406        Some(seen)
1407    }
1408
1409    /// Move pending entries to a consumer, which is `XCLAIM`.
1410    ///
1411    /// Only entries idle at least `min_idle` move. `time` is what the delivery
1412    /// time becomes, `retry` replaces the delivery count when it is given, and
1413    /// `bump` says whether to add one to it, which `JUSTID` turns off. `force`
1414    /// makes a pending entry for an ID that is in the stream but was not
1415    /// pending.
1416    ///
1417    /// An ID that is pending but no longer in the stream is dropped from the
1418    /// pending list rather than claimed, and reported through `gone`, which is
1419    /// what Redis does and what stops a deleted entry being handed round
1420    /// forever. Answers the IDs that moved.
1421    #[allow(clippy::too_many_arguments)]
1422    pub fn claim(
1423        &mut self,
1424        group: &[u8],
1425        consumer: &[u8],
1426        ids: &[Id],
1427        min_idle: u64,
1428        time: u64,
1429        retry: Option<u64>,
1430        bump: bool,
1431        force: bool,
1432        now: u64,
1433        gone: &mut Vec<Id>,
1434    ) -> Option<Vec<Id>> {
1435        // Before the loop, so that a claim which takes nothing still leaves the
1436        // consumer behind. Redis creates it either way, and an `XAUTOCLAIM`
1437        // against an empty pending list is the ordinary way that happens: the
1438        // consumer turns up in `XINFO CONSUMERS` straight after, holding
1439        // nothing.
1440        self.group_mut(group)?.consumer_or_create(consumer, now);
1441        let mut took = Vec::new();
1442        for &id in ids {
1443            let here = self.contains(id);
1444            let g = self.group_mut(group)?;
1445            let slot = g.consumer_or_create(consumer, now);
1446            match g.nack(id) {
1447                Some(nack) => {
1448                    if !here {
1449                        g.forget(id);
1450                        gone.push(id);
1451                        continue;
1452                    }
1453                    if nack.idle(now) < min_idle {
1454                        continue;
1455                    }
1456                    if g.claim(id, slot, time, retry, bump) {
1457                        took.push(id);
1458                    }
1459                }
1460                None => {
1461                    // FORCE makes one out of nothing, but only for an entry that
1462                    // is really there. Redis ignores the rest in silence.
1463                    if force && here && g.force(id, slot, time, retry.unwrap_or(1)) {
1464                        took.push(id);
1465                    }
1466                }
1467            }
1468        }
1469        // Active only when something moved, which is the same rule a read
1470        // follows. A claim that found nothing idle enough leaves the consumer
1471        // reading as never active.
1472        if !took.is_empty() {
1473            let g = self.group_mut(group).expect("the group found a moment ago");
1474            let slot = g.consumer_or_create(consumer, now);
1475            g.touch(slot, now, true);
1476        }
1477        Some(took)
1478    }
1479
1480    /// Sweep the pending list for stale entries and claim them, which is
1481    /// `XAUTOCLAIM`.
1482    ///
1483    /// Starts at `start` and takes up to `count` entries that have been idle at
1484    /// least `min_idle`. Answers where a following call should carry on from,
1485    /// which is `None` at the end of the list, along with what was claimed and
1486    /// what was dropped for no longer being in the stream.
1487    #[allow(clippy::too_many_arguments)]
1488    pub fn autoclaim(
1489        &mut self,
1490        group: &[u8],
1491        consumer: &[u8],
1492        start: Id,
1493        min_idle: u64,
1494        count: usize,
1495        bump: bool,
1496        now: u64,
1497        gone: &mut Vec<Id>,
1498    ) -> Option<(Option<Id>, Vec<Id>)> {
1499        let mut ids = Vec::new();
1500        let cursor = self
1501            .group(group)?
1502            .claimable(start, min_idle, now, count, &mut ids);
1503        let took = self.claim(
1504            group, consumer, &ids, min_idle, now, None, bump, false, now, gone,
1505        )?;
1506        Some((cursor, took))
1507    }
1508
1509    /// How many bytes the entries and the groups take, not counting this struct.
1510    ///
1511    /// The name is the one every other body in this crate uses, because the
1512    /// keyspace asks all of them the same question through one trait and a
1513    /// stream that answered it under a different name would need its own arm.
1514    #[must_use]
1515    pub fn memory_bytes(&self) -> usize {
1516        let nodes: usize = self
1517            .nodes
1518            .iter()
1519            .map(|node| node.lp.byte_len() + std::mem::size_of::<Node>())
1520            .sum();
1521        let groups: usize = self
1522            .groups
1523            .iter()
1524            .map(|(name, g)| {
1525                name.capacity() + std::mem::size_of::<(Vec<u8>, Group)>() + g.memory_bytes()
1526            })
1527            .sum();
1528        nodes + groups
1529    }
1530
1531    /// How many nodes there are, which only a test and `XINFO STREAM FULL` care
1532    /// about.
1533    #[must_use]
1534    pub fn nodes(&self) -> usize {
1535        self.nodes.len()
1536    }
1537
1538    /// The shared walk behind [`Stream::range`] and [`Stream::first_id`].
1539    fn walk<F>(&self, start: Id, end: Id, count: Option<usize>, f: &mut F) -> usize
1540    where
1541        F: FnMut(Id, Fields<'_>) -> bool,
1542    {
1543        walk_nodes(&self.nodes, start, end, count, None, f).0
1544    }
1545
1546    /// The first node that can hold an entry at or after `id`.
1547    fn node_from(&self, id: Id) -> usize {
1548        node_from(&self.nodes, id)
1549    }
1550
1551    /// The node that would hold `id`, or `None` when no node covers it.
1552    fn node_of(&self, id: Id) -> Option<usize> {
1553        let at = self.node_from(id);
1554        let node = self.nodes.get(at)?;
1555        (node.master <= id && id <= last_of(node)).then_some(at)
1556    }
1557}
1558
1559/// The first node that can hold an entry at or after `id`.
1560///
1561/// The binary search the module docs are about. `partition_point` answers how
1562/// many nodes start strictly before `id`, and the one before that is the one
1563/// `id` would be in, since a node holds everything from its master ID up to the
1564/// next node's.
1565///
1566/// Free rather than a method so that a group read can hold the nodes and the
1567/// groups at the same time, which it has to because it walks the one to write
1568/// into the other.
1569fn node_from(nodes: &VecDeque<Node>, id: Id) -> usize {
1570    let after = nodes.partition_point(|node| node.master <= id);
1571    after.saturating_sub(1)
1572}
1573
1574/// Every live entry from `start` to `end`, both included, oldest first.
1575///
1576/// Free for the same reason [`node_from`] is.
1577/// `resume` is a node's master ID and a byte offset inside it from an earlier
1578/// walk, and the answer carries the same pair back for where this one stopped.
1579/// Only a group read has one, and it is the caller's job to have checked that
1580/// nothing has moved the bytes since.
1581fn walk_nodes<F>(
1582    nodes: &VecDeque<Node>,
1583    start: Id,
1584    end: Id,
1585    count: Option<usize>,
1586    resume: Option<(Id, usize)>,
1587    f: &mut F,
1588) -> (usize, Option<(Id, usize)>)
1589where
1590    F: FnMut(Id, Fields<'_>) -> bool,
1591{
1592    let mut seen = 0;
1593    let mut stop = false;
1594    let first = node_from(nodes, start);
1595    // Only when the mark is about the node this walk is starting in. Node master
1596    // IDs are unique and never reused, so a node that has since been trimmed
1597    // away cannot be mistaken for the one that took its place.
1598    let mut from =
1599        resume.and_then(|(master, byte)| (nodes.get(first)?.master == master).then_some(byte));
1600    let mut mark = None;
1601    for node in nodes.iter().skip(first) {
1602        if node.master > end {
1603            break;
1604        }
1605        let at = each(&node.lp, node.master, from.take(), &mut |id, _, fields| {
1606            if id > end {
1607                stop = true;
1608                return false;
1609            }
1610            if id < start {
1611                return true;
1612            }
1613            if count.is_some_and(|want| seen >= want) {
1614                stop = true;
1615                return false;
1616            }
1617            seen += 1;
1618            if !f(id, fields) {
1619                stop = true;
1620                return false;
1621            }
1622            true
1623        });
1624        mark = Some((node.master, at));
1625        if stop {
1626            break;
1627        }
1628    }
1629    (seen, mark)
1630}
1631
1632/// The field names and values of one entry.
1633///
1634/// Two walks rather than one because an entry that shares the node's field names
1635/// reads them from the master entry and its values from itself, and the whole
1636/// point of that layout is that the names are not copied per entry. Neither walk
1637/// allocates and neither is a copy.
1638#[derive(Debug, Clone)]
1639pub struct Fields<'a> {
1640    /// Where the names come from, when they are not interleaved with the values.
1641    names: Option<listpack::Iter<'a>>,
1642    body: listpack::Iter<'a>,
1643    left: usize,
1644}
1645
1646impl<'a> Iterator for Fields<'a> {
1647    type Item = (Entry<'a>, Entry<'a>);
1648
1649    fn next(&mut self) -> Option<(Entry<'a>, Entry<'a>)> {
1650        if self.left == 0 {
1651            return None;
1652        }
1653        self.left -= 1;
1654        let name = match &mut self.names {
1655            Some(names) => names.next()?,
1656            None => self.body.next()?,
1657        };
1658        Some((name, self.body.next()?))
1659    }
1660
1661    fn size_hint(&self) -> (usize, Option<usize>) {
1662        (self.left, Some(self.left))
1663    }
1664}
1665
1666impl ExactSizeIterator for Fields<'_> {}
1667
1668impl Fields<'_> {
1669    /// Whether there are no fields left.
1670    #[must_use]
1671    #[inline]
1672    pub fn is_empty(&self) -> bool {
1673        self.left == 0
1674    }
1675}
1676
1677/// A fresh node whose master fields are this entry's.
1678fn master_of(fields: &[(&[u8], &[u8])]) -> Listpack {
1679    let mut lp = Listpack::new();
1680    push_int(&mut lp, 0);
1681    push_int(&mut lp, 0);
1682    push_int(&mut lp, fields.len() as i64);
1683    for (name, _) in fields {
1684        lp.push(name);
1685    }
1686    push_int(&mut lp, 0);
1687    lp
1688}
1689
1690/// Whether these fields are exactly the node's master fields, in order.
1691fn same_fields(lp: &Listpack, fields: &[(&[u8], &[u8])]) -> bool {
1692    let mut it = lp.iter();
1693    let (_, _, want) = match (it.next(), it.next(), it.next()) {
1694        (Some(_), Some(_), Some(Entry::Int(n))) => ((), (), n),
1695        _ => return false,
1696    };
1697    if want != fields.len() as i64 {
1698        return false;
1699    }
1700    fields.iter().all(|(name, _)| match it.next() {
1701        Some(Entry::Str(s)) => s == *name,
1702        Some(Entry::Int(n)) => {
1703            let mut buf = [0u8; DIGITS_MAX];
1704            i64_digits(&mut buf, n) == *name
1705        }
1706        None => false,
1707    })
1708}
1709
1710/// The master entry's live and deleted counts.
1711fn counts(lp: &Listpack) -> (u64, u64) {
1712    let mut it = lp.iter();
1713    let count = int_or_zero(it.next());
1714    let deleted = int_or_zero(it.next());
1715    (count.max(0) as u64, deleted.max(0) as u64)
1716}
1717
1718/// Add to the master entry's two counts, answering whether that moved any bytes.
1719///
1720/// Both counts sit in front of every entry in the node, so a write that encodes
1721/// to a different width than what was there shifts the whole rest of the blob
1722/// along and every [`Cursor`] into it is a byte offset to nowhere. It usually
1723/// does not: a node holds a hundred entries by default and a listpack keeps
1724/// anything under a hundred and twenty eight in one byte, so an append writes
1725/// the same width it read almost every time. Almost, because the node limits are
1726/// the caller's to set.
1727fn bump(lp: &mut Listpack, live: i64, dead: i64) -> bool {
1728    let was = lp.byte_len();
1729    let (count, deleted) = counts(lp);
1730    let mut buf = [0u8; DIGITS_MAX];
1731    let at = count as i64 + live;
1732    lp.replace(0, u64_digits(&mut buf, at.max(0) as u64));
1733    let at = deleted as i64 + dead;
1734    lp.replace(1, u64_digits(&mut buf, at.max(0) as u64));
1735    lp.byte_len() != was
1736}
1737
1738/// An entry as an integer, or zero for anything else.
1739fn int_or_zero(entry: Option<Entry<'_>>) -> i64 {
1740    match entry {
1741        Some(Entry::Int(n)) => n,
1742        _ => 0,
1743    }
1744}
1745
1746/// Append an integer, which the listpack encodes as one because it parses as one.
1747fn push_int(lp: &mut Listpack, n: i64) {
1748    let mut buf = [0u8; DIGITS_MAX];
1749    lp.push(i64_digits(&mut buf, n));
1750}
1751
1752/// Overwrite the element at `index` with an integer.
1753fn set_int(lp: &mut Listpack, index: usize, n: i64) {
1754    let mut buf = [0u8; DIGITS_MAX];
1755    lp.replace(index, i64_digits(&mut buf, n));
1756}
1757
1758/// Write one entry onto the end of a node.
1759fn write_entry(lp: &mut Listpack, master: Id, id: Id, fields: &[(&[u8], &[u8])], same: bool) {
1760    let flags = if same { LIVE | SAME_FIELDS } else { LIVE };
1761    push_int(lp, flags);
1762    // Both halves as a plain difference from the master, wrapping, which is what
1763    // Redis writes. The sequence often goes down when the millisecond goes up,
1764    // so that difference is usually negative and it does not matter: adding it
1765    // back is the exact inverse whichever way it went.
1766    push_int(lp, id.ms.wrapping_sub(master.ms) as i64);
1767    push_int(lp, id.seq.wrapping_sub(master.seq) as i64);
1768    if same {
1769        for (_, value) in fields {
1770            lp.push(value);
1771        }
1772        push_int(lp, fields.len() as i64 + 3);
1773    } else {
1774        push_int(lp, fields.len() as i64);
1775        for (name, value) in fields {
1776            lp.push(name);
1777            lp.push(value);
1778        }
1779        push_int(lp, fields.len() as i64 * 2 + 4);
1780    }
1781}
1782
1783/// The greatest ID in a node, live or not.
1784fn last_of(node: &Node) -> Id {
1785    let mut last = node.master;
1786    each(&node.lp, node.master, None, &mut |id, _, _| {
1787        last = id;
1788        true
1789    });
1790    last
1791}
1792
1793/// Where the entry with that ID starts, and its flags, or `None`.
1794///
1795/// The offset is a listpack element index rather than a byte offset, because
1796/// that is what `replace` takes.
1797fn find(lp: &Listpack, master: Id, id: Id) -> Option<(usize, i64)> {
1798    let mut at = None;
1799    walk_node(lp, master, None, &mut |mark, flags, _| {
1800        if mark.id == id {
1801            // Always a `Some`, because this walk starts at the front, and the
1802            // `if let` rather than an unwrap keeps that a fact about this call
1803            // rather than something the mark has to promise everybody.
1804            if let Some(index) = mark.index {
1805                at = Some((index, flags));
1806            }
1807            return false;
1808        }
1809        mark.id < id
1810    });
1811    at
1812}
1813
1814/// Every entry in a node, live ones only, oldest first.
1815/// `from` and the answer are the resume mark [`walk_node`] takes and gives back.
1816fn each<'a, F>(lp: &'a Listpack, master: Id, from: Option<usize>, f: &mut F) -> usize
1817where
1818    F: FnMut(Id, usize, Fields<'a>) -> bool,
1819{
1820    walk_node(lp, master, from, &mut |mark, flags, fields| {
1821        if flags & DELETED != 0 {
1822            return true;
1823        }
1824        f(mark.id, mark.byte, fields)
1825    })
1826}
1827
1828/// Every entry in a node, deleted ones included, with its element index.
1829///
1830/// One forward walk of the whole blob. Nothing here reaches into the middle by
1831/// index, because a listpack index is a walk from the front and doing that per
1832/// entry would turn a node scan into a quadratic one.
1833fn walk_node<'a, F>(lp: &'a Listpack, master: Id, from: Option<usize>, f: &mut F) -> usize
1834where
1835    F: FnMut(Mark, i64, Fields<'a>) -> bool,
1836{
1837    let mut it = lp.iter();
1838    let (Some(_), Some(_), Some(Entry::Int(masters))) = (it.next(), it.next(), it.next()) else {
1839        return it.offset();
1840    };
1841    let masters = masters.max(0) as usize;
1842    // The master field names, kept as a mark to hand to the entries that share
1843    // them, and then stepped over along with the zero that ends the master entry.
1844    let names = it.clone();
1845    let mut index = MASTER_FIELDS;
1846    for _ in 0..=masters {
1847        if it.next().is_none() {
1848            return it.offset();
1849        }
1850        index += 1;
1851    }
1852    // A resume skips straight to where a previous walk stopped. The header above
1853    // still has to be read whichever way this is called, because the field names
1854    // a shared entry hands back live in it, but that is a handful of elements
1855    // rather than every entry in the node.
1856    let counting = from.is_none();
1857    if let Some(byte) = from.filter(|byte| *byte > it.offset()) {
1858        it = lp.iter_at(byte);
1859    }
1860
1861    loop {
1862        let at = it.offset();
1863        let element = index;
1864        let (Some(Entry::Int(flags)), Some(Entry::Int(ms)), Some(Entry::Int(seq))) =
1865            (it.next(), it.next(), it.next())
1866        else {
1867            return at;
1868        };
1869        index += 3;
1870        let id = Id {
1871            ms: master.ms.wrapping_add(ms as u64),
1872            seq: master.seq.wrapping_add(seq as u64),
1873        };
1874
1875        let same = flags & SAME_FIELDS != 0;
1876        let (fields, skip) = if same {
1877            let fields = Fields {
1878                names: Some(names.clone()),
1879                body: it.clone(),
1880                left: masters,
1881            };
1882            (fields, masters + 1)
1883        } else {
1884            let Some(Entry::Int(n)) = it.next() else {
1885                return at;
1886            };
1887            index += 1;
1888            let fields = Fields {
1889                names: None,
1890                body: it.clone(),
1891                left: n.max(0) as usize,
1892            };
1893            (fields, n.max(0) as usize * 2 + 1)
1894        };
1895
1896        let mark = Mark {
1897            id,
1898            index: counting.then_some(element),
1899            byte: at,
1900        };
1901        if !f(mark, flags, fields) {
1902            return at;
1903        }
1904        for _ in 0..skip {
1905            if it.next().is_none() {
1906                return it.offset();
1907            }
1908            index += 1;
1909        }
1910    }
1911}
1912
1913/// Where one entry sits inside its node.
1914#[derive(Debug, Clone, Copy)]
1915struct Mark {
1916    /// The entry's ID, worked out from the node's master and the two
1917    /// differences the entry stores.
1918    id: Id,
1919    /// Which listpack element the entry's flags are, for the writes that reach
1920    /// back in by index. Only filled in on a walk that started at the front,
1921    /// because counting elements from a resume point would give a number
1922    /// [`Listpack::replace`] cannot use.
1923    index: Option<usize>,
1924    /// Where in the blob the entry's flags element starts, which is what
1925    /// [`Listpack::iter_at`] takes to come back here.
1926    byte: usize,
1927}
1928
1929#[cfg(test)]
1930mod tests {
1931    use super::*;
1932    use crate::many;
1933
1934    fn pairs<'a>(of: &'a [(&'a str, &'a str)]) -> Vec<(&'a [u8], &'a [u8])> {
1935        of.iter()
1936            .map(|(f, v)| (f.as_bytes(), v.as_bytes()))
1937            .collect()
1938    }
1939
1940    /// One entry, with its fields owned, which is what the tests compare.
1941    type Flat = (Id, Vec<(Vec<u8>, Vec<u8>)>);
1942
1943    /// Everything in the stream, oldest first.
1944    fn dump(s: &Stream) -> Vec<Flat> {
1945        let mut out = Vec::new();
1946        s.range(Id::MIN, Id::MAX, None, |id, fields| {
1947            out.push((id, fields.map(|(f, v)| (f.to_vec(), v.to_vec())).collect()));
1948            true
1949        });
1950        out
1951    }
1952
1953    fn add(s: &mut Stream, ms: u64, seq: u64, fields: &[(&str, &str)]) {
1954        s.append(Id::new(ms, seq), &pairs(fields), Limits::default())
1955            .expect("an append");
1956    }
1957
1958    #[test]
1959    fn an_entry_comes_back_as_it_went_in() {
1960        let mut s = Stream::new();
1961        add(&mut s, 5, 0, &[("sensor", "1"), ("reading", "23.4")]);
1962        let got = dump(&s);
1963        assert_eq!(got.len(), 1);
1964        assert_eq!(got[0].0, Id::new(5, 0));
1965        assert_eq!(
1966            got[0].1,
1967            vec![
1968                (b"sensor".to_vec(), b"1".to_vec()),
1969                (b"reading".to_vec(), b"23.4".to_vec())
1970            ]
1971        );
1972    }
1973
1974    #[test]
1975    fn entries_come_back_in_order() {
1976        let mut s = Stream::new();
1977        for ms in 1..200u64 {
1978            add(&mut s, ms, 0, &[("n", "x")]);
1979        }
1980        let got = dump(&s);
1981        assert_eq!(got.len(), 199);
1982        for (at, (id, _)) in got.iter().enumerate() {
1983            assert_eq!(*id, Id::new(at as u64 + 1, 0));
1984        }
1985        assert_eq!(s.len(), 199);
1986        assert_eq!(s.added(), 199);
1987        assert_eq!(s.last_id(), Id::new(199, 0));
1988    }
1989
1990    /// The whole reason a node holds a hundred entries.
1991    #[test]
1992    fn a_long_stream_is_many_nodes() {
1993        let mut s = Stream::new();
1994        for ms in 1..=1000u64 {
1995            add(&mut s, ms, 0, &[("n", "x")]);
1996        }
1997        assert_eq!(s.nodes(), 10, "a hundred entries a node");
1998        assert_eq!(dump(&s).len(), 1000);
1999    }
2000
2001    /// Sharing the field names is worth most of the entry on a real stream.
2002    #[test]
2003    fn the_field_names_are_not_stored_twice() {
2004        let mut shared = Stream::new();
2005        let mut apart = Stream::new();
2006        for ms in 1..=100u64 {
2007            add(
2008                &mut shared,
2009                ms,
2010                0,
2011                &[("temperature_celsius", "21"), ("relative_humidity", "44")],
2012            );
2013            let a = format!("temperature_celsius{ms}");
2014            let b = format!("relative_humidity{ms}");
2015            apart
2016                .append(
2017                    Id::new(ms, 0),
2018                    &[(a.as_bytes(), b"21"), (b.as_bytes(), b"44")],
2019                    Limits::default(),
2020                )
2021                .expect("an append");
2022        }
2023        assert!(
2024            shared.memory_bytes() * 3 < apart.memory_bytes(),
2025            "{} against {}",
2026            shared.memory_bytes(),
2027            apart.memory_bytes()
2028        );
2029    }
2030
2031    /// What a real stream entry costs, so that a change that quietly doubles it
2032    /// has somewhere to fail.
2033    ///
2034    /// Ten thousand `sensor` and `reading` entries a millisecond apart, which is
2035    /// the shape the benchmark uses and the shape a stream almost always has.
2036    /// That measures 23.9 bytes an entry today, against 48.7 for the same
2037    /// entries with field names that cannot be shared. Thirty two is a bar with
2038    /// room in it rather than a target, because the point is to catch a
2039    /// regression and not to freeze the encoder.
2040    #[test]
2041    fn an_entry_costs_about_two_dozen_bytes() {
2042        let mut s = Stream::new();
2043        for ms in 1..=10_000u64 {
2044            let reading = format!("{:.3}", ms as f64 / 7.0);
2045            s.append(
2046                Id::new(ms, 0),
2047                &[(b"sensor", b"a4"), (b"reading", reading.as_bytes())],
2048                Limits::default(),
2049            )
2050            .expect("an append");
2051        }
2052        let each = s.memory_bytes() as f64 / 10_000.0;
2053        assert!(each < 32.0, "{each:.2} bytes an entry");
2054    }
2055
2056    #[test]
2057    fn an_entry_with_its_own_fields_still_reads_back() {
2058        let mut s = Stream::new();
2059        add(&mut s, 1, 0, &[("a", "1"), ("b", "2")]);
2060        add(&mut s, 2, 0, &[("c", "3")]);
2061        add(&mut s, 3, 0, &[("a", "4"), ("b", "5")]);
2062        let got = dump(&s);
2063        assert_eq!(got[1].1, vec![(b"c".to_vec(), b"3".to_vec())]);
2064        assert_eq!(
2065            got[2].1,
2066            vec![
2067                (b"a".to_vec(), b"4".to_vec()),
2068                (b"b".to_vec(), b"5".to_vec())
2069            ]
2070        );
2071    }
2072
2073    /// The same names in a different order is not the same shape.
2074    #[test]
2075    fn the_order_of_the_names_matters() {
2076        let mut s = Stream::new();
2077        add(&mut s, 1, 0, &[("a", "1"), ("b", "2")]);
2078        add(&mut s, 2, 0, &[("b", "3"), ("a", "4")]);
2079        let got = dump(&s);
2080        assert_eq!(
2081            got[1].1,
2082            vec![
2083                (b"b".to_vec(), b"3".to_vec()),
2084                (b"a".to_vec(), b"4".to_vec())
2085            ]
2086        );
2087    }
2088
2089    #[test]
2090    fn an_id_must_beat_the_last_one() {
2091        let mut s = Stream::new();
2092        add(&mut s, 5, 5, &[("n", "x")]);
2093        let f = pairs(&[("n", "x")]);
2094        for id in [Id::new(5, 5), Id::new(5, 4), Id::new(1, 0)] {
2095            assert_eq!(
2096                s.append(id, &f, Limits::default()),
2097                Err(Refused::NotGreater),
2098                "{id:?}"
2099            );
2100        }
2101        assert_eq!(s.append(Id::new(5, 6), &f, Limits::default()), Ok(()));
2102    }
2103
2104    #[test]
2105    fn nothing_can_be_added_at_zero() {
2106        let mut s = Stream::new();
2107        assert_eq!(
2108            s.append(Id::MIN, &pairs(&[("n", "x")]), Limits::default()),
2109            Err(Refused::Zero)
2110        );
2111    }
2112
2113    #[test]
2114    fn a_range_takes_both_ends() {
2115        let mut s = Stream::new();
2116        for ms in 1..=10u64 {
2117            add(&mut s, ms, 0, &[("n", "x")]);
2118        }
2119        let mut seen = Vec::new();
2120        s.range(Id::new(3, 0), Id::new(6, 0), None, |id, _| {
2121            seen.push(id.ms);
2122            true
2123        });
2124        assert_eq!(seen, vec![3, 4, 5, 6]);
2125    }
2126
2127    /// A range whose ends fall between entries, and one that misses entirely.
2128    #[test]
2129    fn a_range_that_lands_between_entries() {
2130        let mut s = Stream::new();
2131        for ms in [10u64, 20, 30] {
2132            add(&mut s, ms, 0, &[("n", "x")]);
2133        }
2134        let mut seen = Vec::new();
2135        s.range(Id::new(11, 0), Id::new(29, 0), None, |id, _| {
2136            seen.push(id.ms);
2137            true
2138        });
2139        assert_eq!(seen, vec![20]);
2140
2141        let mut none = 0;
2142        s.range(Id::new(31, 0), Id::MAX, None, |_, _| {
2143            none += 1;
2144            true
2145        });
2146        assert_eq!(none, 0);
2147    }
2148
2149    #[test]
2150    fn a_count_stops_the_walk() {
2151        let mut s = Stream::new();
2152        for ms in 1..=500u64 {
2153            add(&mut s, ms, 0, &[("n", "x")]);
2154        }
2155        let mut seen = 0;
2156        let answered = s.range(Id::MIN, Id::MAX, Some(7), |_, _| {
2157            seen += 1;
2158            true
2159        });
2160        assert_eq!((seen, answered), (7, 7));
2161    }
2162
2163    #[test]
2164    fn the_callback_can_stop_the_walk() {
2165        let mut s = Stream::new();
2166        for ms in 1..=500u64 {
2167            add(&mut s, ms, 0, &[("n", "x")]);
2168        }
2169        let mut seen = 0;
2170        s.range(Id::MIN, Id::MAX, None, |_, _| {
2171            seen += 1;
2172            seen < 3
2173        });
2174        assert_eq!(seen, 3);
2175    }
2176
2177    #[test]
2178    fn a_reverse_range_is_the_forward_one_backwards() {
2179        let mut s = Stream::new();
2180        for ms in 1..=350u64 {
2181            add(&mut s, ms, 0, &[("n", "x")]);
2182        }
2183        let mut forward = Vec::new();
2184        s.range(Id::new(50, 0), Id::new(300, 0), None, |id, _| {
2185            forward.push(id);
2186            true
2187        });
2188        let mut back = Vec::new();
2189        s.rev_range(Id::new(50, 0), Id::new(300, 0), None, |id, _| {
2190            back.push(id);
2191            true
2192        });
2193        back.reverse();
2194        assert_eq!(forward, back);
2195        assert_eq!(forward.len(), 251);
2196    }
2197
2198    #[test]
2199    fn a_reverse_range_takes_a_count_from_the_new_end() {
2200        let mut s = Stream::new();
2201        for ms in 1..=350u64 {
2202            add(&mut s, ms, 0, &[("n", "x")]);
2203        }
2204        let mut seen = Vec::new();
2205        s.rev_range(Id::MIN, Id::MAX, Some(3), |id, _| {
2206            seen.push(id.ms);
2207            true
2208        });
2209        assert_eq!(seen, vec![350, 349, 348]);
2210    }
2211
2212    #[test]
2213    fn deleting_leaves_the_rest_readable() {
2214        let mut s = Stream::new();
2215        for ms in 1..=10u64 {
2216            add(&mut s, ms, 0, &[("n", "x")]);
2217        }
2218        assert!(s.delete(Id::new(4, 0)));
2219        assert!(!s.delete(Id::new(4, 0)), "twice is not twice");
2220        assert!(!s.delete(Id::new(99, 0)));
2221        assert_eq!(s.len(), 9);
2222        assert_eq!(s.max_deleted_id(), Id::new(4, 0));
2223        let seen: Vec<u64> = dump(&s).iter().map(|(id, _)| id.ms).collect();
2224        assert_eq!(seen, vec![1, 2, 3, 5, 6, 7, 8, 9, 10]);
2225    }
2226
2227    #[test]
2228    fn deleting_the_first_entry_moves_the_first_id() {
2229        let mut s = Stream::new();
2230        for ms in 1..=5u64 {
2231            add(&mut s, ms, 0, &[("n", "x")]);
2232        }
2233        assert_eq!(s.first_id(), Some(Id::new(1, 0)));
2234        s.delete(Id::new(1, 0));
2235        assert_eq!(s.first_id(), Some(Id::new(2, 0)));
2236    }
2237
2238    /// The last live entry going takes the node with it.
2239    #[test]
2240    fn emptying_a_node_drops_it() {
2241        let mut s = Stream::new();
2242        for ms in 1..=250u64 {
2243            add(&mut s, ms, 0, &[("n", "x")]);
2244        }
2245        assert_eq!(s.nodes(), 3);
2246        for ms in 1..=100u64 {
2247            assert!(s.delete(Id::new(ms, 0)), "{ms}");
2248        }
2249        assert_eq!(s.nodes(), 2);
2250        assert_eq!(s.len(), 150);
2251        assert_eq!(dump(&s).len(), 150);
2252    }
2253
2254    /// The stream can be empty and still know what came before.
2255    #[test]
2256    fn an_emptied_stream_still_remembers_its_last_id() {
2257        let mut s = Stream::new();
2258        add(&mut s, 7, 0, &[("n", "x")]);
2259        s.delete(Id::new(7, 0));
2260        assert!(s.is_empty());
2261        assert_eq!(s.last_id(), Id::new(7, 0));
2262        assert_eq!(s.added(), 1);
2263        assert_eq!(s.first_id(), None);
2264        assert_eq!(
2265            s.append(Id::new(7, 0), &pairs(&[("n", "x")]), Limits::default()),
2266            Err(Refused::NotGreater),
2267            "a deleted id is still used up"
2268        );
2269    }
2270
2271    #[test]
2272    fn trimming_to_a_length_takes_the_oldest() {
2273        let mut s = Stream::new();
2274        for ms in 1..=1000u64 {
2275            add(&mut s, ms, 0, &[("n", "x")]);
2276        }
2277        assert_eq!(s.trim_maxlen(150, true, None), 850);
2278        assert_eq!(s.len(), 150);
2279        assert_eq!(s.first_id(), Some(Id::new(851, 0)));
2280        assert_eq!(s.last_id(), Id::new(1000, 0));
2281    }
2282
2283    /// The point of `~`: whole nodes only, so nothing is rewritten.
2284    #[test]
2285    fn an_approximate_trim_stops_at_a_node() {
2286        let mut s = Stream::new();
2287        for ms in 1..=1000u64 {
2288            add(&mut s, ms, 0, &[("n", "x")]);
2289        }
2290        assert_eq!(s.trim_maxlen(150, false, None), 800);
2291        assert_eq!(s.len(), 200, "left at the node boundary above 150");
2292        assert_eq!(s.nodes(), 2);
2293    }
2294
2295    #[test]
2296    fn trimming_to_a_length_that_is_already_met_does_nothing() {
2297        let mut s = Stream::new();
2298        for ms in 1..=10u64 {
2299            add(&mut s, ms, 0, &[("n", "x")]);
2300        }
2301        assert_eq!(s.trim_maxlen(50, true, None), 0);
2302        assert_eq!(s.len(), 10);
2303    }
2304
2305    #[test]
2306    fn trimming_to_zero_empties_it() {
2307        let mut s = Stream::new();
2308        for ms in 1..=250u64 {
2309            add(&mut s, ms, 0, &[("n", "x")]);
2310        }
2311        assert_eq!(s.trim_maxlen(0, true, None), 250);
2312        assert!(s.is_empty());
2313        assert_eq!(s.nodes(), 0);
2314        assert_eq!(s.last_id(), Id::new(250, 0));
2315    }
2316
2317    #[test]
2318    fn trimming_below_an_id_takes_everything_under_it() {
2319        let mut s = Stream::new();
2320        for ms in 1..=1000u64 {
2321            add(&mut s, ms, 0, &[("n", "x")]);
2322        }
2323        assert_eq!(s.trim_minid(Id::new(400, 0), true, None), 399);
2324        assert_eq!(s.first_id(), Some(Id::new(400, 0)));
2325        assert_eq!(s.len(), 601);
2326    }
2327
2328    #[test]
2329    fn an_approximate_minid_trim_stops_at_a_node() {
2330        let mut s = Stream::new();
2331        for ms in 1..=1000u64 {
2332            add(&mut s, ms, 0, &[("n", "x")]);
2333        }
2334        assert_eq!(s.trim_minid(Id::new(450, 0), false, None), 400);
2335        assert_eq!(s.first_id(), Some(Id::new(401, 0)));
2336    }
2337
2338    #[test]
2339    fn several_entries_share_a_millisecond() {
2340        let mut s = Stream::new();
2341        for seq in 0..250u64 {
2342            add(&mut s, 5, seq, &[("n", "x")]);
2343        }
2344        let got = dump(&s);
2345        assert_eq!(got.len(), 250);
2346        for (at, (id, _)) in got.iter().enumerate() {
2347            assert_eq!(*id, Id::new(5, at as u64), "at {at}");
2348        }
2349        let mut seen = Vec::new();
2350        s.range(Id::new(5, 100), Id::new(5, 102), None, |id, _| {
2351            seen.push(id.seq);
2352            true
2353        });
2354        assert_eq!(seen, vec![100, 101, 102]);
2355    }
2356
2357    /// Sequence numbers that run on across a node boundary, which is where an
2358    /// ID stored as a difference is easiest to get wrong.
2359    #[test]
2360    fn a_sequence_that_crosses_a_node() {
2361        let mut s = Stream::new();
2362        for seq in 0..300u64 {
2363            add(&mut s, 1, seq, &[("n", "x")]);
2364        }
2365        assert!(s.nodes() > 1);
2366        let got = dump(&s);
2367        assert_eq!(got.len(), 300);
2368        assert_eq!(got[299].0, Id::new(1, 299));
2369        assert!(s.delete(Id::new(1, 250)));
2370        assert_eq!(dump(&s).len(), 299);
2371    }
2372
2373    #[test]
2374    fn an_entry_with_no_fields_is_still_an_entry() {
2375        let mut s = Stream::new();
2376        s.append(Id::new(1, 0), &[], Limits::default())
2377            .expect("an append");
2378        add(&mut s, 2, 0, &[("n", "x")]);
2379        let got = dump(&s);
2380        assert_eq!(got.len(), 2);
2381        assert!(got[0].1.is_empty());
2382    }
2383
2384    #[test]
2385    fn a_value_that_looks_like_a_number_comes_back_as_it_went_in() {
2386        let mut s = Stream::new();
2387        add(&mut s, 1, 0, &[("n", "007"), ("m", "7")]);
2388        let got = dump(&s);
2389        assert_eq!(got[0].1[0].1, b"007".to_vec());
2390        assert_eq!(got[0].1[1].1, b"7".to_vec());
2391    }
2392
2393    #[test]
2394    fn the_auto_id_follows_the_clock_and_never_goes_back() {
2395        let mut s = Stream::new();
2396        assert_eq!(s.auto_id(1000), Some(Id::new(1000, 0)));
2397        add(&mut s, 1000, 0, &[("n", "x")]);
2398        assert_eq!(s.auto_id(1000), Some(Id::new(1000, 1)), "same millisecond");
2399        assert_eq!(s.auto_id(900), Some(Id::new(1000, 1)), "clock went back");
2400        assert_eq!(s.auto_id(1001), Some(Id::new(1001, 0)));
2401    }
2402
2403    #[test]
2404    fn an_explicit_millisecond_takes_the_next_sequence() {
2405        let mut s = Stream::new();
2406        add(&mut s, 5, 0, &[("n", "x")]);
2407        assert_eq!(s.auto_seq(5), Some(Id::new(5, 1)));
2408        assert_eq!(s.auto_seq(6), Some(Id::new(6, 0)));
2409        assert_eq!(s.auto_seq(4), None, "below the last one");
2410    }
2411
2412    #[test]
2413    fn an_id_reads_and_writes() {
2414        for (text, default, want) in [
2415            (&b"5"[..], 0, Some(Id::new(5, 0))),
2416            (b"5", u64::MAX, Some(Id::new(5, u64::MAX))),
2417            (b"5-3", 0, Some(Id::new(5, 3))),
2418            (b"0-0", 0, Some(Id::MIN)),
2419            (b"", 0, None),
2420            (b"-1", 0, None),
2421            (b"5-", 0, None),
2422            (b"a", 0, None),
2423            (b"5-a", 0, None),
2424            (b"18446744073709551616", 0, None),
2425        ] {
2426            assert_eq!(
2427                Id::parse(text, default),
2428                want,
2429                "{:?}",
2430                String::from_utf8_lossy(text)
2431            );
2432        }
2433        assert_eq!(Id::new(5, 3).to_vec(), b"5-3".to_vec());
2434    }
2435
2436    #[test]
2437    fn an_id_round_trips_through_its_bytes() {
2438        for id in [Id::MIN, Id::MAX, Id::new(1, 2), Id::new(u64::MAX, 0)] {
2439            assert_eq!(Id::from_bytes(id.to_bytes()), id);
2440        }
2441        // Big endian is the order that sorts, which is why the format uses it.
2442        assert!(Id::new(1, 2).to_bytes() < Id::new(1, 3).to_bytes());
2443        assert!(Id::new(1, u64::MAX).to_bytes() < Id::new(2, 0).to_bytes());
2444    }
2445
2446    #[test]
2447    fn stepping_an_id_carries_and_stops() {
2448        assert_eq!(Id::new(1, 2).next(), Some(Id::new(1, 3)));
2449        assert_eq!(Id::new(1, u64::MAX).next(), Some(Id::new(2, 0)));
2450        assert_eq!(Id::MAX.next(), None);
2451        assert_eq!(Id::new(1, 3).prev(), Some(Id::new(1, 2)));
2452        assert_eq!(Id::new(2, 0).prev(), Some(Id::new(1, u64::MAX)));
2453        assert_eq!(Id::MIN.prev(), None);
2454    }
2455
2456    /// Nothing about the answer may depend on where the node boundaries fell.
2457    #[test]
2458    fn the_node_size_changes_nothing_but_the_node_count() {
2459        // The list of node sizes stays, since it is the thing being varied.
2460        // Only the number of entries poured through each one comes down.
2461        let last = many(400u64);
2462        let mut want = None;
2463        for entries in [1usize, 2, 7, 100, 4096] {
2464            let mut s = Stream::new();
2465            let limits = Limits {
2466                max_node_bytes: NODE_BYTES,
2467                max_node_entries: entries,
2468            };
2469            for ms in 1..=last {
2470                let value = format!("v{ms}");
2471                s.append(Id::new(ms, 0), &[(b"n", value.as_bytes())], limits)
2472                    .expect("an append");
2473            }
2474            for ms in (1..=last).step_by(7) {
2475                s.delete(Id::new(ms, 0));
2476            }
2477            let got = dump(&s);
2478            match &want {
2479                None => want = Some(got),
2480                Some(want) => assert_eq!(&got, want, "at {entries} entries a node"),
2481            }
2482        }
2483    }
2484
2485    /// A byte limit small enough that every entry is its own node.
2486    #[test]
2487    fn a_tiny_byte_limit_still_works() {
2488        let limits = Limits {
2489            max_node_bytes: 1,
2490            max_node_entries: NODE_ENTRIES,
2491        };
2492        let mut s = Stream::new();
2493        for ms in 1..=20u64 {
2494            s.append(Id::new(ms, 0), &[(b"n", b"x")], limits)
2495                .expect("an append");
2496        }
2497        assert_eq!(s.nodes(), 20);
2498        assert_eq!(dump(&s).len(), 20);
2499    }
2500
2501    /// A `DUMP` of a stream taken from a real Redis, as hexadecimal.
2502    ///
2503    /// Captured from Redis 8.10.1 in the official Docker image on 2026-09-02,
2504    /// from a server that had been given exactly this:
2505    ///
2506    /// ```text
2507    /// XADD s 1-1 temperature_celsius 21 relative_humidity 55
2508    /// XADD s 1-2 temperature_celsius 22 relative_humidity 56
2509    /// XADD s 2-1 temperature_celsius 23 relative_humidity 57
2510    /// XADD s 3-1 sensor a
2511    /// XDEL s 1-2
2512    /// ```
2513    ///
2514    /// Three entries share the master fields and one brings its own, one entry
2515    /// is deleted rather than taken out, and the ids climb in both halves, so
2516    /// between them the four cover every branch the encoder has.
2517    const REDIS_DUMP: &str = "1b0110000000000000000100000000000000014070\
2518        700000001f000301010102019374656d70657261747572655f63656c736975731491\
2519        72656c61746976655f68756d69646974791200010201000100011501370105010301\
2520        0001010116013801050102010101000117013901050100010201000101018673656e\
2521        736f72078161020601ff030301010101020400406440640000000f00239a5c2c7208\
2522        ea0a";
2523
2524    fn unhex(s: &str) -> Vec<u8> {
2525        let digits: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
2526        digits
2527            .chunks(2)
2528            .map(|pair| {
2529                let of = |b: u8| (b as char).to_digit(16).expect("a hex digit") as u8;
2530                of(pair[0]) << 4 | of(pair[1])
2531            })
2532            .collect()
2533    }
2534
2535    /// One RDB length, and how many bytes it took.
2536    ///
2537    /// Only the two plain forms, six bits in one byte and fourteen in two,
2538    /// because those are the only ones this fixture uses and a test that
2539    /// quietly accepted more would be claiming to check something it does not.
2540    fn rdb_len(bytes: &[u8], at: usize) -> (usize, usize) {
2541        match bytes[at] >> 6 {
2542            0 => (usize::from(bytes[at] & 0x3F), 1),
2543            1 => (
2544                usize::from(bytes[at] & 0x3F) << 8 | usize::from(bytes[at + 1]),
2545                2,
2546            ),
2547            other => panic!("the fixture used length form {other}"),
2548        }
2549    }
2550
2551    /// The one node out of the captured dump, and the counters after it.
2552    fn redis_node() -> (Id, Vec<u8>, Vec<u8>) {
2553        let dump = unhex(REDIS_DUMP);
2554        assert_eq!(dump[0], 0x1B, "RDB_TYPE_STREAM_LISTPACKS_3");
2555        let (nodes, n) = rdb_len(&dump, 1);
2556        assert_eq!(nodes, 1, "the fixture is one node");
2557        let mut at = 1 + n;
2558        let (key, n) = rdb_len(&dump, at);
2559        assert_eq!(key, 16, "a node key is an id in sixteen bytes");
2560        at += n;
2561        let master = Id::from_bytes(dump[at..at + 16].try_into().expect("sixteen bytes"));
2562        at += 16;
2563        let (len, n) = rdb_len(&dump, at);
2564        at += n;
2565        let lp = dump[at..at + len].to_vec();
2566        // The last ten bytes are the RDB version and the checksum, which belong
2567        // to DUMP rather than to the stream.
2568        let rest = dump[at + len..dump.len() - 10].to_vec();
2569        (master, lp, rest)
2570    }
2571
2572    #[test]
2573    fn a_node_is_written_the_way_redis_writes_one() {
2574        let mut s = Stream::new();
2575        add(
2576            &mut s,
2577            1,
2578            1,
2579            &[("temperature_celsius", "21"), ("relative_humidity", "55")],
2580        );
2581        add(
2582            &mut s,
2583            1,
2584            2,
2585            &[("temperature_celsius", "22"), ("relative_humidity", "56")],
2586        );
2587        add(
2588            &mut s,
2589            2,
2590            1,
2591            &[("temperature_celsius", "23"), ("relative_humidity", "57")],
2592        );
2593        add(&mut s, 3, 1, &[("sensor", "a")]);
2594        assert!(s.delete(Id::new(1, 2)));
2595
2596        let (master, lp, _) = redis_node();
2597        assert_eq!(s.nodes(), 1, "all four fit in one node");
2598        assert_eq!(s.nodes[0].master, master);
2599        assert_eq!(s.nodes[0].lp.as_bytes(), &lp[..]);
2600    }
2601
2602    #[test]
2603    fn a_node_redis_wrote_reads_back() {
2604        let (master, lp, rest) = redis_node();
2605        let lp = Listpack::from_bytes(&lp).expect("a listpack Redis wrote");
2606        let s = Stream {
2607            nodes: VecDeque::from(vec![Node { master, lp }]),
2608            length: u64::from(rest[0]),
2609            last: Id::new(u64::from(rest[1]), u64::from(rest[2])),
2610            max_deleted: Id::new(u64::from(rest[5]), u64::from(rest[6])),
2611            added: u64::from(rest[7]),
2612            groups: Vec::new(),
2613            epoch: 0,
2614        };
2615
2616        assert_eq!(s.len(), 3);
2617        assert_eq!(s.last_id(), Id::new(3, 1));
2618        assert_eq!(s.max_deleted_id(), Id::new(1, 2));
2619        assert_eq!(s.added(), 4);
2620        assert_eq!(s.first_id(), Some(Id::new(1, 1)));
2621        assert_eq!(
2622            dump(&s),
2623            vec![
2624                (
2625                    Id::new(1, 1),
2626                    vec![
2627                        (b"temperature_celsius".to_vec(), b"21".to_vec()),
2628                        (b"relative_humidity".to_vec(), b"55".to_vec())
2629                    ]
2630                ),
2631                (
2632                    Id::new(2, 1),
2633                    vec![
2634                        (b"temperature_celsius".to_vec(), b"23".to_vec()),
2635                        (b"relative_humidity".to_vec(), b"57".to_vec())
2636                    ]
2637                ),
2638                (Id::new(3, 1), vec![(b"sensor".to_vec(), b"a".to_vec())]),
2639            ]
2640        );
2641    }
2642
2643    /// A stream of `n` entries at 1-0 up to n-0, one field each.
2644    fn logged(n: u64) -> Stream {
2645        let mut s = Stream::new();
2646        for ms in 1..=n {
2647            add(&mut s, ms, 0, &[("job", "x")]);
2648        }
2649        s
2650    }
2651
2652    /// What a group read hands back, with the fields flattened.
2653    fn read(s: &mut Stream, group: &str, who: &str, count: Option<usize>, now: u64) -> Vec<Id> {
2654        let mut out = Vec::new();
2655        s.read_group(
2656            group.as_bytes(),
2657            who.as_bytes(),
2658            count,
2659            false,
2660            now,
2661            |id, _| {
2662                out.push(id);
2663                true
2664            },
2665        )
2666        .expect("the group");
2667        out
2668    }
2669
2670    /// The read the resume cursor exists for.
2671    ///
2672    /// Five hundred entries is five nodes, so this also covers the mark being
2673    /// carried over a node boundary and the walk picking up in the next one.
2674    #[test]
2675    fn a_group_draining_one_at_a_time_gets_every_entry_once() {
2676        let mut s = logged(500);
2677        s.create_group(b"workers", Id::MIN, Some(0));
2678        let mut got = Vec::new();
2679        for _ in 0..500 {
2680            got.extend(read(&mut s, "workers", "alice", Some(1), 100));
2681        }
2682        assert_eq!(got, (1..=500).map(|ms| Id::new(ms, 0)).collect::<Vec<_>>());
2683        assert!(read(&mut s, "workers", "alice", Some(1), 100).is_empty());
2684    }
2685
2686    /// The state a consumer keeping up with a producer sits in, where the mark
2687    /// points past the last entry there was and an append lands right on it.
2688    #[test]
2689    fn a_group_that_has_caught_up_is_handed_the_next_append() {
2690        let mut s = logged(3);
2691        s.create_group(b"workers", Id::MIN, Some(0));
2692        assert_eq!(read(&mut s, "workers", "alice", None, 100).len(), 3);
2693        for ms in 4..=200u64 {
2694            add(&mut s, ms, 0, &[("job", "x")]);
2695            assert_eq!(
2696                read(&mut s, "workers", "alice", Some(1), 100),
2697                vec![Id::new(ms, 0)],
2698                "the entry appended a moment ago"
2699            );
2700        }
2701    }
2702
2703    /// A delete moves every byte behind it, so the mark has to be thrown away
2704    /// rather than followed into the middle of an entry.
2705    #[test]
2706    fn a_delete_between_two_group_reads_does_not_lose_the_rest() {
2707        let mut s = logged(300);
2708        s.create_group(b"workers", Id::MIN, Some(0));
2709        let mut got = read(&mut s, "workers", "alice", Some(10), 100);
2710        assert!(s.delete(Id::new(150, 0)));
2711        while got.len() < 299 {
2712            let more = read(&mut s, "workers", "alice", Some(1), 100);
2713            assert_eq!(more.len(), 1, "at {}", got.len());
2714            got.extend(more);
2715        }
2716        let want: Vec<Id> = (1..=300)
2717            .filter(|ms| *ms != 150)
2718            .map(|ms| Id::new(ms, 0))
2719            .collect();
2720        assert_eq!(got, want);
2721    }
2722
2723    /// `XGROUP SETID` back to the start puts the bookmark somewhere the mark
2724    /// says nothing about, and the read after it has to walk from the front.
2725    #[test]
2726    fn moving_the_bookmark_back_reads_it_all_again() {
2727        let mut s = logged(250);
2728        s.create_group(b"workers", Id::MIN, Some(0));
2729        for _ in 0..120 {
2730            read(&mut s, "workers", "alice", Some(1), 100);
2731        }
2732        s.group_mut(b"workers")
2733            .expect("the group")
2734            .set_id(Id::MIN, Some(0));
2735        let mut got = Vec::new();
2736        for _ in 0..250 {
2737            got.extend(read(&mut s, "workers", "alice", Some(1), 100));
2738        }
2739        assert_eq!(got, (1..=250).map(|ms| Id::new(ms, 0)).collect::<Vec<_>>());
2740    }
2741
2742    /// A trim drops whole nodes from the front, one of which may be the one a
2743    /// group's mark is about. Node master IDs are never reused, so the mark
2744    /// cannot be mistaken for one about the node that took its place.
2745    #[test]
2746    fn a_trim_under_a_group_does_not_hand_out_the_wrong_entries() {
2747        let mut s = logged(500);
2748        s.create_group(b"workers", Id::MIN, Some(0));
2749        for _ in 0..50 {
2750            read(&mut s, "workers", "alice", Some(1), 100);
2751        }
2752        assert_eq!(s.trim_maxlen(200, false, None), 300);
2753        let mut got = Vec::new();
2754        for _ in 0..250 {
2755            got.extend(read(&mut s, "workers", "alice", Some(1), 100));
2756        }
2757        assert_eq!(
2758            got,
2759            (301..=500).map(|ms| Id::new(ms, 0)).collect::<Vec<_>>(),
2760            "everything the trim left that the group had not read"
2761        );
2762    }
2763
2764    #[test]
2765    fn a_group_is_made_once() {
2766        let mut s = logged(3);
2767        assert!(s.create_group(b"workers", Id::MIN, Some(0)));
2768        assert!(!s.create_group(b"workers", Id::MIN, Some(0)));
2769        assert!(s.group(b"workers").is_some());
2770        assert!(s.destroy_group(b"workers"));
2771        assert!(!s.destroy_group(b"workers"));
2772        assert!(s.group(b"workers").is_none());
2773    }
2774
2775    #[test]
2776    fn a_group_read_hands_out_what_comes_after_the_bookmark() {
2777        let mut s = logged(5);
2778        s.create_group(b"workers", Id::MIN, Some(0));
2779
2780        assert_eq!(
2781            read(&mut s, "workers", "alice", Some(2), 100),
2782            vec![Id::new(1, 0), Id::new(2, 0)]
2783        );
2784        // The bookmark moved, so bob gets what alice did not.
2785        assert_eq!(
2786            read(&mut s, "workers", "bob", Some(2), 100),
2787            vec![Id::new(3, 0), Id::new(4, 0)]
2788        );
2789        assert_eq!(
2790            read(&mut s, "workers", "alice", None, 100),
2791            vec![Id::new(5, 0)]
2792        );
2793        assert_eq!(read(&mut s, "workers", "alice", None, 100), vec![]);
2794    }
2795
2796    #[test]
2797    fn a_group_read_fills_the_pending_list() {
2798        let mut s = logged(3);
2799        s.create_group(b"workers", Id::MIN, Some(0));
2800        read(&mut s, "workers", "alice", None, 500);
2801
2802        let g = s.group(b"workers").expect("the group");
2803        assert_eq!(g.pending_len(), 3);
2804        assert_eq!(g.last_id(), Id::new(3, 0));
2805        assert_eq!(g.entries_read(), Some(3));
2806        assert_eq!(s.lag(g), Some(0));
2807        let c = g.consumer_named(b"alice").expect("alice");
2808        assert_eq!(c.len(), 3);
2809        assert_eq!(c.active(), Some(500));
2810    }
2811
2812    #[test]
2813    fn a_read_that_finds_nothing_is_seen_but_not_active() {
2814        let mut s = logged(1);
2815        s.create_group(b"workers", Id::MIN, Some(0));
2816        read(&mut s, "workers", "alice", None, 100);
2817        read(&mut s, "workers", "alice", None, 900);
2818
2819        let c = s
2820            .group(b"workers")
2821            .expect("the group")
2822            .consumer_named(b"alice")
2823            .expect("alice");
2824        assert_eq!((c.seen(), c.active()), (900, Some(100)));
2825    }
2826
2827    #[test]
2828    fn a_group_starting_at_the_end_reads_only_what_comes_next() {
2829        let mut s = logged(3);
2830        s.create_group(b"workers", s.last_id(), Some(s.added()));
2831        assert_eq!(read(&mut s, "workers", "alice", None, 1), vec![]);
2832        add(&mut s, 4, 0, &[("job", "x")]);
2833        assert_eq!(
2834            read(&mut s, "workers", "alice", None, 1),
2835            vec![Id::new(4, 0)]
2836        );
2837    }
2838
2839    #[test]
2840    fn reading_a_group_that_is_not_there_says_so() {
2841        let mut s = logged(1);
2842        assert!(
2843            s.read_group(b"nope", b"alice", None, false, 1, |_, _| true)
2844                .is_none()
2845        );
2846    }
2847
2848    #[test]
2849    fn a_consumer_can_re_read_what_it_is_holding() {
2850        let mut s = logged(4);
2851        s.create_group(b"workers", Id::MIN, Some(0));
2852        read(&mut s, "workers", "alice", Some(2), 1);
2853        read(&mut s, "workers", "bob", Some(2), 1);
2854
2855        let mut out = Vec::new();
2856        s.read_group_pending(b"workers", b"alice", Id::MIN, None, 2, |id, fields| {
2857            out.push((id, fields.map(|f| f.len())));
2858            true
2859        })
2860        .expect("the group");
2861        assert_eq!(
2862            out,
2863            vec![(Id::new(1, 0), Some(1)), (Id::new(2, 0), Some(1))]
2864        );
2865
2866        // From an ID, which is how a consumer pages through its own backlog.
2867        let mut after = Vec::new();
2868        s.read_group_pending(b"workers", b"alice", Id::new(1, 0), None, 2, |id, _| {
2869            after.push(id);
2870            true
2871        });
2872        assert_eq!(after, vec![Id::new(2, 0)]);
2873    }
2874
2875    /// A history read counts as a delivery, which is Redis's behaviour and not
2876    /// the one I would have guessed.
2877    ///
2878    /// Checked against Redis 8.10.1: an entry left idle for 2006 milliseconds
2879    /// and then read back through `XREADGROUP ... 0` came out idle for 2 with
2880    /// its delivery count up by one. The count is how many times a consumer has
2881    /// been told to do the work, and a consumer re-reading its backlog after a
2882    /// restart has been told again.
2883    #[test]
2884    fn re_reading_counts_as_being_handed_it_again() {
2885        let mut s = logged(2);
2886        s.create_group(b"workers", Id::MIN, Some(0));
2887        read(&mut s, "workers", "alice", None, 100);
2888        s.read_group_pending(
2889            b"workers",
2890            b"alice",
2891            Id::MIN,
2892            None,
2893            700,
2894            |_: Id, _: Option<Fields<'_>>| true,
2895        );
2896
2897        let g = s.group(b"workers").expect("the group");
2898        let nack = g.nack(Id::new(1, 0)).expect("a nack");
2899        assert_eq!((nack.count(), nack.time()), (2, 700));
2900        // The bookmark does not move, because nothing new was handed out.
2901        assert_eq!(g.last_id(), Id::new(2, 0));
2902        assert_eq!(g.pending_len(), 2);
2903    }
2904
2905    /// The lag and the read counter, which are two answers and not one.
2906    ///
2907    /// Every line here was run against Redis 8.10.1 first and the numbers are
2908    /// its numbers. The one worth pointing at is the last pair: the counter goes
2909    /// away and the group's bookmark keeps moving, because a delete ahead of a
2910    /// group makes the counter unknowable rather than merely stale.
2911    #[test]
2912    fn a_hole_in_front_of_a_group_takes_its_lag() {
2913        let mut s = logged(5);
2914        s.create_group(b"workers", Id::MIN, Some(0));
2915        read(&mut s, "workers", "alice", Some(3), 1);
2916        assert_eq!(counters(&s), (Some(3), Some(2)));
2917
2918        // Deleting something the group has already read leaves both alone,
2919        // since the hole is behind the bookmark and the entries in front of it
2920        // are all still there.
2921        assert!(s.delete(Id::new(1, 0)));
2922        assert_eq!(counters(&s), (Some(3), Some(2)));
2923
2924        // Deleting something it has not reached takes the lag, and leaves the
2925        // counter exactly where it was. Redis does not clear it here.
2926        assert!(s.delete(Id::new(5, 0)));
2927        assert_eq!(counters(&s), (Some(3), None));
2928
2929        // Reading what is left moves the bookmark to 4-0, which is not the last
2930        // ID the stream ever handed out, so there is still no way to say how far
2931        // along that is and the counter goes too.
2932        read(&mut s, "workers", "alice", None, 1);
2933        assert_eq!(s.group(b"workers").expect("g").last_id(), Id::new(4, 0));
2934        assert_eq!(counters(&s), (None, None));
2935    }
2936
2937    /// A trim is not a hole, so it costs a group nothing, and once it has cut
2938    /// past the bookmark the lag becomes what is left rather than nothing.
2939    ///
2940    /// Checked against Redis 8.10.1 at twenty entries, where the three lines
2941    /// below read 5, 5 and 2.
2942    #[test]
2943    fn trimming_past_a_group_leaves_it_the_length() {
2944        let mut s = logged(500);
2945        s.create_group(b"workers", Id::MIN, Some(0));
2946        read(&mut s, "workers", "alice", Some(400), 1);
2947        assert_eq!(counters(&s), (Some(400), Some(100)));
2948
2949        // Whole nodes off the front, all of them well behind the bookmark.
2950        assert_eq!(s.trim_maxlen(200, true, None), 300);
2951        assert_eq!(counters(&s), (Some(400), Some(100)));
2952
2953        // And now past it. The bookmark is below every entry left, so the lag is
2954        // the length: those are exactly the entries the group has still to read.
2955        assert_eq!(s.trim_maxlen(10, true, None), 190);
2956        assert_eq!(counters(&s), (Some(400), Some(10)));
2957    }
2958
2959    /// The counter and the lag of the one group, which every lag test reads.
2960    fn counters(s: &Stream) -> (Option<u64>, Option<u64>) {
2961        let g = s.group(b"workers").expect("the group");
2962        (g.entries_read(), s.lag(g))
2963    }
2964
2965    #[test]
2966    fn an_entry_that_went_away_still_comes_back_as_a_hole() {
2967        let mut s = logged(3);
2968        s.create_group(b"workers", Id::MIN, Some(0));
2969        read(&mut s, "workers", "alice", None, 1);
2970        assert!(s.delete(Id::new(2, 0)));
2971
2972        let mut out = Vec::new();
2973        s.read_group_pending(b"workers", b"alice", Id::MIN, None, 2, |id, fields| {
2974            out.push((id, fields.is_some()));
2975            true
2976        });
2977        assert_eq!(
2978            out,
2979            vec![
2980                (Id::new(1, 0), true),
2981                (Id::new(2, 0), false),
2982                (Id::new(3, 0), true)
2983            ]
2984        );
2985    }
2986
2987    #[test]
2988    fn acking_clears_the_pending_list() {
2989        let mut s = logged(3);
2990        s.create_group(b"workers", Id::MIN, Some(0));
2991        read(&mut s, "workers", "alice", None, 1);
2992        let g = s.group_mut(b"workers").expect("the group");
2993        assert!(g.ack(Id::new(2, 0)));
2994        assert_eq!(g.pending_len(), 2);
2995    }
2996
2997    #[test]
2998    fn a_claim_moves_work_off_a_consumer_that_stopped() {
2999        let mut s = logged(2);
3000        s.create_group(b"workers", Id::MIN, Some(0));
3001        read(&mut s, "workers", "alice", None, 100);
3002
3003        let mut gone = Vec::new();
3004        let took = s
3005            .claim(
3006                b"workers",
3007                b"bob",
3008                &[Id::new(1, 0), Id::new(2, 0)],
3009                500,
3010                5_000,
3011                None,
3012                true,
3013                false,
3014                5_000,
3015                &mut gone,
3016            )
3017            .expect("the group");
3018        assert_eq!(took, vec![Id::new(1, 0), Id::new(2, 0)]);
3019        assert!(gone.is_empty());
3020
3021        let g = s.group(b"workers").expect("the group");
3022        assert!(g.consumer_named(b"alice").expect("alice").is_empty());
3023        assert_eq!(g.consumer_named(b"bob").expect("bob").len(), 2);
3024        assert_eq!(g.nack(Id::new(1, 0)).expect("a nack").count(), 2);
3025    }
3026
3027    #[test]
3028    fn a_claim_leaves_work_that_is_not_idle_enough_alone() {
3029        let mut s = logged(1);
3030        s.create_group(b"workers", Id::MIN, Some(0));
3031        read(&mut s, "workers", "alice", None, 100);
3032
3033        let mut gone = Vec::new();
3034        let took = s
3035            .claim(
3036                b"workers",
3037                b"bob",
3038                &[Id::new(1, 0)],
3039                5_000,
3040                200,
3041                None,
3042                true,
3043                false,
3044                200,
3045                &mut gone,
3046            )
3047            .expect("the group");
3048        assert!(took.is_empty());
3049        assert_eq!(
3050            s.group(b"workers")
3051                .expect("the group")
3052                .consumer_named(b"alice")
3053                .expect("alice")
3054                .len(),
3055            1
3056        );
3057    }
3058
3059    #[test]
3060    fn claiming_an_entry_that_went_away_drops_it_instead() {
3061        let mut s = logged(2);
3062        s.create_group(b"workers", Id::MIN, Some(0));
3063        read(&mut s, "workers", "alice", None, 100);
3064        assert!(s.delete(Id::new(1, 0)));
3065
3066        let mut gone = Vec::new();
3067        let took = s
3068            .claim(
3069                b"workers",
3070                b"bob",
3071                &[Id::new(1, 0), Id::new(2, 0)],
3072                0,
3073                5_000,
3074                None,
3075                true,
3076                false,
3077                5_000,
3078                &mut gone,
3079            )
3080            .expect("the group");
3081        assert_eq!(took, vec![Id::new(2, 0)]);
3082        assert_eq!(gone, vec![Id::new(1, 0)]);
3083        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 1);
3084    }
3085
3086    #[test]
3087    fn force_only_works_on_an_entry_that_is_really_there() {
3088        let mut s = logged(2);
3089        s.create_group(b"workers", s.last_id(), Some(2));
3090
3091        let mut gone = Vec::new();
3092        let took = s
3093            .claim(
3094                b"workers",
3095                b"bob",
3096                &[Id::new(1, 0), Id::new(99, 0)],
3097                0,
3098                100,
3099                None,
3100                true,
3101                true,
3102                100,
3103                &mut gone,
3104            )
3105            .expect("the group");
3106        assert_eq!(took, vec![Id::new(1, 0)], "99-0 is not in the stream");
3107        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 1);
3108    }
3109
3110    #[test]
3111    fn autoclaim_sweeps_the_stale_ones_and_says_where_it_stopped() {
3112        let mut s = logged(6);
3113        s.create_group(b"workers", Id::MIN, Some(0));
3114        read(&mut s, "workers", "alice", Some(3), 100);
3115        read(&mut s, "workers", "alice", None, 900);
3116
3117        let mut gone = Vec::new();
3118        let (cursor, took) = s
3119            .autoclaim(
3120                b"workers",
3121                b"bob",
3122                Id::MIN,
3123                500,
3124                100,
3125                true,
3126                1_000,
3127                &mut gone,
3128            )
3129            .expect("the group");
3130        assert_eq!(cursor, None, "the sweep reached the end");
3131        assert_eq!(took, vec![Id::new(1, 0), Id::new(2, 0), Id::new(3, 0)]);
3132        assert_eq!(
3133            s.group(b"workers")
3134                .expect("the group")
3135                .consumer_named(b"bob")
3136                .expect("bob")
3137                .len(),
3138            3
3139        );
3140    }
3141
3142    #[test]
3143    fn autoclaim_hands_back_a_cursor_when_it_hits_the_count() {
3144        let mut s = logged(10);
3145        s.create_group(b"workers", Id::MIN, Some(0));
3146        read(&mut s, "workers", "alice", None, 100);
3147
3148        let mut gone = Vec::new();
3149        let (cursor, took) = s
3150            .autoclaim(b"workers", b"bob", Id::MIN, 0, 4, true, 1_000, &mut gone)
3151            .expect("the group");
3152        assert_eq!(took.len(), 4);
3153        assert_eq!(cursor, Some(Id::new(5, 0)));
3154
3155        // And carrying on from the cursor takes the rest.
3156        let (cursor, took) = s
3157            .autoclaim(
3158                b"workers",
3159                b"bob",
3160                cursor.expect("a cursor"),
3161                0,
3162                100,
3163                true,
3164                1_000,
3165                &mut gone,
3166            )
3167            .expect("the group");
3168        assert_eq!(took.len(), 6);
3169        assert_eq!(cursor, None);
3170    }
3171
3172    #[test]
3173    fn a_group_survives_the_stream_being_trimmed_under_it() {
3174        let mut s = logged(10);
3175        s.create_group(b"workers", Id::MIN, Some(0));
3176        read(&mut s, "workers", "alice", Some(5), 100);
3177        // Trimming takes entries alice is still holding.
3178        assert_eq!(s.trim_maxlen(3, true, None), 7);
3179
3180        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 5);
3181        let mut gone = Vec::new();
3182        let took = s
3183            .claim(
3184                b"workers",
3185                b"bob",
3186                &(1..=5).map(|ms| Id::new(ms, 0)).collect::<Vec<_>>(),
3187                0,
3188                1_000,
3189                None,
3190                true,
3191                false,
3192                1_000,
3193                &mut gone,
3194            )
3195            .expect("the group");
3196        assert!(took.is_empty(), "none of them are there any more");
3197        assert_eq!(gone.len(), 5);
3198        assert_eq!(s.group(b"workers").expect("the group").pending_len(), 0);
3199    }
3200
3201    /// Out and back, checking everything a client can see on the way.
3202    fn round_trip(s: &Stream) -> Stream {
3203        let mut bytes = Vec::new();
3204        s.freeze(&mut bytes);
3205        let back = Stream::thaw(&bytes).expect("our own bytes");
3206        assert_eq!(dump(&back), dump(s), "the entries");
3207        assert_eq!(back.len(), s.len(), "the length");
3208        assert_eq!(back.added(), s.added(), "the count added");
3209        assert_eq!(back.last_id(), s.last_id(), "the last ID");
3210        assert_eq!(back.max_deleted_id(), s.max_deleted_id(), "the max deleted");
3211        assert_eq!(back.nodes(), s.nodes(), "the node count");
3212        assert_eq!(back, *s, "the whole thing");
3213        back
3214    }
3215
3216    #[test]
3217    fn a_frozen_stream_comes_back_with_every_entry_it_held() {
3218        let mut s = Stream::new();
3219        for ms in 1..=500u64 {
3220            add(&mut s, ms, 0, &[("job", "x"), ("n", "1")]);
3221        }
3222        add(&mut s, 500, 1, &[("job", "y")]);
3223        assert!(s.nodes() > 1, "more than one node, so the walk is tested");
3224        round_trip(&s);
3225    }
3226
3227    #[test]
3228    fn a_frozen_stream_keeps_the_holes_and_the_counters() {
3229        let mut s = logged(200);
3230        for ms in [3u64, 4, 5, 100, 199] {
3231            assert!(s.delete(Id::new(ms, 0)));
3232        }
3233        s.trim_minid(Id::new(20, 0), true, None);
3234        let back = round_trip(&s);
3235        assert_eq!(back.first_id(), Some(Id::new(20, 0)));
3236        assert!(!back.contains(Id::new(100, 0)), "a hole is still a hole");
3237        assert_eq!(back.max_deleted_id(), Id::new(199, 0));
3238    }
3239
3240    #[test]
3241    fn a_frozen_stream_keeps_its_groups_and_who_is_holding_what() {
3242        let mut s = logged(20);
3243        s.create_group(b"workers", Id::MIN, Some(0));
3244        s.create_group(b"audit", Id::new(5, 0), None);
3245        read(&mut s, "workers", "alice", Some(6), 1_000);
3246        read(&mut s, "workers", "bob", Some(4), 2_000);
3247        // One handed back to the group with nobody holding it, so the NACK count
3248        // has something to come back as.
3249        assert_eq!(
3250            s.nack(b"workers", Id::new(2, 0), Retry::Keep, true),
3251            Some(true)
3252        );
3253        // And one consumer deleted, so a slot in the middle is empty and the
3254        // slot numbers behind it have to survive.
3255        s.group_mut(b"workers")
3256            .expect("the group")
3257            .create_consumer(b"carol", 3_000);
3258        read(&mut s, "workers", "dave", Some(2), 4_000);
3259        s.group_mut(b"workers")
3260            .expect("the group")
3261            .delete_consumer(b"carol");
3262
3263        let back = round_trip(&s);
3264        let g = back.group(b"workers").expect("the group");
3265        assert_eq!(g.pending_len(), 12);
3266        assert_eq!(g.nacked_len(), 1);
3267        assert_eq!(g.entries_read(), Some(12));
3268        // Six, less the one handed back to the group.
3269        assert_eq!(g.consumer_named(b"alice").expect("alice").len(), 5);
3270        assert_eq!(g.consumer_named(b"bob").expect("bob").len(), 4);
3271        assert_eq!(g.consumer_named(b"dave").expect("dave").len(), 2);
3272        assert_eq!(g.consumer_named(b"carol"), None);
3273        // Dave came after carol, so his slot is the fourth one and reading the
3274        // empties back as empties is what keeps his entries his.
3275        assert_eq!(g.slot(b"dave"), Some(3));
3276        assert_eq!(g.nack(Id::new(1, 0)).expect("a nack").owner(), Some(0));
3277        assert_eq!(g.nack(Id::new(2, 0)).expect("a nack").owner(), None);
3278        assert_eq!(
3279            back.group(b"audit").expect("audit").last_id(),
3280            Id::new(5, 0)
3281        );
3282        assert_eq!(back.group(b"audit").expect("audit").entries_read(), None);
3283    }
3284
3285    #[test]
3286    fn a_stream_that_came_back_still_takes_entries_and_reads_them() {
3287        let mut s = logged(10);
3288        s.create_group(b"workers", Id::MIN, Some(0));
3289        read(&mut s, "workers", "alice", Some(4), 1_000);
3290
3291        let mut back = round_trip(&s);
3292        add(&mut back, 11, 0, &[("job", "new")]);
3293        assert_eq!(back.len(), 11);
3294        assert_eq!(
3295            read(&mut back, "workers", "alice", Some(3), 2_000),
3296            vec![Id::new(5, 0), Id::new(6, 0), Id::new(7, 0)]
3297        );
3298        assert!(
3299            back.group_mut(b"workers")
3300                .expect("the group")
3301                .ack(Id::new(1, 0))
3302        );
3303        assert_eq!(back.group(b"workers").expect("the group").pending_len(), 6);
3304    }
3305
3306    #[test]
3307    fn an_empty_stream_that_still_exists_comes_back() {
3308        let mut s = logged(3);
3309        for ms in 1..=3u64 {
3310            assert!(s.delete(Id::new(ms, 0)));
3311        }
3312        assert_eq!(s.nodes(), 0, "the last node went with the last entry");
3313        let back = round_trip(&s);
3314        assert!(back.is_empty());
3315        // The whole reason an empty stream is kept: a new entry still has to
3316        // beat the ID of one that is gone.
3317        assert_eq!(back.last_id(), Id::new(3, 0));
3318        round_trip(&Stream::new());
3319    }
3320
3321    #[test]
3322    fn a_frozen_stream_that_arrives_damaged_is_an_error_and_not_a_panic() {
3323        let mut s = logged(8);
3324        s.create_group(b"workers", Id::MIN, Some(0));
3325        read(&mut s, "workers", "alice", Some(3), 1_000);
3326        let mut bytes = Vec::new();
3327        s.freeze(&mut bytes);
3328
3329        for cut in 0..bytes.len() {
3330            assert!(Stream::thaw(&bytes[..cut]).is_err(), "cut at {cut}");
3331        }
3332        // Every bit of the header and the front of the first node, which is
3333        // where the counts and the lengths that a reader trusts all live.
3334        for at in 0..bytes.len().min(40) {
3335            for bit in 0..8 {
3336                let mut bad = bytes.clone();
3337                bad[at] ^= 1 << bit;
3338                // It either parses into some other stream or it does not. Either
3339                // way it comes back rather than going through a length that was
3340                // never checked.
3341                let _ = Stream::thaw(&bad);
3342            }
3343        }
3344        assert_eq!(Stream::thaw(&[]), Err(Broken::Short));
3345        assert_eq!(Stream::thaw(&[9]), Err(Broken::Form));
3346    }
3347
3348    #[test]
3349    fn nothing_at_all() {
3350        let mut s = Stream::new();
3351        assert!(s.is_empty());
3352        assert_eq!(s.len(), 0);
3353        assert_eq!(s.first_id(), None);
3354        assert_eq!(s.last_id(), Id::MIN);
3355        assert_eq!(s.trim_maxlen(0, true, None), 0);
3356        assert_eq!(s.trim_minid(Id::MAX, true, None), 0);
3357        assert_eq!(dump(&s), vec![]);
3358    }
3359}