Skip to main content

qubit_io/ext/
input_ext.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8// qubit-style: allow coverage-cfg
9
10#[cfg(coverage)]
11use std::cell::Cell;
12use std::io::{
13    Error,
14    ErrorKind,
15    Result,
16};
17
18use crate::capacity_const::DEFAULT_BUFFER_CAPACITY;
19use crate::ext::output_ext::OutputExt;
20use crate::traits::validate_read_count;
21use crate::util::{
22    UncheckedSlice,
23    create_vec,
24    try_reserve_vec,
25};
26use crate::{
27    Input,
28    Output,
29};
30
31/// Extension methods for [`Input`] values.
32///
33/// `InputExt` keeps convenience and complete-read helpers outside the minimal
34/// [`Input`] trait. The methods are implemented for every item-oriented input,
35/// including `dyn Input` trait objects.
36pub trait InputExt: Input {
37    /// Reads exactly enough items to fill `output`.
38    ///
39    /// # Parameters
40    /// - `output`: Destination storage to fill.
41    ///
42    /// # Errors
43    /// Returns [`ErrorKind::UnexpectedEof`] when EOF is reached before the
44    /// output slice is full. Returns the first non-[`ErrorKind::Interrupted`]
45    /// error reported by the input. Interrupted reads are retried. Returns
46    /// [`ErrorKind::InvalidData`] if the input reports more items than
47    /// requested.
48    #[inline]
49    fn read_exact(&mut self, output: &mut [Self::Item]) -> Result<()> {
50        let read = self.read_exact_or_eof(output)?;
51        if read == output.len() {
52            Ok(())
53        } else {
54            Err(Error::new(
55                ErrorKind::UnexpectedEof,
56                "failed to fill whole buffer",
57            ))
58        }
59    }
60
61    /// Reads exactly `count` items into an indexed output range without
62    /// checking the range bounds in release builds.
63    ///
64    /// This method has the same blocking and error behavior as
65    /// [`InputExt::read_exact`], but writes into
66    /// `output[index..index + count]` using indexed unchecked reads.
67    ///
68    /// # Parameters
69    /// - `output`: Destination storage.
70    /// - `index`: Start index inside `output`.
71    /// - `count`: Number of items to read.
72    ///
73    /// # Errors
74    /// Returns [`ErrorKind::UnexpectedEof`] when EOF is reached before the
75    /// range is full. Returns the first non-[`ErrorKind::Interrupted`] error
76    /// reported by the input. Interrupted reads are retried. Returns
77    /// [`ErrorKind::InvalidData`] if the input reports more items than
78    /// requested.
79    ///
80    /// # Safety
81    /// The caller must guarantee that `index..index + count` is a valid range
82    /// inside `output` and that the addition does not overflow.
83    #[inline]
84    unsafe fn read_exact_unchecked(
85        &mut self,
86        output: &mut [Self::Item],
87        index: usize,
88        count: usize,
89    ) -> Result<()> {
90        let read =
91            unsafe { self.read_exact_or_eof_unchecked(output, index, count)? };
92        if read == count {
93            Ok(())
94        } else {
95            Err(Error::new(
96                ErrorKind::UnexpectedEof,
97                "failed to fill whole buffer",
98            ))
99        }
100    }
101
102    /// Reads items until `output` is full or EOF is reached.
103    ///
104    /// This method treats EOF as a successful partial result. It keeps retrying
105    /// short reads until the output slice is full, EOF is reached, or a
106    /// non-interrupted error occurs.
107    ///
108    /// # Parameters
109    /// - `output`: Destination storage to fill.
110    ///
111    /// # Returns
112    /// The number of items written to `output`.
113    ///
114    /// # Errors
115    /// Returns the first non-[`ErrorKind::Interrupted`] error reported by the
116    /// input. Interrupted reads are retried. Returns [`ErrorKind::InvalidData`]
117    /// if the input reports more items than requested.
118    fn read_exact_or_eof(
119        &mut self,
120        output: &mut [Self::Item],
121    ) -> Result<usize> {
122        let mut total = 0;
123        while total < output.len() {
124            let remaining = output.len() - total;
125            // SAFETY: `total..output.len()` is a valid suffix of `output`.
126            match unsafe { self.read_unchecked(output, total, remaining) } {
127                Ok(0) => break,
128                Ok(read) => {
129                    validate_read_count(read, remaining)?;
130                    total += read;
131                }
132                Err(error) if error.kind() == ErrorKind::Interrupted => {}
133                Err(error) => return Err(error),
134            }
135        }
136        Ok(total)
137    }
138
139    /// Reads items into an indexed output range until that range is full or
140    /// EOF is reached, without checking the range bounds in release builds.
141    ///
142    /// This method has the same EOF and retry behavior as
143    /// [`InputExt::read_exact_or_eof`], but writes into
144    /// `output[index..index + count]` using indexed unchecked reads.
145    ///
146    /// # Parameters
147    /// - `output`: Destination storage.
148    /// - `index`: Start index inside `output`.
149    /// - `count`: Number of items to try to read.
150    ///
151    /// # Returns
152    /// The number of items written into `output[index..index + count]`. The
153    /// value is in `0..=count`.
154    ///
155    /// # Errors
156    /// Returns the first non-[`ErrorKind::Interrupted`] error reported by the
157    /// input. Interrupted reads are retried. Returns [`ErrorKind::InvalidData`]
158    /// if the input reports more items than requested.
159    ///
160    /// # Safety
161    /// The caller must guarantee that `index..index + count` is a valid range
162    /// inside `output` and that the addition does not overflow.
163    unsafe fn read_exact_or_eof_unchecked(
164        &mut self,
165        output: &mut [Self::Item],
166        index: usize,
167        count: usize,
168    ) -> Result<usize> {
169        debug_assert!(
170            UncheckedSlice::range_fits(output.len(), index, count),
171            "unchecked read range exceeds output buffer"
172        );
173        let mut total = 0;
174        while total < count {
175            let remaining = count - total;
176            // SAFETY: The caller guarantees the original destination range is
177            // valid; `total < count`, so this suffix remains inside it.
178            match unsafe {
179                self.read_unchecked(output, index + total, remaining)
180            } {
181                Ok(0) => break,
182                Ok(read) => {
183                    validate_read_count(read, remaining)?;
184                    total += read;
185                }
186                Err(error) if error.kind() == ErrorKind::Interrupted => {}
187                Err(error) => return Err(error),
188            }
189        }
190        Ok(total)
191    }
192
193    /// Copies all remaining items from this input into `output`.
194    ///
195    /// The method allocates a reusable heap buffer and copies until EOF. It
196    /// does not close or flush the output.
197    ///
198    /// # Parameters
199    /// - `output`: Destination output.
200    ///
201    /// # Returns
202    /// The number of items copied.
203    ///
204    /// # Errors
205    /// Returns the first non-[`ErrorKind::Interrupted`] read error or output
206    /// error reported by the underlying streams. Interrupted reads are retried.
207    /// Returns [`ErrorKind::InvalidData`] if the input reports more items than
208    /// requested.
209    fn copy_to<O>(&mut self, output: &mut O) -> Result<u64>
210    where
211        O: Output<Item = Self::Item> + ?Sized,
212        Self::Item: Copy + Default,
213    {
214        let mut buffer =
215            create_vec(DEFAULT_BUFFER_CAPACITY, Self::Item::default())?;
216        let mut copied = 0_u64;
217        loop {
218            let requested = buffer.len();
219            let read = read_retrying_interrupted_limited(
220                self,
221                &mut buffer,
222                requested,
223            )?;
224            if read == 0 {
225                return Ok(copied);
226            }
227            // SAFETY: `read` has been validated against `buffer.len()`.
228            unsafe {
229                output.write_all_unchecked(&buffer, 0, read)?;
230            }
231            copied = add_copied(copied, read)?;
232        }
233    }
234
235    /// Copies at most `max_units` items from this input into `output`.
236    ///
237    /// This method stops successfully when either EOF is reached or
238    /// `max_units` items have been copied. It does not close or flush the
239    /// output.
240    ///
241    /// # Parameters
242    /// - `output`: Destination output.
243    /// - `max_units`: Maximum number of items to copy.
244    ///
245    /// # Returns
246    /// The number of items copied.
247    ///
248    /// # Errors
249    /// Returns the first non-[`ErrorKind::Interrupted`] read error or output
250    /// error reported by the underlying streams. Interrupted reads are retried.
251    /// Returns [`ErrorKind::InvalidData`] if the input reports more items than
252    /// requested.
253    fn copy_to_at_most<O>(
254        &mut self,
255        output: &mut O,
256        max_units: u64,
257    ) -> Result<u64>
258    where
259        O: Output<Item = Self::Item> + ?Sized,
260        Self::Item: Copy + Default,
261    {
262        if max_units == 0 {
263            return Ok(0);
264        }
265        let mut buffer =
266            create_vec(DEFAULT_BUFFER_CAPACITY, Self::Item::default())?;
267        let mut remaining = max_units;
268        let mut copied = 0_u64;
269        while remaining > 0 {
270            let requested = remaining.min(buffer.len() as u64) as usize;
271            let read = read_retrying_interrupted_limited(
272                self,
273                &mut buffer,
274                requested,
275            )?;
276            if read == 0 {
277                break;
278            }
279            // SAFETY: `read` has been validated against the requested prefix.
280            unsafe {
281                output.write_all_unchecked(&buffer, 0, read)?;
282            }
283            let read = read as u64;
284            remaining -= read;
285            copied += read;
286        }
287        Ok(copied)
288    }
289
290    /// Copies the remaining input if its total length is at most `max_units`.
291    ///
292    /// This method copies from the current input position until EOF. If EOF is
293    /// not reached within `max_units` items, it returns
294    /// [`ErrorKind::InvalidData`]. Detecting oversized input consumes one
295    /// excess item from this input; that excess item is not written to
296    /// `output`.
297    ///
298    /// Oversized input, read errors, and allocation failures before output
299    /// flushing leave `output` unchanged. Once EOF is reached and collected
300    /// items are written to `output`, a write error may leave partial items
301    /// accepted by `output` because [`Output`] has no rollback operation.
302    ///
303    /// # Parameters
304    /// - `output`: Destination output.
305    /// - `max_units`: Maximum accepted number of remaining input items.
306    ///
307    /// # Returns
308    /// The number of items copied when EOF is reached within the limit.
309    ///
310    /// # Errors
311    /// Returns [`ErrorKind::InvalidData`] when the remaining input is longer
312    /// than `max_units`. Returns the first non-[`ErrorKind::Interrupted`] read
313    /// error or output error reported by the underlying streams. Interrupted
314    /// reads are retried. Returns [`ErrorKind::InvalidData`] if the input
315    /// reports more items than requested.
316    fn copy_to_end_limited<O>(
317        &mut self,
318        output: &mut O,
319        max_units: u64,
320    ) -> Result<u64>
321    where
322        O: Output<Item = Self::Item> + ?Sized,
323        Self::Item: Copy + Default,
324    {
325        let mut buffer =
326            create_vec(DEFAULT_BUFFER_CAPACITY, Self::Item::default())?;
327        let mut collected = Vec::new();
328        let mut remaining = max_units;
329        let mut copied = 0_u64;
330        loop {
331            let requested =
332                remaining.saturating_add(1).min(buffer.len() as u64) as usize;
333            let read = read_retrying_interrupted_limited(
334                self,
335                &mut buffer,
336                requested,
337            )?;
338            if read == 0 {
339                if !collected.is_empty() {
340                    // SAFETY: `collected` contains exactly the items validated
341                    // below.
342                    unsafe {
343                        output.write_all_unchecked(
344                            &collected,
345                            0,
346                            collected.len(),
347                        )?;
348                    }
349                }
350                return Ok(copied);
351            }
352            if (read as u64) > remaining {
353                return Err(Error::new(
354                    ErrorKind::InvalidData,
355                    format!(
356                        "input exceeds maximum length of {max_units} items"
357                    ),
358                ));
359            }
360            try_reserve_vec(&mut collected, read)?;
361            collected.extend_from_slice(&buffer[..read]);
362            let read = read as u64;
363            remaining -= read;
364            copied = add_copied(copied, read as usize)?;
365        }
366    }
367}
368
369impl<T> InputExt for T where T: Input + ?Sized {}
370
371/// Reads into a buffer prefix while retrying interrupted reads.
372///
373/// # Parameters
374/// - `input`: Source input.
375/// - `buffer`: Destination buffer.
376/// - `requested`: Number of items to request.
377///
378/// # Returns
379/// The number of items read.
380///
381/// # Errors
382/// Returns the first non-interrupted input error. Returns
383/// [`ErrorKind::InvalidData`] if the input reports more items than requested.
384fn read_retrying_interrupted_limited<I>(
385    input: &mut I,
386    buffer: &mut [I::Item],
387    requested: usize,
388) -> Result<usize>
389where
390    I: Input + ?Sized,
391{
392    loop {
393        // SAFETY: Callers pass a valid prefix length within `buffer`.
394        match unsafe { input.read_unchecked(buffer, 0, requested) } {
395            Ok(read) => {
396                validate_read_count(read, requested)?;
397                return Ok(read);
398            }
399            Err(error) if error.kind() == ErrorKind::Interrupted => {}
400            Err(error) => return Err(error),
401        }
402    }
403}
404
405#[cfg(coverage)]
406thread_local! {
407    static COVERAGE_FAIL_NEXT_ADD_COPIED: Cell<bool> = const { Cell::new(false) };
408}
409
410/// Makes the next [`add_copied`] call fail.
411///
412/// Coverage-only helper for exercising overflow propagation inside copy loops.
413#[cfg(coverage)]
414#[doc(hidden)]
415pub fn coverage_fail_next_add_copied() {
416    COVERAGE_FAIL_NEXT_ADD_COPIED.with(|state| state.set(true));
417}
418
419/// Clears coverage-only add_copied hooks between tests.
420#[cfg(coverage)]
421#[doc(hidden)]
422pub fn coverage_reset_add_copied_hooks() {
423    COVERAGE_FAIL_NEXT_ADD_COPIED.with(|state| state.set(false));
424}
425
426#[cfg(coverage)]
427#[doc(hidden)]
428/// Triggers natural add_copied overflow for coverage assertions.
429pub fn coverage_natural_add_copied_overflow() -> Result<u64> {
430    add_copied(u64::MAX, 1)
431}
432
433/// Adds a copied item count to an accumulated total.
434///
435/// # Parameters
436/// - `copied`: Existing copied item count.
437/// - `read`: Newly copied item count.
438///
439/// # Returns
440/// The updated copied item count.
441///
442/// # Errors
443/// Returns [`ErrorKind::InvalidData`] if the count overflows `u64`.
444#[inline(always)]
445fn add_copied(copied: u64, read: usize) -> Result<u64> {
446    #[cfg(coverage)]
447    if COVERAGE_FAIL_NEXT_ADD_COPIED.with(|state| {
448        let fail = state.get();
449        if fail {
450            state.set(false);
451        }
452        fail
453    }) {
454        return Err(Error::new(
455            ErrorKind::InvalidData,
456            "copied item count overflows u64",
457        ));
458    }
459    copied.checked_add(read as u64).ok_or_else(|| {
460        Error::new(ErrorKind::InvalidData, "copied item count overflows u64")
461    })
462}