tinyzip 0.4.0

Low level ZIP file parsing and navigation
Documentation
mod fixture;

use std::alloc::{GlobalAlloc, Layout, System};
use std::io::{Cursor, Read};
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};

static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static ALLOC_BYTES: AtomicUsize = AtomicUsize::new(0);
static CURRENT_BYTES: AtomicUsize = AtomicUsize::new(0);
static PEAK_BYTES: AtomicUsize = AtomicUsize::new(0);

struct CountingAlloc;

unsafe impl GlobalAlloc for CountingAlloc {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let ptr = unsafe { System.alloc(layout) };
        if !ptr.is_null() {
            ALLOC_COUNT.fetch_add(1, Relaxed);
            ALLOC_BYTES.fetch_add(layout.size(), Relaxed);
            let cur = CURRENT_BYTES.fetch_add(layout.size(), Relaxed) + layout.size();
            PEAK_BYTES.fetch_max(cur, Relaxed);
        }
        ptr
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        CURRENT_BYTES.fetch_sub(layout.size(), Relaxed);
        unsafe { System.dealloc(ptr, layout) }
    }
}

#[global_allocator]
static A: CountingAlloc = CountingAlloc;

fn reset() {
    ALLOC_COUNT.store(0, Relaxed);
    ALLOC_BYTES.store(0, Relaxed);
    CURRENT_BYTES.store(0, Relaxed);
    PEAK_BYTES.store(0, Relaxed);
}

struct Stats {
    alloc_count: usize,
    alloc_bytes: usize,
    peak_bytes: usize,
}

fn snapshot() -> Stats {
    Stats {
        alloc_count: ALLOC_COUNT.load(Relaxed),
        alloc_bytes: ALLOC_BYTES.load(Relaxed),
        peak_bytes: PEAK_BYTES.load(Relaxed),
    }
}

fn fmt_bytes(n: usize) -> String {
    if n >= 1024 * 1024 {
        format!("{:.1} MB", n as f64 / (1024.0 * 1024.0))
    } else if n >= 1024 {
        format!("{:.1} KB", n as f64 / 1024.0)
    } else {
        format!("{n} B")
    }
}

fn main() {
    let zip_bytes = fixture::generate();

    let expected_size = {
        let mut archive = zip::ZipArchive::new(Cursor::new(&zip_bytes)).unwrap();
        archive
            .by_name(fixture::TARGET_FILE)
            .unwrap()
            .size() as usize
    };

    let file_count = {
        let archive = zip::ZipArchive::new(Cursor::new(&zip_bytes)).unwrap();
        archive.len()
    };

    println!(
        "Archive: {}, {file_count} files",
        fmt_bytes(zip_bytes.len())
    );
    println!(
        "Target: {} ({} uncompressed)\n",
        fixture::TARGET_FILE,
        fmt_bytes(expected_size)
    );
    println!(
        "{:<30} {:>12} {:>14} {:>14}",
        "Operation", "Alloc count", "Alloc bytes", "Peak heap"
    );
    println!("{:-<72}", "");

    // --- tinyzip: find_file ---
    reset();
    {
        let archive = tinyzip::Archive::open(zip_bytes.as_slice()).unwrap();
        archive
            .find_file(fixture::TARGET_FILE.as_bytes())
            .unwrap();
    }
    let s = snapshot();
    println!(
        "{:<30} {:>12} {:>14} {:>14}",
        "tinyzip find_file",
        s.alloc_count,
        fmt_bytes(s.alloc_bytes),
        fmt_bytes(s.peak_bytes)
    );

    // --- zip: find_file ---
    reset();
    {
        let archive = zip::ZipArchive::new(Cursor::new(&zip_bytes)).unwrap();
        archive.index_for_name(fixture::TARGET_FILE).unwrap();
    }
    let s = snapshot();
    println!(
        "{:<30} {:>12} {:>14} {:>14}",
        "zip find_file",
        s.alloc_count,
        fmt_bytes(s.alloc_bytes),
        fmt_bytes(s.peak_bytes)
    );

    println!();

    // --- tinyzip: extract ---
    reset();
    {
        let archive = tinyzip::Archive::open(zip_bytes.as_slice()).unwrap();
        let entry = archive
            .find_file(fixture::TARGET_FILE.as_bytes())
            .unwrap();
        let mut decompressed = [0u8; 128 * 1024];
        let mut chunks = entry.read_chunks::<4096>().unwrap();
        let mut state =
            miniz_oxide::inflate::stream::InflateState::new(miniz_oxide::DataFormat::Raw);
        let mut out_pos = 0;
        while let Some(chunk) = chunks.next() {
            let result = miniz_oxide::inflate::stream::inflate(
                &mut state,
                chunk.unwrap(),
                &mut decompressed[out_pos..],
                miniz_oxide::MZFlush::None,
            );
            out_pos += result.bytes_written;
        }
    }
    let s = snapshot();
    println!(
        "{:<30} {:>12} {:>14} {:>14}",
        "tinyzip extract",
        s.alloc_count,
        fmt_bytes(s.alloc_bytes),
        fmt_bytes(s.peak_bytes)
    );

    // --- zip: extract ---
    reset();
    {
        let mut archive = zip::ZipArchive::new(Cursor::new(&zip_bytes)).unwrap();
        let mut entry = archive.by_name(fixture::TARGET_FILE).unwrap();
        let mut buf = Vec::with_capacity(expected_size);
        entry.read_to_end(&mut buf).unwrap();
    }
    let s = snapshot();
    println!(
        "{:<30} {:>12} {:>14} {:>14}",
        "zip extract",
        s.alloc_count,
        fmt_bytes(s.alloc_bytes),
        fmt_bytes(s.peak_bytes)
    );
}