urna_format/reader/
parse.rs1use super::UrnaView;
6use crate::error::UrnaError;
7use crate::layout::{
8 LEGACY_MAGIC, SECTION_ALIGNMENT, SectionEntry, URNA_FOOTER_SIZE, URNA_HEADER_SIZE, URNA_MAGIC,
9 URNA_SECTION_ENTRY_SIZE, URNA_VERSION_MAJOR, URNA_VERSION_MINOR, UrnaFooter, UrnaHeader,
10};
11use crate::manifest::Manifest;
12
13impl<'a> UrnaView<'a> {
14 pub fn from_bytes(data: &'a [u8]) -> crate::Result<Self> {
15 if data.len() < URNA_HEADER_SIZE + URNA_FOOTER_SIZE {
16 return Err(UrnaError::FileTruncated);
17 }
18
19 let mut header = UrnaHeader::default();
21 header
22 .as_bytes_mut()
23 .copy_from_slice(&data[..URNA_HEADER_SIZE]);
24
25 if &header.magic != URNA_MAGIC && &header.magic != LEGACY_MAGIC {
26 return Err(UrnaError::MagicMismatch {
27 expected: *URNA_MAGIC,
28 got: header.magic,
29 });
30 }
31 if header.version_major != URNA_VERSION_MAJOR || header.version_minor > URNA_VERSION_MINOR {
32 return Err(UrnaError::UnsupportedVersion(
33 header.version_major,
34 header.version_minor,
35 ));
36 }
37 header.validate_checksum()?;
38
39 if header.file_size as usize != data.len() {
40 return Err(UrnaError::FileSizeMismatch {
41 expected: header.file_size,
42 got: data.len() as u64,
43 });
44 }
45
46 let section_table_offset = header.section_table_offset as usize;
48 let section_table_count = header.section_table_count as usize;
49 let section_table_end = section_table_offset
50 .checked_add(
51 section_table_count
52 .checked_mul(URNA_SECTION_ENTRY_SIZE)
53 .ok_or(UrnaError::SectionOffsetOutOfBounds {
54 section_id: 0,
55 offset: 0,
56 })?,
57 )
58 .ok_or(UrnaError::SectionOffsetOutOfBounds {
59 section_id: 0,
60 offset: 0,
61 })?;
62 if section_table_end > data.len().saturating_sub(URNA_FOOTER_SIZE) {
63 return Err(UrnaError::FileTruncated);
64 }
65
66 let mut section_table = Vec::with_capacity(section_table_count);
67 for i in 0..section_table_count {
68 let off = section_table_offset + i * URNA_SECTION_ENTRY_SIZE;
69 let mut entry = SectionEntry::new(0, 0, 0);
70 entry
71 .as_bytes_mut()
72 .copy_from_slice(&data[off..off + URNA_SECTION_ENTRY_SIZE]);
73 section_table.push(entry);
74 }
75
76 let body_end = data.len() - URNA_FOOTER_SIZE;
78 for entry in §ion_table {
79 super::validate::validate_encoding_for_section(entry.section_id, entry.encoding)?;
80 if entry.offset % SECTION_ALIGNMENT != 0 {
81 return Err(UrnaError::SectionMisaligned {
82 section_id: entry.section_id,
83 offset: entry.offset,
84 alignment: SECTION_ALIGNMENT,
85 });
86 }
87 let start = entry.offset as usize;
88 let end = start.checked_add(entry.size as usize).ok_or(
89 UrnaError::SectionOffsetOutOfBounds {
90 section_id: entry.section_id,
91 offset: entry.offset,
92 },
93 )?;
94 if end > body_end {
95 return Err(UrnaError::SectionOffsetOutOfBounds {
96 section_id: entry.section_id,
97 offset: entry.offset,
98 });
99 }
100 entry.validate_checksum(&data[start..end])?;
101 }
102
103 let manifest_offset = header.manifest_offset as usize;
105 let manifest_size = header.manifest_size as usize;
106 if manifest_offset
107 .checked_add(manifest_size)
108 .map(|e| e > body_end)
109 .unwrap_or(true)
110 {
111 return Err(UrnaError::FileTruncated);
112 }
113 let manifest_data = &data[manifest_offset..manifest_offset + manifest_size];
114 let manifest: Manifest = serde_json::from_slice(manifest_data)?;
115 manifest.validate()?;
116
117 let footer = UrnaFooter::from_bytes(&data[body_end..])?;
119 let computed = UrnaFooter::compute_file_hash(&data[..body_end]);
120 if computed != footer.file_hash {
121 return Err(UrnaError::FooterHashMismatch);
122 }
123
124 if manifest.embedding_dim != header.embedding_dim {
126 return Err(UrnaError::ManifestInvalid(
127 "manifest embedding_dim disagrees with header".into(),
128 ));
129 }
130 if manifest.n_chunks != header.n_chunks {
131 return Err(UrnaError::ManifestInvalid(
132 "manifest n_chunks disagrees with header".into(),
133 ));
134 }
135
136 let view = Self {
137 data,
138 header,
139 section_table,
140 manifest,
141 footer,
142 };
143
144 view.check_required_sections()?;
146 view.validate_embeddings_layout()?;
147 view.validate_space_bands()?;
148 view.validate_search_contract()?;
149
150 Ok(view)
151 }
152}