qualia_core_db/q42/volume/
car.rs1use std::io;
9
10use super::cid::{sha256, CidSha256};
11use super::range::Q42ByteRange;
12
13fn invalid(message: impl Into<String>) -> io::Error {
14 io::Error::new(io::ErrorKind::InvalidData, message.into())
15}
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct VerifiedCarBlock {
20 pub cid: CidSha256,
21 pub data: Vec<u8>,
22}
23
24pub fn decode_and_verify_car(car: &[u8]) -> io::Result<Vec<VerifiedCarBlock>> {
28 let (header_len, header_size) = read_varint(car)?;
29 let header_end = header_size
30 .checked_add(header_len as usize)
31 .ok_or_else(|| invalid("CAR header overflow"))?;
32 if header_end > car.len() {
33 return Err(invalid("CAR header truncated"));
34 }
35 let mut offset = header_end;
36 let mut blocks = Vec::new();
37 while offset < car.len() {
38 let (block_len, used) = read_varint(&car[offset..])?;
39 offset += used;
40 let end = offset
41 .checked_add(block_len as usize)
42 .ok_or_else(|| invalid("CAR block overflow"))?;
43 if end > car.len() {
44 return Err(invalid("CAR block truncated"));
45 }
46 let (cid, cid_len) = parse_embedded_cid(&car[offset..end])?;
47 let data = &car[offset + cid_len..end];
48 cid.verify_block(data)?;
49 blocks.push(VerifiedCarBlock {
50 cid,
51 data: data.to_vec(),
52 });
53 offset = end;
54 }
55 Ok(blocks)
56}
57
58pub fn extract_entity_bytes(
60 blocks: &[VerifiedCarBlock],
61 range: Q42ByteRange,
62 out: &mut [u8],
63) -> io::Result<()> {
64 if out.len() != range.length {
65 return Err(invalid("entity-bytes output length mismatch"));
66 }
67 let mut entity = Vec::new();
68 for block in blocks {
69 if block.cid.codec == CidSha256::RAW {
70 entity.extend_from_slice(&block.data);
71 }
72 }
73 let end = range.end()? as usize;
74 if end > entity.len() {
75 return Err(invalid("entity-bytes range exceeds reconstructed file"));
76 }
77 out.copy_from_slice(&entity[range.offset as usize..end]);
78 Ok(())
79}
80
81pub fn inclusive_entity_bytes(start: u64, end_inclusive: u64) -> io::Result<Q42ByteRange> {
83 if end_inclusive < start {
84 return Err(invalid("entity-bytes end is before start"));
85 }
86 let length = (end_inclusive - start)
87 .checked_add(1)
88 .ok_or_else(|| invalid("entity-bytes length overflow"))?;
89 Ok(Q42ByteRange {
90 offset: start,
91 length: usize::try_from(length).map_err(|_| invalid("entity-bytes exceeds platform"))?,
92 })
93}
94
95pub fn encode_raw_car(blocks: &[&[u8]]) -> Vec<u8> {
97 let mut out = Vec::new();
98 let header = b"\xa2gversion\x01eroots\x80";
100 write_varint(&mut out, header.len() as u64);
101 out.extend_from_slice(header);
102 for block in blocks {
103 let cid = CidSha256::for_raw_block(block);
104 let mut cid_bytes = vec![0x01];
105 write_varint(&mut cid_bytes, cid.codec);
106 cid_bytes.push(0x12);
107 cid_bytes.push(32);
108 cid_bytes.extend_from_slice(&cid.digest);
109 write_varint(&mut out, (cid_bytes.len() + block.len()) as u64);
110 out.extend_from_slice(&cid_bytes);
111 out.extend_from_slice(block);
112 }
113 out
114}
115
116fn parse_embedded_cid(bytes: &[u8]) -> io::Result<(CidSha256, usize)> {
117 if bytes.len() >= 34 && bytes[0] == 0x12 && bytes[1] == 32 {
118 let mut digest = [0u8; 32];
119 digest.copy_from_slice(&bytes[2..34]);
120 return Ok((
121 CidSha256 {
122 version: 0,
123 codec: CidSha256::DAG_PB,
124 digest,
125 },
126 34,
127 ));
128 }
129 if bytes.first() != Some(&0x01) {
130 return Err(invalid("CAR block CID is neither CIDv0 nor CIDv1"));
131 }
132 let (codec, used) = read_varint(&bytes[1..])?;
133 let mh = 1 + used;
134 if bytes.len() < mh + 34 || bytes[mh] != 0x12 || bytes[mh + 1] != 32 {
135 return Err(invalid("CAR block is not sha2-256"));
136 }
137 let mut digest = [0u8; 32];
138 digest.copy_from_slice(&bytes[mh + 2..mh + 34]);
139 let _ = sha256;
141 Ok((
142 CidSha256 {
143 version: 1,
144 codec,
145 digest,
146 },
147 mh + 34,
148 ))
149}
150
151fn write_varint(out: &mut Vec<u8>, mut value: u64) {
152 loop {
153 let mut byte = (value & 0x7f) as u8;
154 value >>= 7;
155 if value != 0 {
156 byte |= 0x80;
157 }
158 out.push(byte);
159 if value == 0 {
160 return;
161 }
162 }
163}
164
165fn read_varint(bytes: &[u8]) -> io::Result<(u64, usize)> {
166 let mut value = 0u64;
167 let mut shift = 0u32;
168 for (index, byte) in bytes.iter().copied().enumerate() {
169 value |= u64::from(byte & 0x7f) << shift;
170 if byte & 0x80 == 0 {
171 return Ok((value, index + 1));
172 }
173 shift += 7;
174 if shift >= 64 {
175 return Err(invalid("CAR varint overflow"));
176 }
177 }
178 Err(invalid("truncated CAR varint"))
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn car_verifies_cids_and_extracts_entity_bytes() {
187 let first = b"Q42-HEADER-BYTES";
188 let second = b"Q42-BLOCK-PAYLOAD";
189 let car = encode_raw_car(&[first.as_slice(), second.as_slice()]);
190 let blocks = decode_and_verify_car(&car).unwrap();
191 assert_eq!(blocks.len(), 2);
192 let range = inclusive_entity_bytes(4, 19).unwrap();
193 let mut out = vec![0u8; range.length];
194 extract_entity_bytes(&blocks, range, &mut out).unwrap();
195 let mut expected = Vec::new();
196 expected.extend_from_slice(first);
197 expected.extend_from_slice(second);
198 assert_eq!(out, expected[4..20]);
199 }
200
201 #[test]
202 fn tampered_car_block_is_rejected() {
203 let mut car = encode_raw_car(&[b"intact".as_slice()]);
204 *car.last_mut().unwrap() ^= 0xff;
205 assert!(decode_and_verify_car(&car).is_err());
206 }
207}