urna_format/sections/blob/
refs.rs1use crate::bytes::{array32, le_u32, le_u64};
19use crate::error::UrnaError;
20use crate::layout::SECTION_BLOB_REFS;
21
22pub const BLOB_REFS_PAYLOAD_VERSION: u32 = 1;
23
24const MIN_ENTRY_SIZE: usize = 32 + 4 + 8 + 1;
28
29#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct BlobRefRecord {
35 pub content_hash: [u8; 32],
36 pub original_uri: String,
37 pub byte_len: u64,
38 pub inlined: bool,
39}
40
41fn malformed(reason: impl Into<String>) -> UrnaError {
42 UrnaError::MalformedSectionPayload {
43 section_id: SECTION_BLOB_REFS,
44 reason: reason.into(),
45 }
46}
47
48pub fn encode_blob_refs(records: &[BlobRefRecord]) -> Result<Vec<u8>, UrnaError> {
51 let mut size = 12;
52 for r in records {
53 size += MIN_ENTRY_SIZE + r.original_uri.len();
54 }
55 let mut out = Vec::with_capacity(size);
56 out.extend_from_slice(&BLOB_REFS_PAYLOAD_VERSION.to_le_bytes());
57 out.extend_from_slice(&(records.len() as u64).to_le_bytes());
58 for r in records {
59 out.extend_from_slice(&r.content_hash);
60 out.extend_from_slice(&(r.original_uri.len() as u32).to_le_bytes());
61 out.extend_from_slice(r.original_uri.as_bytes());
62 out.extend_from_slice(&r.byte_len.to_le_bytes());
63 out.push(u8::from(r.inlined));
64 }
65 Ok(out)
66}
67
68pub fn decode_blob_refs(bytes: &[u8]) -> Result<Vec<BlobRefRecord>, UrnaError> {
71 let mut cur = Cursor::new(bytes);
72 let version = cur.u32()?;
73 if version != BLOB_REFS_PAYLOAD_VERSION {
74 return Err(UrnaError::UnsupportedSectionVersion {
75 section_id: SECTION_BLOB_REFS,
76 version,
77 });
78 }
79 let n = cur.u64()? as usize;
80 if n > cur.remaining() / MIN_ENTRY_SIZE {
83 return Err(malformed("blob_refs: entry count exceeds payload"));
84 }
85 let mut records = Vec::with_capacity(n);
86 for _ in 0..n {
87 let content_hash: [u8; 32] = array32(cur.take(32)?)?;
88 let uri_len = cur.u32()? as usize;
89 if uri_len > cur.remaining() {
90 return Err(malformed("blob_refs: uri length exceeds payload"));
91 }
92 let uri_bytes = cur.take(uri_len)?;
93 let original_uri = std::str::from_utf8(uri_bytes)
94 .map_err(|_| malformed("blob_refs: uri is not utf-8"))?
95 .to_string();
96 let byte_len = cur.u64()?;
97 let inlined = match cur.u8()? {
98 0 => false,
99 1 => true,
100 other => return Err(malformed(format!("blob_refs: bad inlined flag {}", other))),
101 };
102 records.push(BlobRefRecord {
103 content_hash,
104 original_uri,
105 byte_len,
106 inlined,
107 });
108 }
109 if cur.pos != bytes.len() {
110 return Err(malformed("trailing bytes after records"));
111 }
112 Ok(records)
113}
114
115struct Cursor<'a> {
118 buf: &'a [u8],
119 pos: usize,
120}
121
122impl<'a> Cursor<'a> {
123 fn new(buf: &'a [u8]) -> Self {
124 Self { buf, pos: 0 }
125 }
126 fn remaining(&self) -> usize {
127 self.buf.len() - self.pos
128 }
129 fn take(&mut self, n: usize) -> Result<&'a [u8], UrnaError> {
130 if n > self.remaining() {
131 return Err(malformed("unexpected EOF"));
132 }
133 let s = &self.buf[self.pos..self.pos + n];
134 self.pos += n;
135 Ok(s)
136 }
137 fn u8(&mut self) -> Result<u8, UrnaError> {
138 Ok(self.take(1)?[0])
139 }
140 fn u32(&mut self) -> Result<u32, UrnaError> {
141 le_u32(self.take(4)?)
142 }
143 fn u64(&mut self) -> Result<u64, UrnaError> {
144 le_u64(self.take(8)?)
145 }
146}