EntryReader
EntryReader yields one entry’s original bytes, decompressing when the
entry is deflated. It is what you reach for when an asset is too large to hold
at once.
const buffer = try gpa.alloc(u8, zpack.stream_buffer_len);defer gpa.free(buffer);
var entry_reader = try archive.entryReader(entry, buffer);const r = entry_reader.reader();
var header: [4]u8 = undefined;try r.readSliceAll(&header);Obtain one from Archive.entryReader rather than constructing
it directly.
Buffer requirements
Section titled “Buffer requirements”| Entry method | Minimum buffer |
|---|---|
deflate |
zpack.stream_buffer_len |
store |
Any size, including smaller than the entry |
pub const stream_buffer_len = flate.max_window_len + 4096;A deflated entry needs flate.max_window_len (64 KiB) for the decompressor’s
window - twice deflate’s 32 KiB back-reference history, so matches resolve
without shuffling the buffer - plus 4 KiB to hold compressed bytes read from
disk. That is where 69632 comes from. Anything smaller returns
error.BufferTooSmall.
If you do not know the method up front, size for stream_buffer_len and it
works either way. entry.method tells you, if you want to size down for stored
entries.
Internally the buffer is split: the first flate.max_window_len bytes become
the decompressor’s window, the remainder becomes the input buffer for the
compressed bytes. A stored entry uses the whole thing as one plain read buffer.
reader
Section titled “reader”pub fn reader(self: *EntryReader) *Io.ReaderReturns a std.Io.Reader over the entry’s original bytes. It reports end of
stream at the entry’s last byte rather than the file’s, so a deflate stream
can never run on into a neighbouring entry or into the index.
Everything in the std.Io.Reader vocabulary works - readSliceAll,
peekGreedy, streamRemaining, toss, and so on.
No hash checking
Section titled “No hash checking”EntryReader streams raw bytes and verifies nothing. That is the trade for not
buffering the whole entry - the hash covers the entry in full, so it cannot be
confirmed until the last byte has gone past.
Three options:
Hash as you stream, if you need the check and the bytes:
var hasher = zpack.format.Hasher.init(zpack.format.hash_seed);var total: u64 = 0;
while (true) { const chunk = r.peekGreedy(1) catch |err| switch (err) { error.EndOfStream => break, else => |e| return e, }; hasher.update(chunk); // ...consume chunk... total += chunk.len; r.toss(chunk.len);}
if (total != entry.size or hasher.final() != entry.hash) return error.HashMismatch;Check the length as well as the digest: an entry that produces fewer bytes than promised could otherwise pass a partial-prefix hash.
Use Archive.read when the asset fits in a buffer. It
verifies before returning.
Use Archive.verify once at startup, then stream freely
afterwards.
StoredReader
Section titled “StoredReader”pub const StoredReader = struct { io: Io, file: File, pos: u64, remaining: u64, err: ?File.ReadPositionalError, interface: Io.Reader,};The layer beneath EntryReader: it reads the bytes an entry occupies in the
archive, stopping at the entry’s last byte even though the file continues past
it. Those bytes are still compressed when the entry is deflated.
It implements the std.Io.Reader vtable directly rather than wrapping a
File.Reader, so it holds no pointer into itself and can be returned by value.
Reach for it only when you want an entry’s stored bytes - copying a deflate
stream between archives without recompressing it, for instance. For contents,
use EntryReader.
Reads use readPositional, which is why concurrent reads from one Archive do
not race on a shared file cursor. A failed read is recorded in err and
surfaces through the interface as error.ReadFailed.
Worked example
Section titled “Worked example”Example 04 streams a 1 MiB asset through a 68 KiB buffer, hashing as it goes:
zig build example-04