pub struct ZiPatchReader<R> { /* private fields */ }Expand description
Streaming parser over the Chunks in a ZiPatch stream.
ZiPatchReader wraps any std::io::Read source and yields one
ChunkRecord per call to Self::next_chunk. It validates the
12-byte file magic on construction, then reads chunks sequentially
until the EOF_ terminator is encountered or an error occurs.
§Stream contract
- Magic — the first 12 bytes must be
\x91ZIPATCH\r\n\x1a\n. Any mismatch returnsParseError::InvalidMagicfromZiPatchReader::new. - Framing — every chunk is a length-prefixed frame:
[body_len: u32 BE] [tag: 4 B] [body: body_len B] [crc32: u32 BE]. - CRC32 — computed over
tag ++ body. Verification is enabled by default; passfalsetoZiPatchReader::with_checksum_verificationto disable it. - Termination — the
EOF_chunk is consumed internally and causesSelf::next_chunkto returnOk(None). CallZiPatchReader::is_completeafter iteration to distinguish a clean end from a truncated stream. - Fused — once
Ok(None)(clean EOF) or anErr(_)is returned, subsequent calls tonext_chunkalso returnOk(None).
§Errors
Each call to Self::next_chunk returns Err(e) on parse failure,
then Ok(None) on all future calls. Possible errors include:
ParseError::TruncatedPatch— stream ended beforeEOF_.ParseError::OversizedChunk— a declared chunk body exceeds the configured max chunk size (defaultDEFAULT_MAX_CHUNK_SIZE, 512 MiB).ParseError::ChecksumMismatch— CRC32 verification failed.ParseError::UnknownChunkTag— unrecognised 4-byte tag.ParseError::Io— underlying I/O failure.
§Async usage
ZiPatchReader is a synchronous parser over a std::io::Read
source — see the crate-level “Async usage” section for the rationale.
Async consumers wrap iteration (and any apply call that drives it)
in tokio::task::spawn_blocking. To stream a patch that is itself
arriving over an async transport (e.g. reqwest::Response::bytes_stream),
either buffer it through a tempfile::NamedTempFile and feed the
reopened std::fs::File to ZiPatchReader::new, or bridge with a
blocking-reader adapter that pulls from a
tokio::sync::mpsc-equivalent channel populated
by the async download task.
§Example
Build a minimal in-memory patch (magic + ADIR + EOF_) and walk it:
use std::io::Cursor;
use zipatch_rs::{Chunk, ZiPatchReader};
// Helper: wrap tag + body into a correctly framed chunk with CRC32.
fn make_chunk(tag: &[u8; 4], body: &[u8]) -> Vec<u8> {
let mut crc_input = Vec::new();
crc_input.extend_from_slice(tag);
crc_input.extend_from_slice(body);
let crc = crc32fast::hash(&crc_input);
let mut out = Vec::new();
out.extend_from_slice(&(body.len() as u32).to_be_bytes());
out.extend_from_slice(tag);
out.extend_from_slice(body);
out.extend_from_slice(&crc.to_be_bytes());
out
}
// 12-byte ZiPatch magic.
let magic: [u8; 12] = [0x91, 0x5A, 0x49, 0x50, 0x41, 0x54, 0x43, 0x48, 0x0D, 0x0A, 0x1A, 0x0A];
// ADIR body: u32 BE name_len (7) + b"created".
let mut adir_body = Vec::new();
adir_body.extend_from_slice(&7u32.to_be_bytes());
adir_body.extend_from_slice(b"created");
let mut patch = Vec::new();
patch.extend_from_slice(&magic);
patch.extend_from_slice(&make_chunk(b"ADIR", &adir_body));
patch.extend_from_slice(&make_chunk(b"EOF_", &[]));
let mut reader = ZiPatchReader::new(Cursor::new(patch)).unwrap();
let mut chunks = Vec::new();
while let Some(rec) = reader.next_chunk().unwrap() {
chunks.push(rec.chunk);
}
assert_eq!(chunks.len(), 1);
assert!(matches!(chunks[0], Chunk::AddDirectory(_)));Implementations§
Source§impl<R: Read> ZiPatchReader<R>
impl<R: Read> ZiPatchReader<R>
Sourcepub fn new(reader: R) -> Result<Self>
pub fn new(reader: R) -> Result<Self>
Wrap reader and validate the leading 12-byte ZiPatch magic.
Consumes exactly 12 bytes from reader. The magic is the byte sequence
0x91 0x5A 0x49 0x50 0x41 0x54 0x43 0x48 0x0D 0x0A 0x1A 0x0A
(i.e. \x91ZIPATCH\r\n\x1a\n).
The reader is wrapped in a std::io::BufReader internally, so the
many small typed reads the chunk parser issues (4-byte size, 4-byte
tag, 5-byte SQPK prefix, …) coalesce into a small number of syscalls.
Callers do not need to pre-wrap a raw std::fs::File or other
unbuffered source.
CRC32 verification is enabled by default. Call
ZiPatchReader::with_checksum_verification with false before
iterating to disable it.
§Errors
ParseError::InvalidMagic— the first 12 bytes do not match the expected magic.ParseError::Io— an I/O error occurred while reading the magic.
Sourcepub fn with_max_chunk_size(self, bytes: u32) -> Self
pub fn with_max_chunk_size(self, bytes: u32) -> Self
Set the upper bound on a single chunk’s declared body length, in bytes.
The parser rejects any chunk whose body_len exceeds bytes with
ParseError::OversizedChunk before allocating space for its body.
Defaults to DEFAULT_MAX_CHUNK_SIZE (512 MiB). Raise it for
patches with unusually large chunks; lower it when applying untrusted
streams to bound the parser’s worst-case allocation.
§Panics
Panics if bytes is zero — a zero ceiling rejects every chunk and
is a programming error.
Sourcepub fn max_chunk_size(&self) -> u32
pub fn max_chunk_size(&self) -> u32
Returns the configured maximum chunk-body length, in bytes.
Sourcepub fn with_patch_name(self, name: impl Into<String>) -> Self
pub fn with_patch_name(self, name: impl Into<String>) -> Self
Attach a human-readable identifier to this patch stream.
The identifier is stamped onto every
SequentialCheckpoint the apply
driver emits so a future
resume_apply_patch call can
detect a checkpoint that was persisted for a different patch and
refuse to resume from it.
Typical value is the patch filename (e.g. "H2017.07.11.0000.0000a.patch").
No interpretation is performed — the string is compared verbatim.
Sourcepub fn patch_name(&self) -> Option<&str>
pub fn patch_name(&self) -> Option<&str>
Returns the caller-supplied patch identifier, if any.
Set by Self::with_patch_name; None otherwise.
Sourcepub fn with_checksum_verification(self, on: bool) -> Self
pub fn with_checksum_verification(self, on: bool) -> Self
Toggle per-chunk CRC32 verification.
Verification is enabled by default after ZiPatchReader::new.
Pass false to skip CRC checks — useful when the source has already
been verified out-of-band (e.g. a download hash was checked before the
file was opened), or when processing known-good test data where the
overhead is unnecessary.
Sourcepub fn is_complete(&self) -> bool
pub fn is_complete(&self) -> bool
Returns true if iteration reached the EOF_ terminator cleanly.
A false return after next() yields None indicates the stream was
truncated — the download or file copy was incomplete. In that case the
iterator stopped because of a ParseError::TruncatedPatch error,
not because the patch finished normally.
Sourcepub fn bytes_read(&self) -> u64
pub fn bytes_read(&self) -> u64
Returns the running total of bytes consumed from the patch stream.
Starts at 12 after ZiPatchReader::new (the magic header has been
read) and increases monotonically by the size of each chunk’s wire
frame after each successful Self::next_chunk call. Includes the
EOF_ terminator’s frame.
On parse error, the counter is not advanced past the failing chunk — it reflects the byte offset at the start of that chunk’s length prefix, not the broken position somewhere inside its frame.
Per-chunk consumers should read the equivalent counter off the
ChunkRecord::bytes_read field. This getter is for end-of-stream
reporting — after Self::next_chunk returned Ok(None), no
ChunkRecord is produced for the consumed EOF_ frame, so the
final stream position is only available through this method.
Sourcepub fn next_chunk(&mut self) -> Result<Option<ChunkRecord>>
pub fn next_chunk(&mut self) -> Result<Option<ChunkRecord>>
Read the next chunk frame from the underlying stream.
Returns Ok(Some(record)) for each successfully parsed chunk in
stream order, Ok(None) after the EOF_ terminator has been
consumed (the terminator itself is never surfaced as a record), and
Err(_) on a parse failure. After Ok(None) or any Err(_),
subsequent calls return Ok(None) — the reader is fused.
§Errors
See Self’s “Errors” section.
Trait Implementations§
Auto Trait Implementations§
impl<R> Freeze for ZiPatchReader<R>where
R: Freeze,
impl<R> RefUnwindSafe for ZiPatchReader<R>where
R: RefUnwindSafe,
impl<R> Send for ZiPatchReader<R>where
R: Send,
impl<R> Sync for ZiPatchReader<R>where
R: Sync,
impl<R> Unpin for ZiPatchReader<R>where
R: Unpin,
impl<R> UnsafeUnpin for ZiPatchReader<R>where
R: UnsafeUnpin,
impl<R> UnwindSafe for ZiPatchReader<R>where
R: UnwindSafe,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more