Skip to main content

ZiPatchReader

Struct ZiPatchReader 

Source
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 returns ParseError::InvalidMagic from ZiPatchReader::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; pass false to ZiPatchReader::with_checksum_verification to disable it.
  • Termination — the EOF_ chunk is consumed internally and causes Self::next_chunk to return Ok(None). Call ZiPatchReader::is_complete after iteration to distinguish a clean end from a truncated stream.
  • Fused — once Ok(None) (clean EOF) or an Err(_) is returned, subsequent calls to next_chunk also return Ok(None).

§Errors

Each call to Self::next_chunk returns Err(e) on parse failure, then Ok(None) on all future calls. Possible errors include:

§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>

Source

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
Source

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.

Source

pub fn max_chunk_size(&self) -> u32

Returns the configured maximum chunk-body length, in bytes.

Source

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.

Source

pub fn patch_name(&self) -> Option<&str>

Returns the caller-supplied patch identifier, if any.

Set by Self::with_patch_name; None otherwise.

Source

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.

Source

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.

Source

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.

Source

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§

Source§

impl<R: Debug> Debug for ZiPatchReader<R>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more