use urna_format::UrnaError;
use urna_format::layout::{
SECTION_BLOB_DATA, SECTION_BLOB_REFS, SECTION_BLOB_SPAN_OVERLAY, SECTION_ENCODING_RAW,
};
use urna_format::reader::UrnaView;
use urna_format::sections::{
BLOB_REF_NONE, BlobRefRecord, OriginalSpan, decode_blob_data_table, decode_blob_refs,
decode_blob_span_overlay,
};
use crate::error::RuntimeError;
#[derive(Clone, Debug)]
pub struct OpenBlobData {
pub entries: Vec<(u64, u64)>,
pub abs_data_start: usize,
}
pub(crate) fn open_blob_data(
view: &UrnaView,
n_refs: usize,
) -> Result<Option<OpenBlobData>, RuntimeError> {
let Some(entry) = view
.section_table
.iter()
.find(|e| e.section_id == SECTION_BLOB_DATA)
else {
return Ok(None);
};
if entry.encoding != SECTION_ENCODING_RAW {
return Err(RuntimeError::Format(UrnaError::MalformedSectionPayload {
section_id: SECTION_BLOB_DATA,
reason: format!("blob_data must be raw, found encoding {}", entry.encoding),
}));
}
let payload = view.get_section_data(SECTION_BLOB_DATA)?;
let table = decode_blob_data_table(payload)?;
if table.entries.len() != n_refs {
return Err(RuntimeError::Format(UrnaError::MalformedSectionPayload {
section_id: SECTION_BLOB_DATA,
reason: format!(
"blob_data has {} entries but blob_refs has {} records",
table.entries.len(),
n_refs
),
}));
}
Ok(Some(OpenBlobData {
entries: table.entries,
abs_data_start: entry.offset as usize + table.data_start,
}))
}
impl crate::mmap_file::MmapUrnaFile {
pub fn has_blob_data(&self) -> bool {
self.blob_data.is_some()
}
pub fn blob_bytes(&self, index: usize) -> Result<&[u8], RuntimeError> {
let data = self
.blob_data
.as_ref()
.ok_or(RuntimeError::BlobNotInlined { index })?;
let &(offset, len) = data
.entries
.get(index)
.ok_or(RuntimeError::BlobNotInlined { index })?;
let inlined = self
.blob_refs
.as_ref()
.and_then(|refs| refs.get(index))
.is_some_and(|r| r.inlined);
if !inlined {
return Err(RuntimeError::BlobNotInlined { index });
}
let start = data.abs_data_start + offset as usize;
let end = start + len as usize;
self._mmap.get(start..end).ok_or_else(|| {
RuntimeError::Format(UrnaError::MalformedSectionPayload {
section_id: SECTION_BLOB_DATA,
reason: format!("blob {} spans past the file", index),
})
})
}
}
pub(crate) fn open_blob_sections(
view: &UrnaView,
spans: &mut [OriginalSpan],
) -> Result<Option<Vec<BlobRefRecord>>, RuntimeError> {
let blobs_present = view
.manifest
.capabilities_ext
.as_ref()
.and_then(|e| e.blobs_present)
.unwrap_or(false);
if !blobs_present {
return Ok(None);
}
let has = |id: u32| view.section_table.iter().any(|e| e.section_id == id);
let blob_refs = if has(SECTION_BLOB_REFS) {
let bytes = view.decoded_section(SECTION_BLOB_REFS)?;
Some(decode_blob_refs(&bytes)?)
} else {
None
};
if has(SECTION_BLOB_SPAN_OVERLAY) {
let bytes = view.decoded_section(SECTION_BLOB_SPAN_OVERLAY)?;
let overlay = decode_blob_span_overlay(&bytes)?;
for (i, entry) in overlay.iter().enumerate().take(spans.len()) {
if entry.blob_ref_index == BLOB_REF_NONE {
continue;
}
let uri = blob_refs
.as_ref()
.and_then(|refs| refs.get(entry.blob_ref_index as usize))
.map(|r| r.original_uri.clone())
.ok_or_else(|| {
RuntimeError::Format(UrnaError::MalformedSectionPayload {
section_id: SECTION_BLOB_SPAN_OVERLAY,
reason: format!(
"overlay entry {} references missing blob_ref {}",
i, entry.blob_ref_index
),
})
})?;
spans[i] = OriginalSpan {
source_uri: uri,
byte_start: entry.byte_start,
byte_end: entry.byte_end,
};
}
}
Ok(blob_refs)
}
pub fn apply_blob_span_overlay(
view: &UrnaView,
spans: &mut [OriginalSpan],
) -> Result<(), RuntimeError> {
open_blob_sections(view, spans).map(|_| ())
}