use ipld_core::{cid::Cid, ipld::Ipld};
use crate::error::CarDecodeError;
#[derive(Debug, PartialEq)]
pub(crate) struct CarV1Header {
pub version: u64,
pub roots: Option<Vec<Cid>>,
}
pub(crate) fn decode_carv1_header(header: &[u8]) -> Result<CarV1Header, CarDecodeError> {
let header: Ipld = serde_ipld_dagcbor::from_slice(header).map_err(|e| {
CarDecodeError::InvalidCarV1Header(format!("header cbor codec error: {e:?}"))
})?;
let header = if let Ipld::Map(map) = header {
map
} else {
return Err(CarDecodeError::InvalidCarV1Header(format!(
"header expected cbor Map but got {:#?}",
header
)));
};
let roots = match header.get("roots") {
Some(Ipld::List(roots_ipld)) => {
let mut roots = Vec::with_capacity(roots_ipld.len());
for root in roots_ipld {
if let Ipld::Link(cid) = root {
roots.push(*cid);
} else {
return Err(CarDecodeError::InvalidCarV1Header(format!(
"roots key elements expected cbor Link but got {:#?}",
root
)));
}
}
Some(roots)
}
Some(ipld) => {
return Err(CarDecodeError::InvalidCarV1Header(format!(
"roots key expected cbor List but got {:#?}",
ipld
)))
}
None => None,
};
let version = match header.get("version") {
Some(Ipld::Integer(int)) => *int as u64,
Some(ipld) => {
return Err(CarDecodeError::InvalidCarV1Header(format!(
"version key expected cbor Integer but got {:#?}",
ipld
)))
}
None => {
return Err(CarDecodeError::InvalidCarV1Header(format!(
"expected header key version, keys: {:?}",
header.keys().collect::<Vec<&String>>()
)))
}
};
Ok(CarV1Header { version, roots })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{carv2_header::CARV2_PRAGMA, *};
#[test]
fn decode_carv1_header_basic() {
let header_buf = hex::decode("a265726f6f747381d82a58230012205b0995ced69229d26009c53c185a62ea805a339383521edbed1028c4966154486776657273696f6e01").unwrap();
let cid = Cid::try_from("QmUU2HcUBVSXkfWPUc3WUSeCMrWWeEJTuAgR9uyWBhh9Nf").unwrap();
assert_eq!(
decode_carv1_header(&header_buf).unwrap(),
CarV1Header {
version: 1,
roots: Some(vec!(cid))
}
)
}
#[test]
fn decode_carv1_header_error_cbor_codec() {
let header_buf = hex::decode("a265726f6f747371d82a58230012205b0995ced69229d26009c53c185a62ea805a339383521edbed1028c4966154486776657273696f6e01").unwrap();
match decode_carv1_header(&header_buf) {
Err(CarDecodeError::InvalidCarV1Header(str)) => assert_eq!(
str,
"header cbor codec error: InvalidUtf8(Utf8Error { valid_up_to: 0, error_len: Some(1) })"
),
x => panic!("other result {:?}", x),
}
}
#[test]
fn decode_carv1_header_error_cbor_type() {
let header_buf = hex::decode("0000").unwrap();
match decode_carv1_header(&header_buf) {
Err(CarDecodeError::InvalidCarV1Header(str)) => {
assert_eq!(str, "header cbor codec error: TrailingData")
}
x => panic!("other result {:?}", x),
}
}
#[test]
fn decode_carv1_header_v2_pragma() {
assert_eq!(
decode_carv1_header(&CARV2_PRAGMA[1..]).unwrap(),
CarV1Header {
version: 2,
roots: None
}
)
}
}