use std::io::Read;
pub use ipld_core::cid::Cid;
use crate::{
block_cid::assert_block_cid,
car_block::decode_block,
car_header::{read_car_header, StreamEnd},
};
pub use crate::{car_header::CarHeader, error::CarDecodeError};
mod block_cid;
mod car_block;
mod car_header;
mod carv1_header;
mod carv2_header;
mod error;
mod varint;
pub struct CarReader<'a, R> {
pub header: CarHeader,
read_bytes: usize,
validate_block_hash: bool,
reader: &'a mut R,
}
impl<'a, R> CarReader<'a, R>
where
R: Read,
{
pub fn new(
reader: &'a mut R,
validate_block_hash: bool,
) -> Result<CarReader<'a, R>, CarDecodeError> {
let header = read_car_header(reader)?;
return Ok(CarReader {
header,
read_bytes: 0,
validate_block_hash,
reader,
});
}
}
pub fn car_read_all<R: Read>(
r: &mut R,
validate_block_hash: bool,
) -> Result<(Vec<(Cid, Vec<u8>)>, CarHeader), CarDecodeError> {
let mut decoder = CarReader::new(r, validate_block_hash)?;
let mut items: Vec<(Cid, Vec<u8>)> = vec![];
while let Some(item) = decoder.next() {
let item = item?;
items.push(item);
}
Ok((items, decoder.header))
}
impl<'a, R> Iterator for CarReader<'a, R>
where
R: Read + 'a,
{
type Item = Result<(Cid, Vec<u8>), CarDecodeError>;
fn next(&mut self) -> Option<Self::Item> {
if let StreamEnd::AfterNBytes(blocks_len) = self.header.eof_stream {
if self.read_bytes >= blocks_len {
return None;
}
}
match decode_block(&mut self.reader) {
Ok((_r, cid, block, block_len)) => {
if self.validate_block_hash {
if let Err(e) = assert_block_cid(&cid, &block) {
return Some(Err(e));
}
}
self.read_bytes += block_len;
Some(Ok((cid, block)))
}
Err(CarDecodeError::BlockStartEOF)
if self.header.eof_stream == StreamEnd::OnBlockEOF =>
{
None
}
Err(err) => Some(Err(err)),
}
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, str::FromStr};
use serde::{Deserialize, Serialize};
use super::*;
use crate::car_header::CarVersion;
#[derive(Debug, Deserialize, Serialize)]
struct ExpectedCarv1 {
header: ExpectedCarv1Header,
blocks: Vec<ExpectedCarBlock>,
}
#[derive(Debug, Deserialize, Serialize)]
struct ExpectedCarv1Header {
roots: Vec<ExpectedCid>,
version: u8,
}
#[derive(Debug, Deserialize, Serialize)]
#[allow(non_snake_case)]
struct ExpectedCarBlock {
cid: ExpectedCid,
blockLength: usize,
}
type ExpectedCid = HashMap<String, String>;
fn parse_expected_cids(cids: &Vec<ExpectedCid>) -> Vec<Cid> {
cids.iter().map(parse_expected_cid).collect()
}
fn parse_expected_cid(cid: &ExpectedCid) -> Cid {
Cid::from_str(cid.get("/").unwrap()).unwrap()
}
#[test]
fn decode_carv1_helloworld_no_stream() {
let car_filepath = "./tests/custom_fixtures/helloworld.car";
let mut file = std::fs::File::open(car_filepath).unwrap();
let (blocks, header) = car_read_all(&mut file, true).unwrap();
let root_cid = Cid::from_str("QmUU2HcUBVSXkfWPUc3WUSeCMrWWeEJTuAgR9uyWBhh9Nf").unwrap();
let root_block = hex::decode("0a110802120b68656c6c6f776f726c640a180b").unwrap();
assert_eq!(blocks, vec!((root_cid, root_block)));
assert_eq!(header.version, CarVersion::V1);
assert_eq!(header.roots, vec!(root_cid));
}
#[test]
fn decode_carv1_helloworld_stream() {
let car_filepath = "./tests/custom_fixtures/helloworld.car";
let mut file = std::fs::File::open(car_filepath).unwrap();
let (blocks, header) = car_read_all(&mut file, true).unwrap();
let root_cid = Cid::from_str("QmUU2HcUBVSXkfWPUc3WUSeCMrWWeEJTuAgR9uyWBhh9Nf").unwrap();
let root_block = hex::decode("0a110802120b68656c6c6f776f726c640a180b").unwrap();
assert_eq!(blocks, vec!((root_cid, root_block)));
assert_eq!(header.version, CarVersion::V1);
assert_eq!(header.roots, vec!(root_cid));
}
#[test]
fn decode_carv1_basic() {
run_car_basic_test(
"./tests/spec_fixtures/carv1-basic.car",
"./tests/spec_fixtures/carv1-basic.json",
);
}
#[test]
fn decode_carv2_basic() {
run_car_basic_test(
"./tests/spec_fixtures/carv2-basic.car",
"./tests/spec_fixtures/carv2-basic.json",
);
}
fn run_car_basic_test(car_filepath: &str, car_json_expected: &str) {
let expected_car = std::fs::read_to_string(car_json_expected).unwrap();
let expected_car: ExpectedCarv1 = serde_json::from_str(&expected_car).unwrap();
let mut file = std::fs::File::open(car_filepath).unwrap();
let mut streamer = CarReader::new(&mut file, true).unwrap();
assert_eq!(streamer.header.version as u8, expected_car.header.version);
assert_eq!(
streamer.header.roots,
parse_expected_cids(&expected_car.header.roots)
);
let mut blocks: Vec<(Cid, Vec<u8>)> = vec![];
while let Some(item) = streamer.next() {
let item = item.unwrap();
blocks.push(item);
}
let block_cids = blocks.iter().map(|block| block.0).collect::<Vec<Cid>>();
let expected_block_cids = expected_car
.blocks
.iter()
.map(|block| parse_expected_cid(&block.cid))
.collect::<Vec<Cid>>();
assert_eq!(block_cids, expected_block_cids);
}
}