Skip to main content

qubit_io/wrappers/
async_checksum_output.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
9use std::hash::Hasher;
10use std::io;
11use std::pin::Pin;
12use std::task::Context;
13use std::task::Poll;
14
15use crate::AsyncClose;
16use crate::AsyncOutput;
17use crate::traits::normalize_async_error;
18
19/// Asynchronous byte output that hashes successfully accepted bytes.
20///
21/// Pending and failed writes do not change the hasher. The checksum algorithm
22/// and stability guarantees are those of the supplied [`Hasher`].
23///
24/// # Type Parameters
25///
26/// - `O`: Wrapped asynchronous byte output type.
27/// - `H`: Checksum hasher type.
28#[must_use]
29#[derive(Debug)]
30pub struct AsyncChecksumOutput<O, H> {
31    /// Output whose successful writes are hashed.
32    inner: O,
33    /// Hasher tracking accepted bytes.
34    hasher: H,
35}
36
37impl<O, H> AsyncClose for AsyncChecksumOutput<O, H>
38where
39    O: AsyncClose<Item = u8>,
40    H: Hasher,
41{
42    /// Polls closing through the wrapped output.
43    ///
44    /// # Parameters
45    ///
46    /// - `cx`: Task context used to register a wake-up.
47    ///
48    /// # Returns
49    ///
50    /// Returns [`Poll::Pending`] while closing is incomplete, otherwise a
51    /// ready success result.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error reported by the wrapped output. Invalid asynchronous
56    /// error kinds are normalized to [`io::ErrorKind::InvalidData`].
57    #[inline(always)]
58    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
59        // SAFETY: `inner` is never moved while projecting this pinned wrapper.
60        let this = unsafe { self.get_unchecked_mut() };
61        // SAFETY: The pinned wrapper keeps `inner` at a stable address.
62        unsafe { Pin::new_unchecked(&mut this.inner) }
63            .poll_close(cx)
64            .map(|result| result.map_err(normalize_async_error))
65    }
66}
67
68impl<O, H> AsyncChecksumOutput<O, H>
69where
70    H: Hasher,
71{
72    /// Creates a checksum-tracking asynchronous output.
73    ///
74    /// # Parameters
75    ///
76    /// - `inner`: Asynchronous byte output to wrap.
77    /// - `hasher`: Hasher updated after successful writes.
78    ///
79    /// # Returns
80    ///
81    /// Returns an output with the supplied initial hasher state.
82    #[inline(always)]
83    pub const fn new(inner: O, hasher: H) -> Self {
84        Self { inner, hasher }
85    }
86
87    /// Returns the current checksum value.
88    ///
89    /// # Returns
90    ///
91    /// Returns [`Hasher::finish`] for the current state.
92    #[inline(always)]
93    #[must_use]
94    pub fn checksum(&self) -> u64 {
95        self.hasher.finish()
96    }
97
98    /// Returns a shared reference to the wrapped output.
99    ///
100    /// # Returns
101    ///
102    /// Returns the wrapped asynchronous byte output.
103    #[inline(always)]
104    #[must_use]
105    pub const fn inner(&self) -> &O {
106        &self.inner
107    }
108
109    /// Returns a mutable reference to the wrapped output.
110    ///
111    /// Writes performed directly on the returned output do not update this
112    /// wrapper's hasher.
113    ///
114    /// # Returns
115    ///
116    /// Returns the wrapped asynchronous byte output.
117    #[inline(always)]
118    #[must_use]
119    pub fn inner_mut(&mut self) -> &mut O {
120        &mut self.inner
121    }
122
123    /// Returns a shared reference to the hasher.
124    ///
125    /// # Returns
126    ///
127    /// Returns the current hasher state.
128    #[inline(always)]
129    #[must_use]
130    pub const fn hasher(&self) -> &H {
131        &self.hasher
132    }
133
134    /// Returns a mutable reference to the hasher.
135    ///
136    /// Mutating the returned hasher changes the checksum independently of
137    /// bytes written through this wrapper.
138    ///
139    /// # Returns
140    ///
141    /// Returns the current hasher state mutably.
142    #[inline(always)]
143    #[must_use]
144    pub fn hasher_mut(&mut self) -> &mut H {
145        &mut self.hasher
146    }
147
148    /// Consumes this wrapper without flushing the wrapped output.
149    ///
150    /// This method does not call [`AsyncOutput::flush_async`] and performs no
151    /// asynchronous I/O. Any buffering owned by the returned output remains
152    /// pending and unchanged.
153    ///
154    /// # Returns
155    ///
156    /// Returns the wrapped output and final hasher state.
157    #[inline(always)]
158    #[must_use]
159    pub fn into_parts(self) -> (O, H) {
160        (self.inner, self.hasher)
161    }
162}
163
164impl<O, H> AsyncOutput for AsyncChecksumOutput<O, H>
165where
166    O: AsyncOutput<Item = u8>,
167    H: Hasher,
168{
169    /// Byte item hashed after successful writes.
170    type Item = u8;
171
172    /// Preserves the wrapped output's buffering declaration.
173    ///
174    /// # Returns
175    ///
176    /// Returns the wrapped output's buffering declaration.
177    #[inline(always)]
178    fn is_buffered(&self) -> bool {
179        self.inner.is_buffered()
180    }
181
182    /// Polls a write and hashes only bytes in a successful ready result.
183    ///
184    /// # Parameters
185    ///
186    /// - `cx`: Task context used to register a wake-up.
187    /// - `input`: Source byte slice.
188    /// - `index`: Starting source index.
189    /// - `count`: Maximum number of bytes to write.
190    ///
191    /// # Returns
192    ///
193    /// Returns [`Poll::Pending`] when the output is not ready. A ready success
194    /// contains the number of bytes accepted and hashed.
195    ///
196    /// # Errors
197    ///
198    /// Returns an I/O error reported by the wrapped output without changing the
199    /// hasher.
200    ///
201    /// # Safety
202    ///
203    /// The range `index..index + count` must be valid for `input`.
204    unsafe fn poll_write_unchecked(
205        mut self: Pin<&mut Self>,
206        cx: &mut Context<'_>,
207        input: &[u8],
208        index: usize,
209        count: usize,
210    ) -> Poll<io::Result<usize>> {
211        // SAFETY: `inner` is never moved while projecting this pinned wrapper.
212        let this = unsafe { self.as_mut().get_unchecked_mut() };
213        let source = &input[index..index + count];
214        // SAFETY: The pinned wrapper keeps `inner` at a stable address.
215        let inner = unsafe { Pin::new_unchecked(&mut this.inner) };
216        match inner.poll_write(cx, source) {
217            Poll::Ready(Ok(written)) => {
218                this.hasher.write(&input[index..index + written]);
219                Poll::Ready(Ok(written))
220            }
221            Poll::Ready(Err(error)) => Poll::Ready(Err(error)),
222            Poll::Pending => Poll::Pending,
223        }
224    }
225
226    /// Polls the wrapped output's flush operation.
227    ///
228    /// # Parameters
229    ///
230    /// - `cx`: Task context used to register a wake-up.
231    ///
232    /// # Returns
233    ///
234    /// Returns [`Poll::Pending`] while flushing is incomplete, otherwise a
235    /// ready success result.
236    ///
237    /// # Errors
238    ///
239    /// Returns an error reported by the wrapped output. Invalid asynchronous
240    /// error kinds are normalized to [`io::ErrorKind::InvalidData`].
241    #[inline(always)]
242    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
243        // SAFETY: `inner` is never moved while projecting this pinned wrapper.
244        let this = unsafe { self.get_unchecked_mut() };
245        // SAFETY: The pinned wrapper keeps `inner` at a stable address.
246        unsafe { Pin::new_unchecked(&mut this.inner) }
247            .poll_flush(cx)
248            .map(|result| result.map_err(normalize_async_error))
249    }
250}