asar-rust 0.1.0

Rust port of @electron/asar — create and extract Electron ASAR archives
Documentation
use sha2::{Digest, Sha256};

const ALGORITHM: &str = "SHA256";
const BLOCK_SIZE: usize = 4 * 1024 * 1024; // 4MB

/// Encode a byte slice as a lowercase hexadecimal string.
fn encode_hex(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for byte in bytes {
        s.push(char::from_digit((byte >> 4) as u32, 16).unwrap());
        s.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap());
    }
    s
}

/// SHA256-based file integrity information for an ASAR archive entry.
#[derive(Debug, Clone)]
pub struct FileIntegrity {
    pub algorithm: String,
    pub hash: String,
    pub block_size: usize,
    pub blocks: Vec<String>,
}

impl FileIntegrity {
    pub fn from_buffer(data: &[u8]) -> Self {
        let hash = encode_hex(&Sha256::digest(data));
        let block_count = data.len().div_ceil(BLOCK_SIZE);
        let mut blocks = Vec::with_capacity(std::cmp::max(block_count, 1));

        if data.is_empty() {
            blocks.push(encode_hex(&Sha256::digest(data)));
        } else {
            let mut offset = 0;
            while offset < data.len() {
                let end = std::cmp::min(offset + BLOCK_SIZE, data.len());
                blocks.push(encode_hex(&Sha256::digest(&data[offset..end])));
                offset = end;
            }
        }

        FileIntegrity {
            algorithm: ALGORITHM.to_string(),
            hash,
            block_size: BLOCK_SIZE,
            blocks,
        }
    }

    pub fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
        let mut file_hasher = Sha256::new();
        let mut block_hasher = Sha256::new();
        let mut current_block_size = 0usize;
        let mut blocks = Vec::new();
        let mut buf = vec![0u8; 65536];

        loop {
            let n = reader.read(&mut buf)?;
            if n == 0 {
                break;
            }
            let chunk = &buf[..n];
            file_hasher.update(chunk);

            let mut offset = 0;
            while offset < chunk.len() {
                let remaining = BLOCK_SIZE - current_block_size;
                let end = std::cmp::min(offset + remaining, chunk.len());
                block_hasher.update(&chunk[offset..end]);
                current_block_size += end - offset;
                if current_block_size == BLOCK_SIZE {
                    blocks.push(encode_hex(&block_hasher.finalize_reset()));
                    current_block_size = 0;
                }
                offset = end;
            }
        }

        if current_block_size > 0 || blocks.is_empty() {
            blocks.push(encode_hex(&block_hasher.finalize()));
        }

        Ok(FileIntegrity {
            algorithm: ALGORITHM.to_string(),
            hash: encode_hex(&file_hasher.finalize()),
            block_size: BLOCK_SIZE,
            blocks,
        })
    }
}