Skip to main content

asar_rust/
integrity.rs

1use sha2::{Digest, Sha256};
2
3const ALGORITHM: &str = "SHA256";
4const BLOCK_SIZE: usize = 4 * 1024 * 1024; // 4MB
5
6/// Encode a byte slice as a lowercase hexadecimal string.
7fn encode_hex(bytes: &[u8]) -> String {
8    let mut s = String::with_capacity(bytes.len() * 2);
9    for byte in bytes {
10        s.push(char::from_digit((byte >> 4) as u32, 16).unwrap());
11        s.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap());
12    }
13    s
14}
15
16/// SHA256-based file integrity information for an ASAR archive entry.
17#[derive(Debug, Clone)]
18pub struct FileIntegrity {
19    pub algorithm: String,
20    pub hash: String,
21    pub block_size: usize,
22    pub blocks: Vec<String>,
23}
24
25impl FileIntegrity {
26    pub fn from_buffer(data: &[u8]) -> Self {
27        let hash = encode_hex(&Sha256::digest(data));
28        let block_count = data.len().div_ceil(BLOCK_SIZE);
29        let mut blocks = Vec::with_capacity(std::cmp::max(block_count, 1));
30
31        if data.is_empty() {
32            blocks.push(encode_hex(&Sha256::digest(data)));
33        } else {
34            let mut offset = 0;
35            while offset < data.len() {
36                let end = std::cmp::min(offset + BLOCK_SIZE, data.len());
37                blocks.push(encode_hex(&Sha256::digest(&data[offset..end])));
38                offset = end;
39            }
40        }
41
42        FileIntegrity {
43            algorithm: ALGORITHM.to_string(),
44            hash,
45            block_size: BLOCK_SIZE,
46            blocks,
47        }
48    }
49
50    pub fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
51        let mut file_hasher = Sha256::new();
52        let mut block_hasher = Sha256::new();
53        let mut current_block_size = 0usize;
54        let mut blocks = Vec::new();
55        let mut buf = vec![0u8; 65536];
56
57        loop {
58            let n = reader.read(&mut buf)?;
59            if n == 0 {
60                break;
61            }
62            let chunk = &buf[..n];
63            file_hasher.update(chunk);
64
65            let mut offset = 0;
66            while offset < chunk.len() {
67                let remaining = BLOCK_SIZE - current_block_size;
68                let end = std::cmp::min(offset + remaining, chunk.len());
69                block_hasher.update(&chunk[offset..end]);
70                current_block_size += end - offset;
71                if current_block_size == BLOCK_SIZE {
72                    blocks.push(encode_hex(&block_hasher.finalize_reset()));
73                    current_block_size = 0;
74                }
75                offset = end;
76            }
77        }
78
79        if current_block_size > 0 || blocks.is_empty() {
80            blocks.push(encode_hex(&block_hasher.finalize()));
81        }
82
83        Ok(FileIntegrity {
84            algorithm: ALGORITHM.to_string(),
85            hash: encode_hex(&file_hasher.finalize()),
86            block_size: BLOCK_SIZE,
87            blocks,
88        })
89    }
90}