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