///|
pub struct Entry {
cid : @cid.Cid
location : Location
}
///|
pub fn Entry::cid(self : Entry) -> @cid.Cid {
self.cid
}
///|
pub fn Entry::location(self : Entry) -> Location {
self.location
}
///|
/// Validated CAR bytes with an exact full-CID offset index. Duplicate sections
/// remain visible in source order. No payloads are retained in the index.
pub struct Archive {
bytes : Bytes
header : Header
entries : Array[Entry]
by_cid : Map[Bytes, Array[Entry]]
limits : Limits
}
///|
pub fn Archive::decode(
bytes : Bytes,
limits? : Limits = Limits::default(),
) -> Archive raise CarError {
budget("archive", bytes.length(), limits.max_archive)
let decoder = Decoder::new(limits~)
let entries = []
let by_cid : Map[Bytes, Array[Entry]] = Map([])
let mut header = None
let mut pos = 0
while pos < bytes.length() {
let end = pos + (bytes.length() - pos).min(4096)
for event in decoder.feed(bytes[pos:end]) {
match event {
Header(h) => header = Some(h)
Block(block, location) => {
let entry = Entry::{ cid: block.cid, location, }
entries.push(entry)
by_cid.get_or_init(block.cid.to_bytes(), () => []).push(entry)
}
}
}
pos = end
}
decoder.finish()
match header {
Some(header) => { bytes, header, entries, by_cid, limits, }
None => raise InvalidHeader("missing CAR header")
}
}
///|
pub fn Archive::header(self : Archive) -> Header {
self.header
}
///|
pub fn Archive::entries(self : Archive) -> Array[Entry] {
self.entries.copy()
}
///|
pub fn Archive::bytes(self : Archive) -> Bytes {
self.bytes
}
///|
pub fn Archive::length(self : Archive) -> Int {
self.entries.length()
}
///|
/// Read a section by source-order index, including individual duplicates.
pub fn Archive::block_at(self : Archive, index : Int) -> Block raise CarError {
if index < 0 || index >= self.entries.length() {
raise InvalidState("block index out of range")
}
self.read_entry(self.entries[index])
}
///|
pub fn Archive::locations(self : Archive, cid : @cid.Cid) -> Array[Location] {
match self.by_cid.get(cid.to_bytes()) {
Some(entries) => entries.map(entry => entry.location)
None => []
}
}
///|
fn Archive::read_entry(self : Archive, entry : Entry) -> Block raise CarError {
let loc = entry.location
let bytes = self.bytes[loc.section_offset:loc.section_offset +
loc.section_length]
let prefix_length = match @cid.decode_u64(bytes) {
Ok((_, n)) => n
Err(_) => raise InvalidVarint(loc.section_offset)
}
let (block, _) = decode_block(bytes[prefix_length:].to_owned(), self.limits)
if block.cid != entry.cid {
raise InvalidCid("index entry CID mismatch")
}
block
}
///|
/// Return the first section for an exact CID. Hash integrity is checked separately.
pub fn Archive::get(self : Archive, cid : @cid.Cid) -> Block raise CarError {
match self.by_cid.get(cid.to_bytes()) {
Some(entries) => self.read_entry(entries[0])
None => raise NotFound
}
}
///|
pub fn Archive::get_all(
self : Archive,
cid : @cid.Cid,
) -> Array[Block] raise CarError {
match self.by_cid.get(cid.to_bytes()) {
Some(entries) => entries.map(entry => self.read_entry(entry))
None => []
}
}
///|
/// Check root block presence only. This does not prove transitive DAG closure.
pub fn Archive::require_roots(self : Archive) -> Unit raise CarError {
for root in self.header.roots {
if !self.by_cid.contains(root.to_bytes()) {
let text = match root.to_text() {
Ok(s) => s
Err(_) => "unprintable CID"
}
raise MissingRoot(text)
}
}
}