1use std::io::{self, Read, Write};
10
11pub(crate) const MAX_CAR_HEADER_BYTES: usize = 8 * 1024;
14
15pub(crate) fn write_uvarint<W: Write>(w: &mut W, mut n: u64) -> io::Result<()> {
17 while n >= 0x80 {
18 w.write_all(&[(n as u8) | 0x80])?;
19 n >>= 7;
20 }
21 w.write_all(&[n as u8])
22}
23
24pub(crate) fn build_dagcbor_header(root_cid_bytes: &[u8]) -> Vec<u8> {
34 let mut out = Vec::with_capacity(48 + root_cid_bytes.len());
35 out.push(0xA2); out.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
38 out.push(0x81); out.push(0xD8); out.push(0x2A); let bytestring_len = 1 + root_cid_bytes.len();
44 write_cbor_bytestring_header(&mut out, bytestring_len);
45 out.push(0x00);
46 out.extend_from_slice(root_cid_bytes);
47 out.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
49 out.push(0x01);
51 out
52}
53
54fn write_cbor_bytestring_header(out: &mut Vec<u8>, len: usize) {
55 if len <= 23 {
57 out.push(0x40 | (len as u8));
58 } else if len <= u8::MAX as usize {
59 out.push(0x58);
60 out.push(len as u8);
61 } else if len <= u16::MAX as usize {
62 out.push(0x59);
63 out.extend_from_slice(&(len as u16).to_be_bytes());
64 } else if len <= u32::MAX as usize {
65 out.push(0x5A);
66 out.extend_from_slice(&(len as u32).to_be_bytes());
67 } else {
68 out.push(0x5B);
69 out.extend_from_slice(&(len as u64).to_be_bytes());
70 }
71}
72
73pub fn write_carv1_header<W: Write>(w: &mut W, root_cid_bytes: &[u8]) -> io::Result<()> {
76 let header = build_dagcbor_header(root_cid_bytes);
77 write_uvarint(w, header.len() as u64)?;
78 w.write_all(&header)
79}
80
81pub fn write_block_frame<W: Write>(
83 w: &mut W,
84 cid_bytes: &[u8],
85 block_bytes: &[u8],
86) -> io::Result<()> {
87 let total = cid_bytes.len() + block_bytes.len();
88 write_uvarint(w, total as u64)?;
89 w.write_all(cid_bytes)?;
90 w.write_all(block_bytes)
91}
92
93#[derive(Debug, thiserror::Error)]
95pub enum InvalidCarFile {
96 #[error("malformed varint")]
97 MalformedVarint,
98 #[error("declared header size exceeds maximum ({MAX_CAR_HEADER_BYTES} bytes)")]
99 HeaderTooLarge,
100 #[error("truncated CAR header")]
101 TruncatedHeader,
102 #[error("malformed DAG-CBOR header")]
103 MalformedHeader,
104 #[error("unsupported CAR version (got {got}, expected 1)")]
105 UnsupportedVersion { got: u64 },
106 #[error("expected exactly 1 root, got {got}")]
107 BadRootCount { got: usize },
108 #[error("malformed root CID")]
109 MalformedRootCid,
110 #[error("I/O error reading CAR file: {0}")]
111 Io(#[from] std::io::Error),
112}
113
114pub fn read_carv1_root(path: &std::path::Path) -> Result<String, InvalidCarFile> {
118 let mut f = std::fs::File::open(path)?;
119 read_carv1_root_from(&mut f)
120}
121
122pub(crate) fn read_carv1_root_from<R: Read>(r: &mut R) -> Result<String, InvalidCarFile> {
125 let header_len = read_uvarint_checked(r)?;
126 if header_len > MAX_CAR_HEADER_BYTES as u64 {
127 return Err(InvalidCarFile::HeaderTooLarge);
128 }
129 let mut header = vec![0u8; header_len as usize];
130 r.read_exact(&mut header)
131 .map_err(|_| InvalidCarFile::TruncatedHeader)?;
132 parse_dagcbor_header(&header)
133}
134
135fn read_uvarint_checked<R: Read>(r: &mut R) -> Result<u64, InvalidCarFile> {
136 let mut result: u64 = 0;
137 let mut shift = 0u32;
138 for _ in 0..10 {
139 let mut buf = [0u8; 1];
140 if r.read_exact(&mut buf).is_err() {
141 return Err(InvalidCarFile::MalformedVarint);
142 }
143 let byte = buf[0];
144 let payload = (byte & 0x7F) as u64;
145 if shift == 63 && (payload > 1 || byte & 0x80 != 0) {
148 return Err(InvalidCarFile::MalformedVarint);
149 }
150 result |= payload << shift;
151 if byte & 0x80 == 0 {
152 return Ok(result);
153 }
154 shift += 7;
155 }
156 Err(InvalidCarFile::MalformedVarint)
157}
158
159fn parse_dagcbor_header(bytes: &[u8]) -> Result<String, InvalidCarFile> {
162 let mut cur = HeaderCursor::new(bytes);
163 let head = cur.next_byte()?;
164 if head != 0xA2 {
165 return Err(InvalidCarFile::MalformedHeader);
166 }
167 let mut roots_cid: Option<Vec<u8>> = None;
168 let mut roots_count: Option<usize> = None;
169 let mut version: Option<u64> = None;
170 for _ in 0..2 {
171 let key = cur.read_text()?;
172 match key.as_str() {
173 "roots" => {
174 let n = cur.read_array_header()?;
175 roots_count = Some(n);
176 if n != 1 {
177 return Err(InvalidCarFile::BadRootCount { got: n });
179 }
180 let tag1 = cur.next_byte()?;
181 let tag2 = cur.next_byte()?;
182 if tag1 != 0xD8 || tag2 != 0x2A {
183 return Err(InvalidCarFile::MalformedRootCid);
184 }
185 let bs = cur.read_bytestring()?;
186 if bs.is_empty() || bs[0] != 0x00 {
187 return Err(InvalidCarFile::MalformedRootCid);
188 }
189 roots_cid = Some(bs[1..].to_vec());
190 }
191 "version" => {
192 version = Some(cur.read_uint()?);
193 }
194 _ => return Err(InvalidCarFile::MalformedHeader),
195 }
196 }
197 if roots_count.is_none() {
198 return Err(InvalidCarFile::MalformedHeader);
199 }
200 let cid_bytes = roots_cid.ok_or(InvalidCarFile::MalformedRootCid)?;
201 let version = version.ok_or(InvalidCarFile::MalformedHeader)?;
202 if version != 1 {
203 return Err(InvalidCarFile::UnsupportedVersion { got: version });
204 }
205 let cid = ::cid::Cid::try_from(&cid_bytes[..]).map_err(|_| InvalidCarFile::MalformedRootCid)?;
206 Ok(cid.to_string())
209}
210
211struct HeaderCursor<'a> {
212 bytes: &'a [u8],
213 pos: usize,
214}
215
216impl<'a> HeaderCursor<'a> {
217 fn new(bytes: &'a [u8]) -> Self {
218 Self { bytes, pos: 0 }
219 }
220 fn next_byte(&mut self) -> Result<u8, InvalidCarFile> {
221 let b = *self
222 .bytes
223 .get(self.pos)
224 .ok_or(InvalidCarFile::MalformedHeader)?;
225 self.pos += 1;
226 Ok(b)
227 }
228 fn take(&mut self, n: usize) -> Result<&'a [u8], InvalidCarFile> {
229 if self.pos + n > self.bytes.len() {
230 return Err(InvalidCarFile::MalformedHeader);
231 }
232 let out = &self.bytes[self.pos..self.pos + n];
233 self.pos += n;
234 Ok(out)
235 }
236 fn read_text(&mut self) -> Result<String, InvalidCarFile> {
237 let head = self.next_byte()?;
238 let len = match head {
239 0x60..=0x77 => (head - 0x60) as usize,
240 0x78 => self.next_byte()? as usize,
241 0x79 => {
242 let hi = self.next_byte()? as u16;
243 let lo = self.next_byte()? as u16;
244 ((hi << 8) | lo) as usize
245 }
246 _ => return Err(InvalidCarFile::MalformedHeader),
247 };
248 let bytes = self.take(len)?;
249 std::str::from_utf8(bytes)
250 .map(|s| s.to_string())
251 .map_err(|_| InvalidCarFile::MalformedHeader)
252 }
253 fn read_array_header(&mut self) -> Result<usize, InvalidCarFile> {
254 let head = self.next_byte()?;
255 match head {
256 0x80..=0x97 => Ok((head - 0x80) as usize),
257 0x98 => Ok(self.next_byte()? as usize),
258 0x99 => {
259 let hi = self.next_byte()? as u16;
260 let lo = self.next_byte()? as u16;
261 Ok(((hi << 8) | lo) as usize)
262 }
263 _ => Err(InvalidCarFile::MalformedHeader),
264 }
265 }
266 fn read_bytestring(&mut self) -> Result<&'a [u8], InvalidCarFile> {
267 let head = self.next_byte()?;
268 let len = match head {
269 0x40..=0x57 => (head - 0x40) as usize,
270 0x58 => self.next_byte()? as usize,
271 0x59 => {
272 let hi = self.next_byte()? as u16;
273 let lo = self.next_byte()? as u16;
274 ((hi << 8) | lo) as usize
275 }
276 _ => return Err(InvalidCarFile::MalformedHeader),
277 };
278 self.take(len)
279 }
280 fn read_uint(&mut self) -> Result<u64, InvalidCarFile> {
281 let head = self.next_byte()?;
282 match head {
283 0x00..=0x17 => Ok(head as u64),
284 0x18 => Ok(self.next_byte()? as u64),
285 0x19 => Ok(u16::from_be_bytes(self.take(2)?.try_into().unwrap()) as u64),
286 0x1A => Ok(u32::from_be_bytes(self.take(4)?.try_into().unwrap()) as u64),
287 0x1B => Ok(u64::from_be_bytes(self.take(8)?.try_into().unwrap())),
288 _ => Err(InvalidCarFile::MalformedHeader),
289 }
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use cid::Cid as RawCid;
297 use multihash::Multihash;
298 use std::io::Read;
299
300 fn read_uvarint(r: &mut impl Read) -> io::Result<(u64, usize)> {
304 let mut result: u64 = 0;
305 let mut shift = 0u32;
306 let mut bytes_read = 0;
307 loop {
308 let mut buf = [0u8; 1];
309 r.read_exact(&mut buf)?;
310 bytes_read += 1;
311 let byte = buf[0];
312 let payload = (byte & 0x7F) as u64;
313 if shift >= 64 {
314 return Err(io::Error::new(
315 io::ErrorKind::InvalidData,
316 "varint too long",
317 ));
318 }
319 result |= payload << shift;
320 if byte & 0x80 == 0 {
321 return Ok((result, bytes_read));
322 }
323 shift += 7;
324 }
325 }
326
327 #[test]
328 fn varint_round_trip_small_values() {
329 for n in [0u64, 1, 0x7F, 0x80, 0xFF, 0x3FFF, 0x4000, 0xFFFFFFFF] {
330 let mut buf = Vec::new();
331 write_uvarint(&mut buf, n).unwrap();
332 let (got, consumed) = read_uvarint(&mut &buf[..]).unwrap();
333 assert_eq!(got, n, "round trip failed for {n}");
334 assert_eq!(consumed, buf.len(), "byte count off for {n}");
335 }
336 }
337
338 #[test]
339 fn varint_round_trip_u64_max() {
340 let mut buf = Vec::new();
341 write_uvarint(&mut buf, u64::MAX).unwrap();
342 let (got, _) = read_uvarint(&mut &buf[..]).unwrap();
343 assert_eq!(got, u64::MAX);
344 assert_eq!(buf.len(), 10, "u64::MAX should use 10 bytes");
345 }
346
347 #[test]
348 fn varint_encodes_127_as_one_byte() {
349 let mut buf = Vec::new();
350 write_uvarint(&mut buf, 127).unwrap();
351 assert_eq!(buf, vec![0x7F]);
352 }
353
354 #[test]
355 fn varint_encodes_128_as_two_bytes() {
356 let mut buf = Vec::new();
357 write_uvarint(&mut buf, 128).unwrap();
358 assert_eq!(buf, vec![0x80, 0x01]);
359 }
360
361 fn make_cidv1_dagpb(digest: [u8; 32]) -> Vec<u8> {
364 let mh = Multihash::<64>::wrap(0x12, &digest).unwrap();
365 let cid = RawCid::new_v1(0x70, mh);
366 cid.to_bytes()
367 }
368
369 #[test]
370 fn dagcbor_header_v1_root_canonical_bytes() {
371 let cid_bytes = make_cidv1_dagpb([0u8; 32]);
373 let hdr = super::build_dagcbor_header(&cid_bytes);
374
375 let mut expected = vec![0xA2];
385 expected.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
386 expected.push(0x81);
387 expected.push(0xD8);
388 expected.push(0x2A);
389
390 let bytestring_len = 1 + cid_bytes.len();
391 assert!(bytestring_len <= u8::MAX as usize);
392 expected.push(0x58);
393 expected.push(bytestring_len as u8);
394 expected.push(0x00);
395 expected.extend_from_slice(&cid_bytes);
396
397 expected.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
398 expected.push(0x01);
399
400 assert_eq!(hdr, expected);
401 }
402
403 #[test]
404 fn dagcbor_header_small_cid_uses_short_bytestring() {
405 let cid_bytes = vec![0u8; 34];
407 let hdr = super::build_dagcbor_header(&cid_bytes);
408 let tag_idx = hdr.iter().position(|&b| b == 0x2A).unwrap();
409 assert_eq!(
410 hdr[tag_idx + 1],
411 0x58,
412 "bytestring should use 0x58 form for len 35"
413 );
414 assert_eq!(hdr[tag_idx + 2], 35);
415 }
416
417 #[test]
418 fn read_carv1_root_round_trip() {
419 let cid_bytes = make_cidv1_dagpb([7u8; 32]);
420 let header = super::build_dagcbor_header(&cid_bytes);
421 let mut framed = Vec::new();
422 super::write_uvarint(&mut framed, header.len() as u64).unwrap();
423 framed.extend_from_slice(&header);
424
425 let got = super::read_carv1_root_from(&mut &framed[..]).unwrap();
426 let expected_cid_str = cid::Cid::try_from(&cid_bytes[..]).unwrap().to_string();
427 assert_eq!(got, expected_cid_str);
428 }
429
430 #[test]
431 fn read_carv1_root_truncated_varint() {
432 let bytes = [0x80u8]; let err = super::read_carv1_root_from(&mut &bytes[..]).unwrap_err();
434 assert!(matches!(err, super::InvalidCarFile::MalformedVarint));
435 }
436
437 #[test]
438 fn read_carv1_root_oversized_header_declared() {
439 let mut bytes = Vec::new();
440 super::write_uvarint(&mut bytes, (super::MAX_CAR_HEADER_BYTES + 1) as u64).unwrap();
441 let err = super::read_carv1_root_from(&mut &bytes[..]).unwrap_err();
442 assert!(matches!(err, super::InvalidCarFile::HeaderTooLarge));
443 }
444
445 #[test]
446 fn read_carv1_root_truncated_body() {
447 let mut bytes = Vec::new();
448 super::write_uvarint(&mut bytes, 40).unwrap();
449 bytes.extend_from_slice(&[0u8; 20]); let err = super::read_carv1_root_from(&mut &bytes[..]).unwrap_err();
451 assert!(matches!(err, super::InvalidCarFile::TruncatedHeader));
452 }
453
454 #[test]
455 fn read_carv1_root_version_2_rejected() {
456 let cid_bytes = make_cidv1_dagpb([1u8; 32]);
457 let mut hdr = vec![0xA2];
458 hdr.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
459 hdr.push(0x81);
460 hdr.extend_from_slice(&[0xD8, 0x2A]);
461 let bs_len = 1 + cid_bytes.len();
462 hdr.push(0x58);
463 hdr.push(bs_len as u8);
464 hdr.push(0x00);
465 hdr.extend_from_slice(&cid_bytes);
466 hdr.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
467 hdr.push(0x02); let mut framed = Vec::new();
470 super::write_uvarint(&mut framed, hdr.len() as u64).unwrap();
471 framed.extend_from_slice(&hdr);
472
473 let err = super::read_carv1_root_from(&mut &framed[..]).unwrap_err();
474 assert!(matches!(
475 err,
476 super::InvalidCarFile::UnsupportedVersion { got: 2 }
477 ));
478 }
479
480 #[test]
481 fn read_carv1_root_zero_roots_rejected() {
482 let mut hdr = vec![0xA2];
483 hdr.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
484 hdr.push(0x80); hdr.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
486 hdr.push(0x01);
487
488 let mut framed = Vec::new();
489 super::write_uvarint(&mut framed, hdr.len() as u64).unwrap();
490 framed.extend_from_slice(&hdr);
491
492 let err = super::read_carv1_root_from(&mut &framed[..]).unwrap_err();
493 assert!(matches!(
494 err,
495 super::InvalidCarFile::BadRootCount { got: 0 }
496 ));
497 }
498
499 #[test]
500 fn read_carv1_root_two_roots_rejected() {
501 let cid_bytes = make_cidv1_dagpb([2u8; 32]);
502 let mut hdr = vec![0xA2];
503 hdr.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
504 hdr.push(0x82); for _ in 0..2 {
506 hdr.extend_from_slice(&[0xD8, 0x2A]);
507 let bs_len = 1 + cid_bytes.len();
508 hdr.push(0x58);
509 hdr.push(bs_len as u8);
510 hdr.push(0x00);
511 hdr.extend_from_slice(&cid_bytes);
512 }
513 hdr.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
514 hdr.push(0x01);
515
516 let mut framed = Vec::new();
517 super::write_uvarint(&mut framed, hdr.len() as u64).unwrap();
518 framed.extend_from_slice(&hdr);
519
520 let err = super::read_carv1_root_from(&mut &framed[..]).unwrap_err();
521 assert!(matches!(
522 err,
523 super::InvalidCarFile::BadRootCount { got: 2 }
524 ));
525 }
526
527 #[test]
528 fn read_carv1_root_malformed_cbor_rejected() {
529 let mut framed = Vec::new();
530 super::write_uvarint(&mut framed, 4).unwrap();
531 framed.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
532 let err = super::read_carv1_root_from(&mut &framed[..]).unwrap_err();
533 assert!(matches!(err, super::InvalidCarFile::MalformedHeader));
534 }
535
536 #[test]
537 fn read_carv1_root_overlong_varint_rejected() {
538 let bytes = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02];
542 let err = super::read_carv1_root_from(&mut &bytes[..]).unwrap_err();
543 assert!(matches!(err, super::InvalidCarFile::MalformedVarint));
544 }
545
546 #[test]
547 fn read_carv1_root_version_first_ordering_accepted() {
548 let cid_bytes = make_cidv1_dagpb([9u8; 32]);
552 let mut hdr = vec![0xA2];
553 hdr.extend_from_slice(&[0x67, b'v', b'e', b'r', b's', b'i', b'o', b'n']);
555 hdr.push(0x01);
556 hdr.extend_from_slice(&[0x65, b'r', b'o', b'o', b't', b's']);
558 hdr.push(0x81);
559 hdr.extend_from_slice(&[0xD8, 0x2A]);
560 let bs_len = 1 + cid_bytes.len();
561 hdr.push(0x58);
562 hdr.push(bs_len as u8);
563 hdr.push(0x00);
564 hdr.extend_from_slice(&cid_bytes);
565
566 let mut framed = Vec::new();
567 super::write_uvarint(&mut framed, hdr.len() as u64).unwrap();
568 framed.extend_from_slice(&hdr);
569
570 let got = super::read_carv1_root_from(&mut &framed[..]).unwrap();
571 let expected = cid::Cid::try_from(&cid_bytes[..]).unwrap().to_string();
572 assert_eq!(got, expected);
573 }
574
575 #[test]
576 fn write_carv1_header_emits_varint_prefix_and_canonical_header() {
577 let cid_bytes = make_cidv1_dagpb([3u8; 32]);
578 let mut out = Vec::new();
579 super::write_carv1_header(&mut out, &cid_bytes).unwrap();
580
581 let cid_str = super::read_carv1_root_from(&mut &out[..]).unwrap();
583 let expected = cid::Cid::try_from(&cid_bytes[..]).unwrap().to_string();
584 assert_eq!(cid_str, expected);
585 }
586
587 #[test]
588 fn write_block_frame_layout() {
589 let mut out = Vec::new();
590 super::write_block_frame(&mut out, b"abc", b"hello").unwrap();
591 assert_eq!(out, b"\x08abchello");
593 }
594
595 #[test]
596 fn write_block_frame_large_payload_uses_multi_byte_varint() {
597 let cid = vec![0xAAu8; 36];
598 let block = vec![0xBBu8; 130]; let mut out = Vec::new();
600 super::write_block_frame(&mut out, &cid, &block).unwrap();
601 assert_eq!(&out[..2], &[0xA6, 0x01]);
602 assert_eq!(&out[2..2 + cid.len()], &cid[..]);
603 assert_eq!(&out[2 + cid.len()..], &block[..]);
604 }
605}