1use urna_format::UrnaError;
8use urna_format::layout::{
9 SECTION_BLOB_DATA, SECTION_BLOB_REFS, SECTION_BLOB_SPAN_OVERLAY, SECTION_ENCODING_RAW,
10};
11use urna_format::reader::UrnaView;
12use urna_format::sections::{
13 BLOB_REF_NONE, BlobRefRecord, OriginalSpan, decode_blob_data_table, decode_blob_refs,
14 decode_blob_span_overlay,
15};
16
17use crate::error::RuntimeError;
18
19#[derive(Clone, Debug)]
23pub struct OpenBlobData {
24 pub entries: Vec<(u64, u64)>,
25 pub abs_data_start: usize,
26}
27
28pub(crate) fn open_blob_data(
33 view: &UrnaView,
34 n_refs: usize,
35) -> Result<Option<OpenBlobData>, RuntimeError> {
36 let Some(entry) = view
37 .section_table
38 .iter()
39 .find(|e| e.section_id == SECTION_BLOB_DATA)
40 else {
41 return Ok(None);
42 };
43 if entry.encoding != SECTION_ENCODING_RAW {
44 return Err(RuntimeError::Format(UrnaError::MalformedSectionPayload {
45 section_id: SECTION_BLOB_DATA,
46 reason: format!("blob_data must be raw, found encoding {}", entry.encoding),
47 }));
48 }
49 let payload = view.get_section_data(SECTION_BLOB_DATA)?;
50 let table = decode_blob_data_table(payload)?;
51 if table.entries.len() != n_refs {
52 return Err(RuntimeError::Format(UrnaError::MalformedSectionPayload {
53 section_id: SECTION_BLOB_DATA,
54 reason: format!(
55 "blob_data has {} entries but blob_refs has {} records",
56 table.entries.len(),
57 n_refs
58 ),
59 }));
60 }
61 Ok(Some(OpenBlobData {
62 entries: table.entries,
63 abs_data_start: entry.offset as usize + table.data_start,
64 }))
65}
66
67impl crate::mmap_file::MmapUrnaFile {
68 pub fn has_blob_data(&self) -> bool {
70 self.blob_data.is_some()
71 }
72
73 pub fn blob_bytes(&self, index: usize) -> Result<&[u8], RuntimeError> {
77 let data = self
78 .blob_data
79 .as_ref()
80 .ok_or(RuntimeError::BlobNotInlined { index })?;
81 let &(offset, len) = data
82 .entries
83 .get(index)
84 .ok_or(RuntimeError::BlobNotInlined { index })?;
85 let inlined = self
86 .blob_refs
87 .as_ref()
88 .and_then(|refs| refs.get(index))
89 .is_some_and(|r| r.inlined);
90 if !inlined {
91 return Err(RuntimeError::BlobNotInlined { index });
92 }
93 let start = data.abs_data_start + offset as usize;
94 let end = start + len as usize;
95 self._mmap.get(start..end).ok_or_else(|| {
98 RuntimeError::Format(UrnaError::MalformedSectionPayload {
99 section_id: SECTION_BLOB_DATA,
100 reason: format!("blob {} spans past the file", index),
101 })
102 })
103 }
104}
105
106pub(crate) fn open_blob_sections(
115 view: &UrnaView,
116 spans: &mut [OriginalSpan],
117) -> Result<Option<Vec<BlobRefRecord>>, RuntimeError> {
118 let blobs_present = view
119 .manifest
120 .capabilities_ext
121 .as_ref()
122 .and_then(|e| e.blobs_present)
123 .unwrap_or(false);
124 if !blobs_present {
125 return Ok(None);
126 }
127 let has = |id: u32| view.section_table.iter().any(|e| e.section_id == id);
128 let blob_refs = if has(SECTION_BLOB_REFS) {
129 let bytes = view.decoded_section(SECTION_BLOB_REFS)?;
130 Some(decode_blob_refs(&bytes)?)
131 } else {
132 None
133 };
134 if has(SECTION_BLOB_SPAN_OVERLAY) {
135 let bytes = view.decoded_section(SECTION_BLOB_SPAN_OVERLAY)?;
136 let overlay = decode_blob_span_overlay(&bytes)?;
137 for (i, entry) in overlay.iter().enumerate().take(spans.len()) {
138 if entry.blob_ref_index == BLOB_REF_NONE {
139 continue;
140 }
141 let uri = blob_refs
142 .as_ref()
143 .and_then(|refs| refs.get(entry.blob_ref_index as usize))
144 .map(|r| r.original_uri.clone())
145 .ok_or_else(|| {
146 RuntimeError::Format(UrnaError::MalformedSectionPayload {
147 section_id: SECTION_BLOB_SPAN_OVERLAY,
148 reason: format!(
149 "overlay entry {} references missing blob_ref {}",
150 i, entry.blob_ref_index
151 ),
152 })
153 })?;
154 spans[i] = OriginalSpan {
155 source_uri: uri,
156 byte_start: entry.byte_start,
157 byte_end: entry.byte_end,
158 };
159 }
160 }
161 Ok(blob_refs)
162}