Skip to main content

zencodec_io/
stepper.rs

1//! The byte-stepping dialogue every driver serves.
2//!
3//! An image decoder ([`IncrementalDecode`]) and a container demuxer speak the
4//! same protocol: the driver offers an [`Input`] at some offset, the stage
5//! takes a prefix and either produces something or names the bytes it
6//! [`requested`](ByteStepper::requested) next. [`ByteStepper`] is that
7//! protocol with the stage's output left abstract, so one set of drivers
8//! (slice, [`ReadAt`], `std::io::Read`, async) serves both.
9//!
10//! The drivers here are cursors: each call to `next` services `Need`s until
11//! the stage produces one output, and returns it. A loop over a cursor is a
12//! complete drive; a pull-style adapter (an animation view over a demuxer)
13//! calls `next` once per item it needs.
14
15use alloc::vec::Vec;
16use core::convert::Infallible;
17use core::fmt;
18
19use zencodec::decode::{ByteRange, Input, InputRequest};
20use zencodec::source::ReadAt;
21
22use crate::admission::{self, InputError};
23use crate::drive::{DriveOptions, DriveStats};
24use crate::window::{ProtocolViolation, Window, check_need, read_target};
25
26/// A stage that consumes bytes through the sans-I/O dialogue.
27pub trait ByteStepper {
28    /// What a productive step yields: an image decode event, a demuxed
29    /// packet.
30    type Output;
31    /// The stage's error.
32    type Error;
33
34    /// Take a prefix of `input` and report what happened. `None` adds no
35    /// per-call control; any control retained by the stage remains active.
36    fn step(
37        &mut self,
38        input: Input<'_>,
39        pulse: Option<&dyn zencodec::Pulse>,
40    ) -> Result<Stepped<Self::Output>, Self::Error>;
41
42    /// What the stage needs next, after a [`Step::Need`]. Never empty then.
43    fn requested(&self) -> &[InputRequest];
44}
45
46/// The result of one [`ByteStepper::step`].
47#[derive(Debug)]
48pub struct Stepped<O> {
49    /// Bytes taken from the front of the input.
50    pub consumed: usize,
51    /// What the step produced.
52    pub step: Step<O>,
53}
54
55/// What a step produced.
56#[derive(Debug)]
57pub enum Step<O> {
58    /// Nothing until one of [`requested`](ByteStepper::requested) is met.
59    Need,
60    /// An output; the stage has more to do.
61    Output(O),
62    /// The last output: the stage is done.
63    Finished(O),
64}
65
66/// What a cursor's `next` returns: an output, or the last one.
67#[derive(Debug)]
68pub enum Yield<O> {
69    /// An output; call `next` again for more.
70    Output(O),
71    /// The last output.
72    Finished(O),
73}
74
75impl<O> Yield<O> {
76    /// The output, either way.
77    #[inline]
78    pub fn into_inner(self) -> O {
79        match self {
80            Self::Output(o) | Self::Finished(o) => o,
81        }
82    }
83
84    /// Whether this was the last output.
85    #[inline]
86    pub fn is_finished(&self) -> bool {
87        matches!(self, Self::Finished(_))
88    }
89}
90
91/// Failure of a drive over any [`ByteStepper`].
92#[derive(Debug)]
93#[non_exhaustive]
94pub enum DriveFailure<E, SE> {
95    /// The stage failed (for an image decoder: the decoder, its sink or the
96    /// pulse).
97    Step(E),
98    /// The source failed.
99    Source(SE),
100    /// The stage broke the protocol.
101    Protocol(ProtocolViolation),
102    /// Input admission or allocation failed before a source read.
103    Input(InputError),
104}
105
106impl<E: fmt::Display, SE: fmt::Display> fmt::Display for DriveFailure<E, SE> {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::Step(e) => write!(f, "{e}"),
110            Self::Source(e) => write!(f, "source: {e}"),
111            Self::Protocol(e) => write!(f, "protocol: {e}"),
112            Self::Input(e) => write!(f, "input: {e}"),
113        }
114    }
115}
116
117impl<E: core::error::Error + 'static, SE: core::error::Error + 'static> core::error::Error
118    for DriveFailure<E, SE>
119{
120    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
121        match self {
122            Self::Step(e) => Some(e),
123            Self::Source(e) => Some(e),
124            Self::Protocol(e) => Some(e),
125            Self::Input(e) => Some(e),
126        }
127    }
128}
129
130impl<E> DriveFailure<E, Infallible> {
131    /// Widen a source-free failure to any source error.
132    pub fn widen<SE>(self) -> DriveFailure<E, SE> {
133        match self {
134            Self::Step(e) => DriveFailure::Step(e),
135            Self::Protocol(p) => DriveFailure::Protocol(p),
136            Self::Input(e) => DriveFailure::Input(e),
137            Self::Source(never) => match never {},
138        }
139    }
140}
141
142/// What a cursor's `next` returns for stage `S` over a source failing with
143/// `SE`.
144type Next<S, SE> =
145    Result<Yield<<S as ByteStepper>::Output>, DriveFailure<<S as ByteStepper>::Error, SE>>;
146
147/// The `start..end` a slice drive offers next, for `requests` (post
148/// [`check_need`], so `requests` is never empty) over `total` bytes; a
149/// sequential request gets at least its minimum and up to `window` more, a
150/// range exactly its span.
151pub(crate) fn slice_span(
152    start: usize,
153    end: usize,
154    window: usize,
155    total: usize,
156    requests: &[InputRequest],
157) -> (usize, usize) {
158    match requests[0] {
159        InputRequest::AtLeast { offset, min_len } => {
160            let s = offset as usize;
161            (s, s.saturating_add(min_len.max(window)).min(total))
162        }
163        InputRequest::Range(r) => (r.offset as usize, r.end() as usize),
164        // Every input carries the total; `check_need` reports a stage that
165        // keeps asking.
166        _ => (start, end),
167    }
168}
169
170/// Drives a stage over bytes already in memory, without copying them.
171///
172/// Every input is a sub-slice carrying the total length. A sequential
173/// request gets at least its minimum and up to `window` bytes (a small
174/// window exercises boundaries); a range gets exactly its span.
175#[derive(Debug)]
176pub struct SliceCursor {
177    start: usize,
178    end: usize,
179    window: usize,
180    stats: DriveStats,
181}
182
183impl SliceCursor {
184    /// A cursor offering at most `window` bytes beyond each request's
185    /// minimum (at least 1).
186    pub fn new(window: usize) -> Self {
187        Self {
188            start: 0,
189            end: 0,
190            window: window.max(1),
191            stats: DriveStats::default(),
192        }
193    }
194
195    /// Steps, reads and bytes offered so far.
196    pub fn stats(&self) -> DriveStats {
197        self.stats
198    }
199
200    /// The input to offer the stage next: `bytes[start..end]` clamped to
201    /// `bytes` (the clamps also keep a stage's over-consumption or a past
202    /// range from slicing out of bounds).
203    #[inline]
204    pub(crate) fn offer<'b>(&mut self, bytes: &'b [u8]) -> Input<'b> {
205        self.end = self.end.min(bytes.len());
206        self.start = self.start.min(self.end);
207        Input::new(self.start as u64, &bytes[self.start..self.end])
208            .with_total_len(bytes.len() as u64)
209    }
210
211    /// Record a step and drop `consumed` bytes from the offered front.
212    #[inline]
213    pub(crate) fn take(&mut self, consumed: usize) {
214        self.stats.steps += 1;
215        self.start += consumed.min(self.end - self.start);
216    }
217
218    /// Point the next offer at the first of `requests` (post
219    /// [`check_need`], so `requests` is never empty).
220    pub(crate) fn serve(&mut self, bytes: &[u8], requests: &[InputRequest]) {
221        let (s, e) = slice_span(self.start, self.end, self.window, bytes.len(), requests);
222        self.start = s;
223        self.end = e;
224        self.stats.reads += 1;
225        self.stats.bytes_read += (e - s) as u64;
226    }
227
228    /// Step `stage` over `bytes` until it produces an output. Pass the same
229    /// `bytes` on every call.
230    #[inline]
231    pub fn next<S: ByteStepper + ?Sized>(
232        &mut self,
233        bytes: &[u8],
234        stage: &mut S,
235        pulse: Option<&dyn zencodec::Pulse>,
236    ) -> Next<S, Infallible> {
237        loop {
238            let input = self.offer(bytes);
239            let p = stage.step(input, pulse).map_err(DriveFailure::Step)?;
240            self.take(p.consumed);
241            if matches!(p.step, Step::Need) {
242                check_need(stage.requested(), &input, p.consumed)
243                    .map_err(DriveFailure::Protocol)?;
244            }
245            match p.step {
246                Step::Output(o) => return Ok(Yield::Output(o)),
247                Step::Finished(o) => return Ok(Yield::Finished(o)),
248                Step::Need => self.serve(bytes, stage.requested()),
249            }
250        }
251    }
252}
253
254/// Drives a stage over a random-access source.
255///
256/// Sequential requests read forward into a driver-owned window (a short
257/// read marks the end of the source; [`ReadAt::len`] is called only when the
258/// stage asks for the length). Range requests are coalesced
259/// ([`DriveOptions::max_gap`]) and fetched with one
260/// [`read_many_at`](ReadAt::read_many_at) per round, so a concurrent source
261/// overlaps their round trips; later ranges of the same round are served
262/// from the fetched buffers. A source already in memory
263/// ([`ReadAt::as_slice`]) is driven without copying.
264#[derive(Debug)]
265pub struct ReadAtCursor {
266    opts: DriveOptions,
267    stats: DriveStats,
268    window: Window,
269    started: bool,
270    /// Buffers fetched by the last range round, by absolute offset.
271    fetched: Vec<(u64, Vec<u8>)>,
272    coalesced: Vec<ByteRange>,
273    /// Which input to offer next: the window, or a fetched buffer from `at`.
274    from_fetched: Option<(usize, usize)>,
275    /// Set once the source turned out to be in memory.
276    slice: Option<SliceCursor>,
277}
278
279impl ReadAtCursor {
280    /// A cursor with the given read sizes.
281    pub fn new(opts: DriveOptions) -> Self {
282        Self {
283            opts,
284            stats: DriveStats::default(),
285            window: Window::new(),
286            started: false,
287            fetched: Vec::new(),
288            coalesced: Vec::new(),
289            from_fetched: None,
290            slice: None,
291        }
292    }
293
294    /// Steps, reads and bytes read so far.
295    pub fn stats(&self) -> DriveStats {
296        match &self.slice {
297            Some(s) => s.stats(),
298            None => self.stats,
299        }
300    }
301
302    /// Retained driver-owned payload capacity; borrowed source bytes are excluded.
303    pub fn retained_input_capacity(&self) -> usize {
304        self.window
305            .capacity()
306            .saturating_add(admission::capacity(&self.fetched))
307    }
308
309    /// The input to offer the stage next: the fetched buffer in play, or
310    /// the window. The first call detects an in-memory source and starts
311    /// the length hint.
312    pub(crate) fn offer<'a, Src: ReadAt + ?Sized>(&'a mut self, source: &'a Src) -> Input<'a> {
313        if !self.started {
314            self.started = true;
315            if source.as_slice().is_some() {
316                self.slice = Some(SliceCursor::new(self.opts.read_size));
317            } else {
318                self.window.total = source.len_hint();
319            }
320        }
321        if let Some(cursor) = &mut self.slice {
322            let bytes = source.as_slice().expect("the source stays in memory");
323            return cursor.offer(bytes);
324        }
325        match self.from_fetched {
326            Some((i, at)) => {
327                let (off, buf) = &self.fetched[i];
328                Input::new(off + at as u64, &buf[at..]).with_total_len_opt(self.window.total)
329            }
330            None => self.window.input(),
331        }
332    }
333
334    /// Record a step and drop `consumed` bytes from the offered front.
335    #[inline]
336    pub(crate) fn take(&mut self, consumed: usize) {
337        if let Some(cursor) = &mut self.slice {
338            return cursor.take(consumed);
339        }
340        self.stats.steps += 1;
341        match &mut self.from_fetched {
342            // Clamped like the window and slice paths: a decoder that
343            // claims more than it was offered must not move the offset past
344            // the fetched buffer.
345            Some((i, at)) => {
346                let len = self.fetched.get(*i).map_or(0, |(_, b)| b.len());
347                *at = at.saturating_add(consumed).min(len);
348            }
349            None => self.window.consume(consumed),
350        }
351    }
352
353    /// Step `stage` over `source` until it produces an output. Pass the same
354    /// source on every call.
355    #[inline]
356    pub fn next<S, Src>(
357        &mut self,
358        source: &mut Src,
359        stage: &mut S,
360        pulse: Option<&dyn zencodec::Pulse>,
361    ) -> Next<S, Src::Error>
362    where
363        S: ByteStepper + ?Sized,
364        Src: ReadAt + ?Sized,
365    {
366        if !self.started && source.as_slice().is_some() {
367            self.started = true;
368            self.slice = Some(SliceCursor::new(self.opts.read_size));
369        }
370        if let Some(cursor) = &mut self.slice {
371            let bytes = source.as_slice().expect("the source stays in memory");
372            return cursor
373                .next(bytes, stage, pulse)
374                .map_err(DriveFailure::widen);
375        }
376        loop {
377            let input = self.offer(source);
378            let p = stage.step(input, pulse).map_err(DriveFailure::Step)?;
379            if matches!(p.step, Step::Need) {
380                check_need(stage.requested(), &input, p.consumed)
381                    .map_err(DriveFailure::Protocol)?;
382            }
383            self.take(p.consumed);
384            match p.step {
385                Step::Output(o) => return Ok(Yield::Output(o)),
386                Step::Finished(o) => return Ok(Yield::Finished(o)),
387                Step::Need => self.serve(stage.requested(), source)?,
388            }
389        }
390    }
391
392    /// Make the input for the first of `requests` available.
393    pub(crate) fn serve<E, Src: ReadAt + ?Sized>(
394        &mut self,
395        requests: &[InputRequest],
396        source: &mut Src,
397    ) -> Result<(), DriveFailure<E, Src::Error>> {
398        let window = &mut self.window;
399        let limits = self.opts.limits;
400        let request = admission::select(requests, limits).map_err(DriveFailure::Input)?;
401        match request {
402            InputRequest::AtLeast { offset, min_len } => {
403                self.from_fetched = None;
404                window.position(offset, &mut self.fetched);
405                // Held bytes already satisfy the request; do not turn a cache
406                // hit into another read merely to refill the prefetch target.
407                if window.len() >= min_len.max(1) {
408                    return Ok(());
409                }
410                // Read until the window holds a whole number of requests at
411                // least `read_size` long, or the source ends (a short read
412                // marks the end).
413                limits.reading().map_err(DriveFailure::Input)?;
414                let target = read_target(min_len, self.opts.read_size)
415                    .min(limits.max_input_bytes())
416                    .min(usize::try_from(u64::MAX - offset).unwrap_or(usize::MAX));
417                admission::make_room(window, &mut self.fetched, target, limits);
418                while window.len() < target && window.total != Some(window.end()) {
419                    let end = window.end();
420                    let n_want = (target - window.len())
421                        .min(usize::try_from(u64::MAX - end).unwrap_or(usize::MAX));
422                    let spare = window
423                        .try_spare(
424                            n_want,
425                            limits.max_input_bytes() - admission::capacity(&self.fetched),
426                        )
427                        .map_err(DriveFailure::Input)?;
428                    let n = source.read_at(end, spare).map_err(DriveFailure::Source)?;
429                    window.commit(n);
430                    self.stats.reads += 1;
431                    self.stats.bytes_read += n as u64;
432                    if n < n_want {
433                        window.total = Some(end + n as u64);
434                    }
435                }
436            }
437            InputRequest::Range(first) => {
438                if first.offset >= window.base() && first.end() <= window.end() {
439                    self.from_fetched = None;
440                    window.consume((first.offset - window.base()) as usize);
441                    return Ok(());
442                }
443                let holds = |(off, buf): &(u64, Vec<u8>)| {
444                    first.offset >= *off && first.end() <= off + buf.len() as u64
445                };
446                // Serve from the last round's buffers if one holds it.
447                if let Some(i) = self.fetched.iter().position(holds) {
448                    self.from_fetched = Some((i, (first.offset - self.fetched[i].0) as usize));
449                    return Ok(());
450                }
451                self.from_fetched = None;
452                if window.position(first.offset, &mut self.fetched) {
453                    // A prefix was prefetched. Read only its missing tail:
454                    // refetching the prefix would break a forward-only source.
455                    let missing = (first.end() - window.end()) as usize;
456                    let offset = window.end();
457                    limits.reading().map_err(DriveFailure::Input)?;
458                    admission::make_room(window, &mut self.fetched, first.length as usize, limits);
459                    let spare = window
460                        .try_spare(
461                            missing,
462                            limits.max_input_bytes() - admission::capacity(&self.fetched),
463                        )
464                        .map_err(DriveFailure::Input)?;
465                    source
466                        .read_exact_at(offset, spare)
467                        .map_err(DriveFailure::Source)?;
468                    window.commit(missing);
469                    self.stats.reads += 1;
470                    self.stats.bytes_read += missing as u64;
471                    return Ok(());
472                }
473                // New round: every range request, coalesced, one batch.
474                admission::plan(
475                    first,
476                    requests,
477                    self.opts.max_gap,
478                    limits,
479                    &mut self.coalesced,
480                )
481                .map_err(DriveFailure::Input)?;
482                window.release();
483                let mut pending = core::mem::take(&mut self.fetched);
484                admission::allocate_ranges(&self.coalesced, limits, &mut pending)
485                    .map_err(DriveFailure::Input)?;
486                {
487                    let mut reqs = Vec::new();
488                    admission::reserve(&mut reqs, pending.len()).map_err(DriveFailure::Input)?;
489                    reqs.extend(pending.iter_mut().map(|(off, buf)| (*off, &mut buf[..])));
490                    for batch in reqs.chunks_mut(limits.max_in_flight()) {
491                        source.read_many_at(batch).map_err(DriveFailure::Source)?;
492                        self.stats.reads += 1;
493                    }
494                }
495                // Publish only complete reads; an error leaves no partial cache.
496                self.fetched = pending;
497                self.stats.bytes_read += self.coalesced.iter().map(|r| r.length).sum::<u64>();
498                let i = self
499                    .fetched
500                    .iter()
501                    .position(holds)
502                    .expect("coalesced ranges cover every request");
503                self.from_fetched = Some((i, (first.offset - self.fetched[i].0) as usize));
504            }
505            InputRequest::Len => {
506                self.from_fetched = None;
507                if window.total.is_none() {
508                    limits.reading().map_err(DriveFailure::Input)?;
509                    window.total = Some(source.len().map_err(DriveFailure::Source)?);
510                }
511            }
512            _ => return Err(DriveFailure::Protocol(ProtocolViolation::EmptyRequest)),
513        }
514        Ok(())
515    }
516}
517
518#[cfg(feature = "std")]
519pub use read::{PumpFailure, ReadCursor};
520
521#[cfg(feature = "std")]
522mod read {
523    use super::*;
524
525    /// Failure of a drive over a sequential `std::io::Read` stream.
526    #[derive(Debug)]
527    #[non_exhaustive]
528    pub enum PumpFailure<E> {
529        /// The stage failed.
530        Step(E),
531        /// Reading the stream failed.
532        Io(std::io::Error),
533        /// The stage asked for bytes behind the stream position, or for the
534        /// total length before the stream ended. A stream cannot serve
535        /// either; wrap it in [`BufferedReadAt`](crate::BufferedReadAt) and
536        /// use a [`ReadAtCursor`].
537        NeedsRandomAccess(InputRequest),
538        /// The stage needs more contiguous bytes than `max_window` allows.
539        WindowLimit {
540            /// Bytes the stage asked for in one input.
541            needed: usize,
542            /// The configured limit.
543            limit: usize,
544        },
545        /// The stage broke the protocol.
546        Protocol(ProtocolViolation),
547        /// Fallible input allocation or address admission failed.
548        Input(InputError),
549    }
550
551    impl<E: fmt::Display> fmt::Display for PumpFailure<E> {
552        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553            match self {
554                Self::Step(e) => write!(f, "{e}"),
555                Self::Io(e) => write!(f, "reading the stream: {e}"),
556                Self::NeedsRandomAccess(r) => write!(
557                    f,
558                    "decoder needs random access ({r:?}); a sequential stream cannot serve it"
559                ),
560                Self::WindowLimit { needed, limit } => write!(
561                    f,
562                    "decoder needs {needed} contiguous bytes, over the {limit}-byte window limit"
563                ),
564                Self::Protocol(e) => write!(f, "protocol: {e}"),
565                Self::Input(e) => write!(f, "input: {e}"),
566            }
567        }
568    }
569
570    impl<E: core::error::Error + 'static> core::error::Error for PumpFailure<E> {
571        fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
572            match self {
573                Self::Step(e) => Some(e),
574                Self::Io(e) => Some(e),
575                Self::Protocol(e) => Some(e),
576                Self::Input(e) => Some(e),
577                _ => None,
578            }
579        }
580    }
581
582    /// Drives a stage over a sequential stream.
583    ///
584    /// The cursor owns one window over the stream. It reads `read_size`
585    /// bytes at a time (more when a request needs a larger contiguous span,
586    /// up to `max_window`), offers the window, and drops what the stage
587    /// took. Bytes the stage skips ahead over are read and discarded; a
588    /// request behind the stream position is
589    /// [`PumpFailure::NeedsRandomAccess`].
590    #[derive(Debug)]
591    pub struct ReadCursor {
592        read_size: usize,
593        max_window: usize,
594        window: Window,
595    }
596
597    impl ReadCursor {
598        /// A cursor reading `read_size` bytes at a time (at least 512) and
599        /// holding at most `max_window` contiguous bytes for one request.
600        pub fn new(read_size: usize, max_window: usize) -> Self {
601            Self {
602                read_size: read_size.max(512),
603                max_window,
604                window: Window::new(),
605            }
606        }
607
608        /// Retained input allocation capacity, excluding the reader's own buffers.
609        pub fn retained_input_capacity(&self) -> usize {
610            self.window.capacity()
611        }
612
613        /// The input to offer the stage next: the whole window.
614        #[inline]
615        pub(crate) fn offer(&self) -> Input<'_> {
616            self.window.input()
617        }
618
619        /// Drop `consumed` bytes from the offered front.
620        #[inline]
621        pub(crate) fn take(&mut self, consumed: usize) {
622            self.window.consume(consumed);
623        }
624
625        /// Read from `reader` until the first of `requests` the stream can
626        /// serve is met (post [`check_need`], so `requests` is never empty).
627        pub(crate) fn serve<R, E>(
628            &mut self,
629            reader: &mut R,
630            requests: &[InputRequest],
631        ) -> Result<(), PumpFailure<E>>
632        where
633            R: std::io::Read + ?Sized,
634        {
635            let window = &mut self.window;
636            // Prefer a request the stream can serve: at or ahead of the
637            // window.
638            let request = requests
639                .iter()
640                .copied()
641                .find(|r| r.offset().is_some_and(|o| o >= window.base()))
642                .unwrap_or(requests[0]);
643            let (offset, min_len) = match request {
644                InputRequest::AtLeast { offset, min_len } => (offset, min_len),
645                InputRequest::Range(r) => (
646                    r.offset,
647                    usize::try_from(r.length).map_err(|_| {
648                        PumpFailure::Input(InputError::Limit {
649                            needed: r.length,
650                            limit: self.max_window,
651                        })
652                    })?,
653                ),
654                _ => return Err(PumpFailure::NeedsRandomAccess(request)),
655            };
656            if offset < window.base() {
657                return Err(PumpFailure::NeedsRandomAccess(request));
658            }
659            if min_len.max(1) > self.max_window {
660                return Err(PumpFailure::WindowLimit {
661                    needed: min_len.max(1),
662                    limit: self.max_window,
663                });
664            }
665            // Skip forward to `offset`, reading and discarding.
666            while offset > window.end() && window.total.is_none() {
667                let end = window.end();
668                window.reset_to(end);
669                let want = usize::try_from(offset - end)
670                    .unwrap_or(usize::MAX)
671                    .min(self.read_size)
672                    .min(self.max_window);
673                let spare = window
674                    .try_spare(want, self.max_window)
675                    .map_err(PumpFailure::Input)?;
676                let n = reader.read(spare).map_err(PumpFailure::Io)?;
677                window.commit(n);
678                if n == 0 {
679                    window.total = Some(end);
680                }
681            }
682            if offset <= window.end() {
683                window.consume((offset - window.base()) as usize);
684            }
685            // Read until `min_len` bytes from `offset` are held, or the
686            // end.
687            let target = read_target(min_len, self.read_size)
688                .min(self.max_window)
689                .min(usize::try_from(u64::MAX - offset).unwrap_or(usize::MAX));
690            while window.len() < min_len.max(1) && window.total.is_none() {
691                let end = window.end();
692                let want = target - window.len().min(target);
693                let spare = window
694                    .try_spare(want, self.max_window)
695                    .map_err(PumpFailure::Input)?;
696                let n = reader.read(spare).map_err(PumpFailure::Io)?;
697                window.commit(n);
698                if n == 0 {
699                    window.total = Some(end);
700                }
701            }
702            Ok(())
703        }
704
705        /// Step `stage` over `reader` until it produces an output. Pass the
706        /// same reader on every call.
707        #[inline]
708        pub fn next<S, R>(
709            &mut self,
710            reader: &mut R,
711            stage: &mut S,
712            pulse: Option<&dyn zencodec::Pulse>,
713        ) -> Result<Yield<S::Output>, PumpFailure<S::Error>>
714        where
715            S: ByteStepper + ?Sized,
716            R: std::io::Read + ?Sized,
717        {
718            loop {
719                let input = self.offer();
720                let p = stage.step(input, pulse).map_err(PumpFailure::Step)?;
721                if matches!(p.step, Step::Need) {
722                    check_need(stage.requested(), &input, p.consumed)
723                        .map_err(PumpFailure::Protocol)?;
724                }
725                self.take(p.consumed);
726                match p.step {
727                    Step::Output(o) => return Ok(Yield::Output(o)),
728                    Step::Finished(o) => return Ok(Yield::Finished(o)),
729                    Step::Need => self.serve(reader, stage.requested())?,
730                }
731            }
732        }
733    }
734}