Skip to main content

block_buffer/
read.rs

1use super::{Array, BlockSizes, Error};
2use core::fmt;
3
4/// Buffer for reading block-generated data.
5pub struct ReadBuffer<BS: BlockSizes> {
6    /// The first byte of the block is used as cursor position.
7    /// `&buffer[usize::from(buffer[0])..]` is interpreted as unread bytes.
8    /// The cursor position is always bigger than zero and smaller than or equal to block size.
9    buffer: Array<u8, BS>,
10}
11
12impl<BS: BlockSizes> fmt::Debug for ReadBuffer<BS> {
13    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14        f.debug_struct("ReadBuffer")
15            .field("remaining_data", &self.remaining())
16            .finish_non_exhaustive()
17    }
18}
19
20impl<BS: BlockSizes> Default for ReadBuffer<BS> {
21    #[inline]
22    fn default() -> Self {
23        let buffer = Default::default();
24        let mut res = Self { buffer };
25        // SAFETY: `BS::USIZE` satisfies the `set_pos_unchecked` safety contract
26        unsafe { res.set_pos_unchecked(BS::USIZE) };
27        res
28    }
29}
30
31impl<BS: BlockSizes> Clone for ReadBuffer<BS> {
32    #[inline]
33    fn clone(&self) -> Self {
34        let buffer = self.buffer.clone();
35        Self { buffer }
36    }
37}
38
39impl<BS: BlockSizes> ReadBuffer<BS> {
40    /// Return current cursor position, i.e. how many bytes were read from the buffer.
41    #[inline(always)]
42    pub fn get_pos(&self) -> usize {
43        let pos = self.buffer[0];
44        if pos == 0 || pos > BS::U8 {
45            debug_assert!(false);
46            // SAFETY: `pos` never breaks the invariant
47            unsafe {
48                core::hint::unreachable_unchecked();
49            }
50        }
51        pos as usize
52    }
53
54    /// Return size of the internal buffer in bytes.
55    #[inline(always)]
56    pub fn size(&self) -> usize {
57        BS::USIZE
58    }
59
60    /// Return number of remaining bytes in the internal buffer.
61    #[inline(always)]
62    pub fn remaining(&self) -> usize {
63        self.size() - self.get_pos()
64    }
65
66    /// Set cursor position.
67    ///
68    /// # Safety
69    /// `pos` must be smaller than or equal to the buffer block size and be bigger than zero.
70    #[inline(always)]
71    #[allow(clippy::cast_possible_truncation)]
72    unsafe fn set_pos_unchecked(&mut self, pos: usize) {
73        debug_assert!(pos != 0 && pos <= BS::USIZE);
74        self.buffer[0] = pos as u8;
75    }
76
77    /// Read up to `len` bytes of remaining data in the buffer.
78    ///
79    /// Returns slice with length of `ret_len = min(len, buffer.remaining())` bytes
80    /// and sets the cursor position to `buffer.get_pos() + ret_len`.
81    #[inline(always)]
82    pub fn read_cached(&mut self, len: usize) -> &[u8] {
83        let rem = self.remaining();
84        let new_len = core::cmp::min(rem, len);
85        let pos = self.get_pos();
86
87        // SAFETY: `pos + new_len` is not equal to zero and not bigger than block size
88        unsafe { self.set_pos_unchecked(pos + new_len) };
89        &self.buffer[pos..][..new_len]
90    }
91
92    /// Write new block and consume `read_len` bytes from it.
93    ///
94    /// If `read_len` is equal to zero, immediately returns without calling the closures.
95    /// Otherwise, the method calls `gen_block` to fill the internal buffer,
96    /// passes to `read_fn` slice with first `read_len` bytes of the block,
97    /// and sets the cursor position to `read_len`.
98    ///
99    /// # Panics
100    /// If `read_len` is bigger than block size.
101    #[inline(always)]
102    pub fn write_block(
103        &mut self,
104        read_len: usize,
105        gen_block: impl FnOnce(&mut Array<u8, BS>),
106        read_fn: impl FnOnce(&[u8]),
107    ) {
108        if read_len == 0 {
109            return;
110        }
111        assert!(read_len < BS::USIZE);
112
113        gen_block(&mut self.buffer);
114        read_fn(&self.buffer[..read_len]);
115
116        // We checked that `read_len` satisfies the `set_pos_unchecked` safety contract
117        unsafe { self.set_pos_unchecked(read_len) };
118    }
119
120    /// Reset buffer into exhausted state.
121    pub fn reset(&mut self) {
122        self.buffer[0] = BS::U8;
123    }
124
125    /// Write remaining data inside buffer into `buf`, fill remaining space
126    /// in `buf` with blocks generated by `gen_block`, and save leftover data
127    /// from the last generated block into the buffer for future use.
128    #[inline]
129    pub fn read(&mut self, buf: &mut [u8], mut gen_block: impl FnMut(&mut Array<u8, BS>)) {
130        let head_ks = self.read_cached(buf.len());
131        let (head, buf) = buf.split_at_mut(head_ks.len());
132        let (blocks, tail) = Array::slice_as_chunks_mut(buf);
133
134        head.copy_from_slice(head_ks);
135        for block in blocks {
136            gen_block(block);
137        }
138
139        self.write_block(tail.len(), gen_block, |tail_ks| {
140            tail.copy_from_slice(tail_ks);
141        });
142    }
143
144    /// Serialize buffer into a byte array.
145    #[inline]
146    pub fn serialize(&self) -> Array<u8, BS> {
147        let pos = self.get_pos();
148        let mut res = self.buffer.clone();
149        // zeroize "garbage" data
150        for b in &mut res[1..pos] {
151            *b = 0;
152        }
153        res
154    }
155
156    /// Deserialize buffer from a byte array.
157    ///
158    /// # Errors
159    /// - If the first byte is `0`.
160    /// - If the first byte is bigger than `BS`.
161    #[inline]
162    pub fn deserialize(buffer: &Array<u8, BS>) -> Result<Self, Error> {
163        let pos = usize::from(buffer[0]);
164        if pos == 0 || pos > BS::USIZE || buffer[1..pos].iter().any(|&b| b != 0) {
165            Err(Error)
166        } else {
167            let buffer = buffer.clone();
168            Ok(Self { buffer })
169        }
170    }
171}
172
173#[cfg(feature = "zeroize")]
174impl<BS: BlockSizes> Drop for ReadBuffer<BS> {
175    fn drop(&mut self) {
176        use zeroize::Zeroize;
177        self.buffer.zeroize();
178    }
179}
180
181#[cfg(feature = "zeroize")]
182impl<BS: BlockSizes> zeroize::ZeroizeOnDrop for ReadBuffer<BS> {}