Builds and indexes a claim-check pack: a standard ZIP archive with every entry stored uncompressed, laid out as
index.bin— one 8-byte row per claim, in claim order: the offset of the claim's bytes in the pack and their length, each a big-endian unsigned 32-bit integer- one entry per claim, named by its claim id
manifest.json— each claim's claim id, hook id, offset, length, digest, content type, and receive time
The index sits first so a reader holding only a claim id finds its bytes
without knowing the pack's size: read the index entry's header and rows
up to the claim's position (index_prefix_bytes/1, locate/2), then read
the claim's range. Stored entries are what make that range the claim's raw
bytes. The central directory at the end is what any ZIP reader (unzip,
Python zipfile, java.util.zip, Erlang :zip) uses to list the pack with
no Ankusa code.
No ZIP64: a pack must stay under 65,535 entries and 4 GiB. Callers split
well below that (claim_check.pack_max_bytes); build/1 raises if asked to
exceed it rather than write an archive no reader could open.
Output is deterministic for the same claims: fixed timestamps (1980-01-01, the DOS epoch) and no extra fields.
Summary
Functions
Build a pack. Returns the archive as iodata (no copy of the bodies) and where each claim's bytes landed, in input order.
How many claims a pack holds, from its first bytes (the index entry's header
is enough). :error when the bytes don't start with a pack's index.
The digest of the claim at index, from digests/3's bytes.
The sha256 of every claim of pack pack_id, from its manifest's JSON: the
raw 32-byte digests concatenated in claim order (digest/2 takes one out).
:error unless the manifest lists exactly count claims, in order, each
under the claim id Ankusa.ClaimCheck.Ref.claim_id/2 gives it and with a
lowercase hex sha256.
Bytes a pack adds for one claim, excluding the manifest: its index row, local header, and central-directory record. Used to size packs up front.
How many bytes, from the start of a pack, locate/2 needs to find the
claim at index: the index entry's header and its rows up to that claim.
Where the claim at index sits, from a pack's first
index_prefix_bytes(index) bytes (a shorter read, when the object is
smaller, is fine). :error when the bytes don't start with a pack's index
or the pack holds fewer claims.
Bytes of the manifest entry's local header: where its JSON starts, from the entry's offset. The entry follows the last claim, so its offset is that claim's offset plus length.
The manifest's JSON size, from the entry's local header
(manifest_header_bytes/0 bytes read where the last claim ends). :error
when those bytes are not the manifest's header, or claim an absurd size.
Types
@type placement() :: %{ claim_id: String.t(), offset: non_neg_integer(), length: non_neg_integer() }
Functions
Build a pack. Returns the archive as iodata (no copy of the bodies) and where each claim's bytes landed, in input order.
@spec count(binary()) :: {:ok, pos_integer()} | :error
How many claims a pack holds, from its first bytes (the index entry's header
is enough). :error when the bytes don't start with a pack's index.
@spec digest(binary(), non_neg_integer()) :: {:ok, binary()} | :error
The digest of the claim at index, from digests/3's bytes.
@spec digests(binary(), String.t(), pos_integer()) :: {:ok, binary()} | :error
The sha256 of every claim of pack pack_id, from its manifest's JSON: the
raw 32-byte digests concatenated in claim order (digest/2 takes one out).
:error unless the manifest lists exactly count claims, in order, each
under the claim id Ankusa.ClaimCheck.Ref.claim_id/2 gives it and with a
lowercase hex sha256.
@spec entry_overhead() :: pos_integer()
Bytes a pack adds for one claim, excluding the manifest: its index row, local header, and central-directory record. Used to size packs up front.
@spec index_prefix_bytes(non_neg_integer()) :: pos_integer()
How many bytes, from the start of a pack, locate/2 needs to find the
claim at index: the index entry's header and its rows up to that claim.
@spec locate(binary(), non_neg_integer()) :: {:ok, non_neg_integer(), non_neg_integer()} | :error
Where the claim at index sits, from a pack's first
index_prefix_bytes(index) bytes (a shorter read, when the object is
smaller, is fine). :error when the bytes don't start with a pack's index
or the pack holds fewer claims.
@spec manifest_header_bytes() :: pos_integer()
Bytes of the manifest entry's local header: where its JSON starts, from the entry's offset. The entry follows the last claim, so its offset is that claim's offset plus length.
@spec manifest_size(binary()) :: {:ok, non_neg_integer()} | :error
The manifest's JSON size, from the entry's local header
(manifest_header_bytes/0 bytes read where the last claim ends). :error
when those bytes are not the manifest's header, or claim an absurd size.