Skip to main content

musefs_format/ogg/
art_source.rs

1use crate::error::{FormatError, Result};
2use std::collections::HashMap;
3
4/// A source of raw art bytes used during synthesis to compute page CRCs. The
5/// production implementation (in `musefs-core`) streams from the SQLite blob
6/// store; tests and fuzzing use [`MapArtSource`]. `offset` and `buf.len()` are in
7/// raw-image coordinates; a read past the stored image is an error (mirrors the
8/// short-read semantics of the DB blob path), surfaced as `FormatError::ArtRead`.
9pub trait ArtSource {
10    fn read_window(&self, art_id: i64, offset: u64, buf: &mut [u8]) -> Result<()>;
11}
12
13/// In-memory `ArtSource` over an `art_id -> image bytes` map. For tests/fuzz.
14#[derive(Default)]
15pub struct MapArtSource {
16    images: HashMap<i64, Vec<u8>>,
17}
18
19impl MapArtSource {
20    pub fn new(images: impl IntoIterator<Item = (i64, Vec<u8>)>) -> Self {
21        Self {
22            images: images.into_iter().collect(),
23        }
24    }
25}
26
27impl ArtSource for MapArtSource {
28    fn read_window(&self, art_id: i64, offset: u64, buf: &mut [u8]) -> Result<()> {
29        let img = self
30            .images
31            .get(&art_id)
32            .ok_or(FormatError::ArtRead { art_id })?;
33        // Guard the u64 -> usize narrowing: an offset past `usize::MAX` (only
34        // reachable on a 32-bit target) must fail closed, not truncate and read
35        // the wrong bytes.
36        let start = usize::try_from(offset)
37            .ok()
38            .filter(|&s| s <= img.len())
39            .ok_or(FormatError::ArtRead { art_id })?;
40        let end = start
41            .checked_add(buf.len())
42            .filter(|&e| e <= img.len())
43            .ok_or(FormatError::ArtRead { art_id })?;
44        buf.copy_from_slice(&img[start..end]);
45        Ok(())
46    }
47}