qubit_io/ext/read_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// =============================================================================
8use std::io::{
9 ErrorKind,
10 Read,
11 Result,
12 Write,
13 copy as copy_all,
14};
15
16use crate::Streams;
17use crate::ext::internal::read_ext_impl;
18use crate::util::{
19 UncheckedSlice,
20 try_reserve_string,
21};
22
23/// Default stack buffer size used by discard operations.
24const DISCARD_BUFFER_SIZE: usize = 8 * 1024;
25
26/// Extension methods for [`Read`] values.
27///
28/// `ReadExt` fills small semantic gaps in the standard [`Read`] trait while
29/// keeping the same blocking and error model. The methods are implemented for
30/// every type that implements [`Read`], including `dyn Read` trait objects.
31///
32/// # Examples
33/// ```
34/// use qubit_io::ReadExt;
35/// use std::io::Cursor;
36///
37/// let mut input = Cursor::new(b"abcdef".to_vec());
38/// let header = input.read_exact_array::<2>()?;
39/// let payload = input.read_exact_vec_limited(4, 16)?;
40///
41/// assert_eq!(*b"ab", header);
42/// assert_eq!(b"cdef", payload.as_slice());
43/// # Ok::<(), std::io::Error>(())
44/// ```
45pub trait ReadExt: Read {
46 /// Reads bytes into a range of `buffer` without checking the range bounds
47 /// in release builds.
48 ///
49 /// This method delegates to [`Read::read`] after creating the target slice
50 /// with raw pointer arithmetic. It performs at most one read operation and
51 /// returns the number of bytes read, keeping the same short-read and error
52 /// behavior as [`Read::read`].
53 ///
54 /// # Parameters
55 /// - `buffer`: Destination buffer.
56 /// - `start_index`: Start offset inside `buffer`.
57 /// - `count`: Maximum number of bytes to read.
58 ///
59 /// # Returns
60 /// The number of bytes written into `buffer[start_index..start_index +
61 /// count]`. The value is in `0..=count`.
62 ///
63 /// # Errors
64 /// Returns the error reported by [`Read::read`].
65 ///
66 /// # Safety
67 /// The caller must guarantee that `start_index..start_index + count` is a
68 /// valid range within `buffer` and that `start_index + count` does not
69 /// overflow `usize`.
70 unsafe fn read_unchecked(
71 &mut self,
72 buffer: &mut [u8],
73 start_index: usize,
74 count: usize,
75 ) -> Result<usize>;
76
77 /// Reads exactly `count` bytes into a range of `buffer` without checking
78 /// the range bounds in release builds.
79 ///
80 /// This method delegates to [`Read::read_exact`] after creating the target
81 /// slice with raw pointer arithmetic. It keeps the same blocking and error
82 /// behavior as [`Read::read_exact`].
83 ///
84 /// # Parameters
85 /// - `buffer`: Destination buffer.
86 /// - `start_index`: Start offset inside `buffer`.
87 /// - `count`: Number of bytes to read.
88 ///
89 /// # Errors
90 /// Returns the error reported by [`Read::read_exact`], including
91 /// [`ErrorKind::UnexpectedEof`] when EOF is reached before `count` bytes
92 /// are read.
93 ///
94 /// # Safety
95 /// The caller must guarantee that `start_index..start_index + count` is a
96 /// valid range within `buffer` and that `start_index + count` does not
97 /// overflow `usize`.
98 unsafe fn read_exact_unchecked(
99 &mut self,
100 buffer: &mut [u8],
101 start_index: usize,
102 count: usize,
103 ) -> Result<()>;
104
105 /// Reads bytes into a range of `buffer` until that range is full or EOF is
106 /// reached, without checking the range bounds in release builds.
107 ///
108 /// This method has the same EOF and retry behavior as
109 /// [`ReadExt::read_exact_or_eof`], but writes into
110 /// `buffer[start_index..start_index + count]` using raw pointer
111 /// arithmetic.
112 ///
113 /// # Parameters
114 /// - `buffer`: Destination buffer.
115 /// - `start_index`: Start offset inside `buffer`.
116 /// - `count`: Number of bytes to try to read.
117 ///
118 /// # Returns
119 /// The number of bytes written into `buffer[start_index..start_index +
120 /// count]`. The value is in `0..=count`.
121 ///
122 /// # Errors
123 /// Returns the first non-[`ErrorKind::Interrupted`] error reported by the
124 /// underlying reader. Interrupted reads are retried.
125 ///
126 /// # Safety
127 /// The caller must guarantee that `start_index..start_index + count` is a
128 /// valid range within `buffer` and that `start_index + count` does not
129 /// overflow `usize`.
130 unsafe fn read_exact_or_eof_unchecked(
131 &mut self,
132 buffer: &mut [u8],
133 start_index: usize,
134 count: usize,
135 ) -> Result<usize>;
136
137 /// Reads bytes until `buffer` is full or EOF is reached.
138 ///
139 /// This method differs from [`Read::read_exact`] by treating EOF as a
140 /// successful partial result. It keeps retrying short reads until the
141 /// caller-provided buffer is full, EOF is reached, or a non-interrupted
142 /// I/O error occurs.
143 ///
144 /// # Parameters
145 /// - `buffer`: Destination buffer to fill.
146 ///
147 /// # Returns
148 /// The number of bytes written into `buffer`. The value is in
149 /// `0..=buffer.len()`.
150 ///
151 /// # Errors
152 /// Returns the first non-[`ErrorKind::Interrupted`] error reported by the
153 /// underlying reader. Interrupted reads are retried.
154 fn read_exact_or_eof(&mut self, buffer: &mut [u8]) -> Result<usize>;
155
156 /// Reads exactly `N` bytes into a stack-allocated array.
157 ///
158 /// This method uses [`Read::read_exact`] and therefore requires the reader
159 /// to provide exactly `N` bytes before EOF.
160 ///
161 /// # Returns
162 /// An array containing exactly `N` bytes read from this reader.
163 ///
164 /// # Errors
165 /// Returns the error reported by [`Read::read_exact`], including
166 /// [`ErrorKind::UnexpectedEof`] when EOF is reached before the array is
167 /// full.
168 fn read_exact_array<const N: usize>(&mut self) -> Result<[u8; N]>;
169
170 /// Reads exactly `len` bytes into a new vector after checking a limit.
171 ///
172 /// If `len` is greater than `max_len`, this method returns
173 /// [`ErrorKind::InvalidData`] before reading any bytes.
174 ///
175 /// # Parameters
176 /// - `len`: Exact number of bytes to read.
177 /// - `max_len`: Maximum accepted exact read length.
178 ///
179 /// # Returns
180 /// A vector containing exactly `len` bytes.
181 ///
182 /// # Errors
183 /// Returns [`ErrorKind::InvalidData`] when `len > max_len`. Returns the
184 /// error reported by [`Read::read_exact`], including
185 /// [`ErrorKind::UnexpectedEof`] when EOF is reached before `len` bytes are
186 /// read.
187 fn read_exact_vec_limited(
188 &mut self,
189 len: usize,
190 max_len: usize,
191 ) -> Result<Vec<u8>>;
192
193 /// Reads exactly `len` bytes and appends them to `output`.
194 ///
195 /// If `len` is greater than `max_len`, this method returns
196 /// [`ErrorKind::InvalidData`] before reading any bytes and leaves `output`
197 /// unchanged. On a read error, `output` is truncated back to its original
198 /// length. The underlying reader may still have consumed bytes before the
199 /// error because [`Read`] does not provide rollback.
200 ///
201 /// # Parameters
202 /// - `output`: Destination vector to append to.
203 /// - `len`: Exact number of bytes to read.
204 /// - `max_len`: Maximum accepted exact read length.
205 ///
206 /// # Errors
207 /// Returns [`ErrorKind::InvalidData`] when `len > max_len`. Returns the
208 /// error reported by [`Read::read_exact`], including
209 /// [`ErrorKind::UnexpectedEof`] when EOF is reached before `len` bytes are
210 /// read.
211 fn read_exact_vec_limited_into(
212 &mut self,
213 output: &mut Vec<u8>,
214 len: usize,
215 max_len: usize,
216 ) -> Result<()>;
217
218 /// Discards up to `bytes` bytes from this reader.
219 ///
220 /// The method repeatedly reads into an internal stack buffer until the
221 /// requested number of bytes has been consumed or EOF is reached. It does
222 /// not allocate and does not require seeking support.
223 ///
224 /// # Parameters
225 /// - `bytes`: Maximum number of bytes to discard.
226 ///
227 /// # Returns
228 /// The number of bytes actually discarded. The value may be smaller than
229 /// `bytes` when EOF is reached first.
230 ///
231 /// # Errors
232 /// Returns the first non-[`ErrorKind::Interrupted`] error reported by the
233 /// underlying reader. Interrupted reads are retried.
234 fn discard_exact_or_eof(&mut self, bytes: u64) -> Result<u64>;
235
236 /// Copies all remaining bytes from this reader into `writer`.
237 ///
238 /// This method is a method-style wrapper around [`std::io::copy`]. It
239 /// copies from the current reader position until EOF and does not close or
240 /// flush either stream.
241 ///
242 /// # Parameters
243 /// - `writer`: Destination writer.
244 ///
245 /// # Returns
246 /// The number of bytes copied.
247 ///
248 /// # Errors
249 /// Returns the first read or write error reported by the underlying
250 /// streams, using the same error behavior as [`std::io::copy`].
251 fn copy_to(&mut self, writer: &mut dyn Write) -> Result<u64>;
252
253 /// Copies at most `max_bytes` bytes from this reader into `writer`.
254 ///
255 /// This method stops successfully when either EOF is reached or
256 /// `max_bytes` bytes have been copied. It does not close or flush either
257 /// stream.
258 ///
259 /// # Parameters
260 /// - `writer`: Destination writer.
261 /// - `max_bytes`: Maximum number of bytes to copy.
262 ///
263 /// # Returns
264 /// The number of bytes copied.
265 ///
266 /// # Errors
267 /// Returns the first non-[`ErrorKind::Interrupted`] read error or write
268 /// error reported by the underlying streams. Interrupted reads are retried.
269 fn copy_to_at_most(
270 &mut self,
271 writer: &mut dyn Write,
272 max_bytes: u64,
273 ) -> Result<u64>;
274
275 /// Copies the remaining input if its total length is at most `max_bytes`.
276 ///
277 /// This method copies from the current reader position until EOF. If EOF is
278 /// not reached within `max_bytes` bytes, it returns
279 /// [`ErrorKind::InvalidData`]. Detecting oversized input consumes one
280 /// excess byte from this reader; that excess byte is not written to
281 /// `writer`.
282 ///
283 /// Unlike bounded reads into in-memory collections, this method cannot roll
284 /// back bytes already accepted by `writer` when the limit is exceeded
285 /// because [`Write`] does not provide truncation. On
286 /// [`ErrorKind::InvalidData`], up to `max_bytes` bytes may remain in
287 /// `writer`.
288 ///
289 /// # Parameters
290 /// - `writer`: Destination writer.
291 /// - `max_bytes`: Maximum accepted number of bytes in the remaining input.
292 ///
293 /// # Returns
294 /// The number of bytes copied when EOF is reached within the limit.
295 ///
296 /// # Errors
297 /// Returns [`ErrorKind::InvalidData`] when the remaining input is longer
298 /// than `max_bytes`. Returns the first non-[`ErrorKind::Interrupted`] read
299 /// error or write error reported by the underlying streams. Interrupted
300 /// reads are retried.
301 fn copy_to_end_limited(
302 &mut self,
303 writer: &mut dyn Write,
304 max_bytes: u64,
305 ) -> Result<u64>;
306
307 /// Reads the remaining bytes into a vector with a maximum accepted length.
308 ///
309 /// This method consumes bytes from the current reader position until EOF is
310 /// reached. If the stream contains more than `max_len` bytes, it returns
311 /// [`ErrorKind::InvalidData`] after detecting the first excess byte. No
312 /// vector is returned on failure.
313 ///
314 /// # Parameters
315 /// - `max_len`: Maximum number of bytes accepted in the returned vector.
316 ///
317 /// # Returns
318 /// A vector containing all remaining bytes when the stream length is within
319 /// the limit.
320 ///
321 /// # Errors
322 /// Returns [`ErrorKind::InvalidData`] when the stream contains more than
323 /// `max_len` bytes. Returns the first non-[`ErrorKind::Interrupted`] error
324 /// reported by the underlying reader; interrupted reads are retried.
325 fn read_to_end_limited(&mut self, max_len: usize) -> Result<Vec<u8>>;
326
327 /// Reads the remaining bytes into `output` with a maximum accepted length.
328 ///
329 /// This method appends at most `max_len` bytes from the current reader
330 /// position to `output`. If the stream contains more than `max_len` bytes,
331 /// it returns [`ErrorKind::InvalidData`] after detecting the first excess
332 /// byte and truncates `output` back to its original length. The underlying
333 /// reader may still have consumed bytes before the error because [`Read`]
334 /// does not provide rollback.
335 ///
336 /// # Parameters
337 /// - `output`: Destination vector to append to.
338 /// - `max_len`: Maximum number of bytes accepted from this reader.
339 ///
340 /// # Returns
341 /// The number of bytes appended to `output`.
342 ///
343 /// # Errors
344 /// Returns [`ErrorKind::InvalidData`] when the stream contains more than
345 /// `max_len` bytes. Returns the first non-[`ErrorKind::Interrupted`] error
346 /// reported by the underlying reader; interrupted reads are retried.
347 fn read_to_end_limited_into(
348 &mut self,
349 output: &mut Vec<u8>,
350 max_len: usize,
351 ) -> Result<usize>;
352
353 /// Reads the remaining bytes as UTF-8 text with a maximum accepted length.
354 ///
355 /// This method has the same size limit and read semantics as
356 /// [`ReadExt::read_to_end_limited`], then validates the collected bytes as
357 /// UTF-8.
358 ///
359 /// # Parameters
360 /// - `max_len`: Maximum number of bytes accepted before UTF-8 decoding.
361 ///
362 /// # Returns
363 /// The decoded UTF-8 string.
364 ///
365 /// # Errors
366 /// Returns [`ErrorKind::InvalidData`] when the stream contains more than
367 /// `max_len` bytes or when the collected bytes are not valid UTF-8. Returns
368 /// the first non-[`ErrorKind::Interrupted`] error reported by the
369 /// underlying reader; interrupted reads are retried.
370 fn read_to_string_limited(&mut self, max_len: usize) -> Result<String>;
371
372 /// Reads the remaining bytes as UTF-8 text and appends to `output`.
373 ///
374 /// This method accepts at most `max_len` bytes from the current reader
375 /// position, validates them as UTF-8, and appends the decoded text to
376 /// `output`. If the input is oversized or invalid UTF-8, `output` is
377 /// truncated back to its original length. Oversized input may still consume
378 /// up to `max_len + 1` bytes from the reader while detecting the limit
379 /// violation.
380 ///
381 /// # Parameters
382 /// - `output`: Destination string to append to.
383 /// - `max_len`: Maximum number of bytes accepted before UTF-8 decoding.
384 ///
385 /// # Returns
386 /// The number of bytes appended to `output`.
387 ///
388 /// # Errors
389 /// Returns [`ErrorKind::InvalidData`] when the stream contains more than
390 /// `max_len` bytes or when the collected bytes are not valid UTF-8. Returns
391 /// the first non-[`ErrorKind::Interrupted`] error reported by the
392 /// underlying reader; interrupted reads are retried.
393 fn read_to_string_limited_into(
394 &mut self,
395 output: &mut String,
396 max_len: usize,
397 ) -> Result<usize>;
398}
399
400impl<T> ReadExt for T
401where
402 T: Read + ?Sized,
403{
404 #[inline(always)]
405 unsafe fn read_unchecked(
406 &mut self,
407 buffer: &mut [u8],
408 start_index: usize,
409 count: usize,
410 ) -> Result<usize> {
411 debug_assert!(
412 UncheckedSlice::range_fits(buffer.len(), start_index, count),
413 "unchecked read range exceeds buffer"
414 );
415 // SAFETY: The caller guarantees that the requested range is valid for
416 // `buffer`.
417 let target =
418 unsafe { UncheckedSlice::subslice_mut(buffer, start_index, count) };
419 self.read(target)
420 }
421
422 unsafe fn read_exact_or_eof_unchecked(
423 &mut self,
424 buffer: &mut [u8],
425 start_index: usize,
426 count: usize,
427 ) -> Result<usize> {
428 debug_assert!(
429 UncheckedSlice::range_fits(buffer.len(), start_index, count),
430 "unchecked read range exceeds buffer"
431 );
432 let mut total = 0;
433 while total < count {
434 // SAFETY: The caller guarantees that `start_index..start_index +
435 // count` is valid for `buffer`; `total < count`, so this remaining
436 // suffix is also a valid mutable subslice.
437 let target = unsafe {
438 UncheckedSlice::subslice_mut(
439 buffer,
440 start_index + total,
441 count - total,
442 )
443 };
444 match self.read(target) {
445 Ok(0) => break,
446 Ok(read) => total += read,
447 Err(error) => {
448 if error.kind() == ErrorKind::Interrupted {
449 continue;
450 }
451 return Err(error);
452 }
453 }
454 }
455 Ok(total)
456 }
457
458 unsafe fn read_exact_unchecked(
459 &mut self,
460 buffer: &mut [u8],
461 start_index: usize,
462 count: usize,
463 ) -> Result<()> {
464 debug_assert!(
465 UncheckedSlice::range_fits(buffer.len(), start_index, count),
466 "unchecked read range exceeds buffer"
467 );
468 // SAFETY: The caller guarantees that the requested range is valid for
469 // `buffer`.
470 let target =
471 unsafe { UncheckedSlice::subslice_mut(buffer, start_index, count) };
472 self.read_exact(target)
473 }
474
475 fn read_exact_or_eof(&mut self, buffer: &mut [u8]) -> Result<usize> {
476 let mut reader = self;
477 read_ext_impl::read_exact_or_eof(&mut reader, buffer)
478 }
479
480 #[inline(always)]
481 fn read_exact_array<const N: usize>(&mut self) -> Result<[u8; N]> {
482 let mut buffer = [0; N];
483 self.read_exact(&mut buffer)?;
484 Ok(buffer)
485 }
486
487 fn read_exact_vec_limited(
488 &mut self,
489 len: usize,
490 max_len: usize,
491 ) -> Result<Vec<u8>> {
492 let mut output = Vec::new();
493 let mut reader = self;
494 read_ext_impl::read_exact_vec_limited_into(
495 &mut reader,
496 &mut output,
497 len,
498 max_len,
499 )?;
500 Ok(output)
501 }
502
503 #[inline(always)]
504 fn read_exact_vec_limited_into(
505 &mut self,
506 output: &mut Vec<u8>,
507 len: usize,
508 max_len: usize,
509 ) -> Result<()> {
510 let mut reader = self;
511 read_ext_impl::read_exact_vec_limited_into(
512 &mut reader,
513 output,
514 len,
515 max_len,
516 )
517 }
518
519 fn discard_exact_or_eof(&mut self, bytes: u64) -> Result<u64> {
520 let mut buffer = [0; DISCARD_BUFFER_SIZE];
521 let mut remaining = bytes;
522 let mut discarded = 0;
523 while remaining > 0 {
524 let requested = remaining.min(DISCARD_BUFFER_SIZE as u64) as usize;
525 match self.read(&mut buffer[..requested]) {
526 Ok(0) => break,
527 Ok(count) => {
528 let count = count as u64;
529 remaining -= count;
530 discarded += count;
531 }
532 Err(error) => {
533 if error.kind() == ErrorKind::Interrupted {
534 continue;
535 }
536 return Err(error);
537 }
538 }
539 }
540 Ok(discarded)
541 }
542
543 #[inline(always)]
544 fn copy_to(&mut self, writer: &mut dyn Write) -> Result<u64> {
545 copy_all(self, writer)
546 }
547
548 #[inline(always)]
549 fn copy_to_at_most(
550 &mut self,
551 writer: &mut dyn Write,
552 max_bytes: u64,
553 ) -> Result<u64> {
554 let mut reader = self;
555 Streams::copy_at_most(&mut reader, writer, max_bytes)
556 }
557
558 #[inline(always)]
559 fn copy_to_end_limited(
560 &mut self,
561 writer: &mut dyn Write,
562 max_bytes: u64,
563 ) -> Result<u64> {
564 let mut reader = self;
565 Streams::copy_to_end_limited(&mut reader, writer, max_bytes)
566 }
567
568 #[inline(always)]
569 fn read_to_end_limited(&mut self, max_len: usize) -> Result<Vec<u8>> {
570 let mut reader = self;
571 read_ext_impl::read_to_end_limited(&mut reader, max_len)
572 }
573
574 #[inline(always)]
575 fn read_to_end_limited_into(
576 &mut self,
577 output: &mut Vec<u8>,
578 max_len: usize,
579 ) -> Result<usize> {
580 let mut reader = self;
581 read_ext_impl::read_to_end_limited_into(&mut reader, output, max_len)
582 }
583
584 fn read_to_string_limited(&mut self, max_len: usize) -> Result<String> {
585 let mut reader = self;
586 let bytes = read_ext_impl::read_to_end_limited(&mut reader, max_len)?;
587 String::from_utf8(bytes).map_err(read_ext_impl::invalid_utf8_error)
588 }
589
590 fn read_to_string_limited_into(
591 &mut self,
592 output: &mut String,
593 max_len: usize,
594 ) -> Result<usize> {
595 let mut reader = self;
596 let bytes = read_ext_impl::read_to_end_limited(&mut reader, max_len)?;
597 let text = String::from_utf8(bytes)
598 .map_err(read_ext_impl::invalid_utf8_error)?;
599 let count = text.len();
600 try_reserve_string(output, count)?;
601 output.push_str(&text);
602 Ok(count)
603 }
604}