// SPDX-License-Identifier: AGPL-3.0-only //! A bounds-checked, forward-only cursor over untrusted save-format bytes. use super::error::SaveError; /// A forward-only reader over a byte slice that bounds-checks every read. pub(crate) struct Reader<'a> { /// The full buffer being read. bytes: &'a [u8], /// The offset of the next unread byte. offset: usize, } impl<'a> Reader<'a> { pub(crate) fn new(bytes: &'a [u8]) -> Self { Self { bytes, offset: 0 } } /// Returns the next `n` bytes and advances the cursor. /// /// # Errors /// /// Returns [`SaveError::Truncated`] if fewer than `n` bytes remain, or if the offset addition overflows. pub(crate) fn take(&mut self, n: usize) -> Result<&'a [u8], SaveError> { let end = self.offset.checked_add(n).ok_or(SaveError::Truncated { offset: self.offset, needed: n, available: self.bytes.len().saturating_sub(self.offset), })?; let slice = self .bytes .get(self.offset..end) .ok_or(SaveError::Truncated { offset: self.offset, needed: n, available: self.bytes.len().saturating_sub(self.offset), })?; self.offset = end; Ok(slice) } /// Returns the next `N` bytes as a fixed-size array and advances the cursor. /// /// # Errors /// /// Returns [`SaveError::Truncated`] if fewer than `N` bytes remain. pub(crate) fn take_array(&mut self) -> Result<[u8; N], SaveError> { let mut array = [0u8; N]; array.copy_from_slice(self.take(N)?); Ok(array) } }