use crate::bytes::{array32, le_u32, le_u64};
use crate::error::UrnaError;
use crate::layout::SECTION_BLOB_REFS;
pub const BLOB_REFS_PAYLOAD_VERSION: u32 = 1;
const MIN_ENTRY_SIZE: usize = 32 + 4 + 8 + 1;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlobRefRecord {
pub content_hash: [u8; 32],
pub original_uri: String,
pub byte_len: u64,
pub inlined: bool,
}
fn malformed(reason: impl Into<String>) -> UrnaError {
UrnaError::MalformedSectionPayload {
section_id: SECTION_BLOB_REFS,
reason: reason.into(),
}
}
pub fn encode_blob_refs(records: &[BlobRefRecord]) -> Result<Vec<u8>, UrnaError> {
let mut size = 12;
for r in records {
size += MIN_ENTRY_SIZE + r.original_uri.len();
}
let mut out = Vec::with_capacity(size);
out.extend_from_slice(&BLOB_REFS_PAYLOAD_VERSION.to_le_bytes());
out.extend_from_slice(&(records.len() as u64).to_le_bytes());
for r in records {
out.extend_from_slice(&r.content_hash);
out.extend_from_slice(&(r.original_uri.len() as u32).to_le_bytes());
out.extend_from_slice(r.original_uri.as_bytes());
out.extend_from_slice(&r.byte_len.to_le_bytes());
out.push(u8::from(r.inlined));
}
Ok(out)
}
pub fn decode_blob_refs(bytes: &[u8]) -> Result<Vec<BlobRefRecord>, UrnaError> {
let mut cur = Cursor::new(bytes);
let version = cur.u32()?;
if version != BLOB_REFS_PAYLOAD_VERSION {
return Err(UrnaError::UnsupportedSectionVersion {
section_id: SECTION_BLOB_REFS,
version,
});
}
let n = cur.u64()? as usize;
if n > cur.remaining() / MIN_ENTRY_SIZE {
return Err(malformed("blob_refs: entry count exceeds payload"));
}
let mut records = Vec::with_capacity(n);
for _ in 0..n {
let content_hash: [u8; 32] = array32(cur.take(32)?)?;
let uri_len = cur.u32()? as usize;
if uri_len > cur.remaining() {
return Err(malformed("blob_refs: uri length exceeds payload"));
}
let uri_bytes = cur.take(uri_len)?;
let original_uri = std::str::from_utf8(uri_bytes)
.map_err(|_| malformed("blob_refs: uri is not utf-8"))?
.to_string();
let byte_len = cur.u64()?;
let inlined = match cur.u8()? {
0 => false,
1 => true,
other => return Err(malformed(format!("blob_refs: bad inlined flag {}", other))),
};
records.push(BlobRefRecord {
content_hash,
original_uri,
byte_len,
inlined,
});
}
if cur.pos != bytes.len() {
return Err(malformed("trailing bytes after records"));
}
Ok(records)
}
struct Cursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn remaining(&self) -> usize {
self.buf.len() - self.pos
}
fn take(&mut self, n: usize) -> Result<&'a [u8], UrnaError> {
if n > self.remaining() {
return Err(malformed("unexpected EOF"));
}
let s = &self.buf[self.pos..self.pos + n];
self.pos += n;
Ok(s)
}
fn u8(&mut self) -> Result<u8, UrnaError> {
Ok(self.take(1)?[0])
}
fn u32(&mut self) -> Result<u32, UrnaError> {
le_u32(self.take(4)?)
}
fn u64(&mut self) -> Result<u64, UrnaError> {
le_u64(self.take(8)?)
}
}