Skip to main content

urna_format/reader/
mod.rs

1//! Zero-copy view over a `.urna` byte slice.
2//!
3//! The reader does no I/O - callers pass an `&[u8]` (e.g. backed by an
4//! `mmap`). Parsing validates magic, header checksum, file_size, all
5//! section checksums, footer hash, manifest schema, and the presence of
6//! every required section.
7//!
8//! Section payloads come in three encodings (`SECTION_ENCODING_*`):
9//! - `raw`: the section bytes ARE the canonical payload.
10//! - `zstd`: stored compressed; the reader decompresses on demand and
11//!   returns an owned `Cow::Owned` buffer.
12//! - `float16` / `int8`: only valid for the embeddings section; the
13//!   physical bytes are also the canonical bytes (the runtime
14//!   dispatches on `manifest.dtype`).
15//!
16//! Section checksums hash the **physical** bytes as stored.
17//! `content_hash` hashes the **decoded** bytes so a zstd-compressed
18//! corpus and its raw equivalent share the same content_hash (and
19//! therefore the same citation URIs).
20
21mod decode;
22mod parse;
23mod validate;
24pub use validate::validate_slab_values;
25
26use crate::error::UrnaError;
27use crate::layout::{SectionEntry, UrnaFooter, UrnaHeader};
28use crate::manifest::Manifest;
29
30pub struct UrnaView<'a> {
31    pub(super) data: &'a [u8],
32    pub header: UrnaHeader,
33    pub section_table: Vec<SectionEntry>,
34    pub manifest: Manifest,
35    pub footer: UrnaFooter,
36}
37
38impl<'a> UrnaView<'a> {
39    pub fn len(&self) -> usize {
40        self.data.len()
41    }
42
43    pub fn is_empty(&self) -> bool {
44        self.data.is_empty()
45    }
46
47    pub fn raw_bytes(&self) -> &[u8] {
48        self.data
49    }
50
51    /// Look up the section table entry for `section_id`.
52    pub fn entry(&self, section_id: u32) -> crate::Result<&SectionEntry> {
53        self.section_table
54            .iter()
55            .find(|e| e.section_id == section_id)
56            .ok_or(UrnaError::SectionNotFound(section_id))
57    }
58
59    /// Physical (on-disk, mmap-backed) bytes of a section's payload.
60    /// Use `decoded_section` if you want the logical bytes (e.g. zstd
61    /// decompressed) the chunk decoders consume.
62    pub fn get_section_data(&self, section_id: u32) -> crate::Result<&'a [u8]> {
63        let entry = self.entry(section_id)?;
64        let start = entry.offset as usize;
65        let end = start + entry.size as usize;
66        Ok(&self.data[start..end])
67    }
68}