visi-core 0.2.19

Embeddable spreadsheet engine: Excel formula compilation and evaluation, dependency-tracked recalculation, and .xlsx import/export
Documentation
pub fn decompress(data: &[u8]) -> Result<Vec<u8>, String> {
    let sig = *data
        .first()
        .ok_or("empty compressed container: missing signature byte")?;
    if sig != 0x01 {
        return Err(format!(
            "bad compressed container signature: expected 0x01, got 0x{sig:02X}"
        ));
    }
    let mut out = Vec::new();
    let mut pos = 1;
    while pos < data.len() {
        let header_bytes = data
            .get(pos..pos + 2)
            .ok_or("truncated compressed container: chunk header cut off")?;
        let header = u16::from_le_bytes([header_bytes[0], header_bytes[1]]);
        let chunk_size = (header & 0x0FFF) as usize + 3;
        let signature = (header >> 12) & 0b111;
        if signature != 0b011 {
            return Err(format!(
                "bad chunk signature 0b{signature:03b} at pos {pos}"
            ));
        }
        let compressed_flag = (header >> 15) & 1;
        let chunk_start = pos + 2;
        let chunk_data_end = pos + chunk_size;
        let chunk_data = data
            .get(chunk_start..chunk_data_end.min(data.len()))
            .ok_or("truncated compressed container: chunk body cut off")?;

        if compressed_flag == 0 {
            out.extend_from_slice(chunk_data);
        } else {
            decompress_chunk(chunk_data, &mut out)?;
        }
        pos = chunk_data_end;
    }
    Ok(out)
}

fn decompress_chunk(chunk_data: &[u8], out: &mut Vec<u8>) -> Result<(), String> {
    let chunk_start_out = out.len();
    let mut i = 0;
    while i < chunk_data.len() {
        let flag_byte = chunk_data[i];
        i += 1;
        for bit in 0..8 {
            if i >= chunk_data.len() {
                break;
            }
            let is_copy_token = (flag_byte >> bit) & 1 == 1;
            if !is_copy_token {
                out.push(chunk_data[i]);
                i += 1;
            } else {
                let token_bytes = chunk_data
                    .get(i..i + 2)
                    .ok_or("truncated copy token in compressed chunk")?;
                let token = u16::from_le_bytes([token_bytes[0], token_bytes[1]]);
                i += 2;
                let decompressed_current = out.len() - chunk_start_out;
                let bit_count = bit_count_for(decompressed_current);
                let length_mask: u16 = 0xFFFF >> bit_count;
                let offset_mask: u16 = !length_mask;
                let length = (token & length_mask) as usize + 3;
                let offset = (((token & offset_mask) >> (16 - bit_count)) + 1) as usize;
                let copy_start = out
                    .len()
                    .checked_sub(offset)
                    .ok_or("copy token offset points before the start of the output")?;
                for k in 0..length {
                    let b = out[copy_start + k];
                    out.push(b);
                }
            }
        }
    }
    Ok(())
}

fn bit_count_for(decompressed_current: usize) -> u32 {
    let mut bit_count = 0u32;
    while (1usize << bit_count) < decompressed_current {
        bit_count += 1;
    }
    bit_count.clamp(4, 12)
}

pub fn compress(data: &[u8]) -> Result<Vec<u8>, String> {
    let mut out = vec![0x01u8];
    let mut remaining = data;
    while !remaining.is_empty() {
        let take = remaining.len().min(4096);
        let (chunk, rest) = remaining.split_at(take);
        let body = compress_chunk(chunk);
        let total_size = 2 + body.len();
        if total_size > 4098 {
            return Err(format!(
                "VBA source can't be encoded: a {}-byte block has too few repeated \
                 sequences to fit Excel's compressed module format (needs {} bytes, \
                 budget is 4096)",
                chunk.len(),
                body.len()
            ));
        }
        let chunk_size_field = (total_size - 3) as u16;
        let header: u16 = (1 << 15) | (0b011 << 12) | chunk_size_field;
        out.extend_from_slice(&header.to_le_bytes());
        out.extend_from_slice(&body);
        remaining = rest;
    }
    Ok(out)
}

fn compress_chunk(input: &[u8]) -> Vec<u8> {
    use std::collections::{HashMap, VecDeque};
    let mut body = Vec::new();
    let mut hash_index: HashMap<[u8; 3], VecDeque<usize>> = HashMap::new();
    let mut i = 0;
    while i < input.len() {
        let mut flag_byte = 0u8;
        let mut group_body = Vec::new();
        for bit in 0..8 {
            if i >= input.len() {
                break;
            }
            let decompressed_current = i;
            let bit_count = bit_count_for(decompressed_current);
            let length_mask: u16 = 0xFFFF >> bit_count;
            let max_length = length_mask as usize + 3;
            let max_offset = 1usize << bit_count;

            let best = if i + 3 <= input.len() {
                find_best_match(input, i, max_offset, max_length, &hash_index)
            } else {
                None
            };

            if let Some((offset, length)) = best {
                let token: u16 =
                    (((offset - 1) as u16) << (16 - bit_count)) | ((length - 3) as u16);
                group_body.extend_from_slice(&token.to_le_bytes());
                flag_byte |= 1 << bit;
                for k in i..(i + length).min(input.len()) {
                    if k + 3 <= input.len() {
                        let key = [input[k], input[k + 1], input[k + 2]];
                        let entries = hash_index.entry(key).or_default();
                        entries.push_back(k);
                        if entries.len() > 64 {
                            entries.pop_front();
                        }
                    }
                }
                i += length;
            } else {
                group_body.push(input[i]);
                if i + 3 <= input.len() {
                    let key = [input[i], input[i + 1], input[i + 2]];
                    let entries = hash_index.entry(key).or_default();
                    entries.push_back(i);
                    if entries.len() > 64 {
                        entries.pop_front();
                    }
                }
                i += 1;
            }
        }
        body.push(flag_byte);
        body.extend_from_slice(&group_body);
    }
    body
}

fn find_best_match(
    input: &[u8],
    i: usize,
    max_offset: usize,
    max_length: usize,
    hash_index: &std::collections::HashMap<[u8; 3], std::collections::VecDeque<usize>>,
) -> Option<(usize, usize)> {
    let key = [input[i], input[i + 1], input[i + 2]];
    let candidates = hash_index.get(&key)?;
    let min_j = i.saturating_sub(max_offset);
    let mut best_offset = 0usize;
    let mut best_length = 0usize;
    for &j in candidates.iter().rev() {
        if j < min_j || j >= i {
            continue;
        }
        let mut length = 0;
        while i + length < input.len()
            && length < max_length
            && input[j + length] == input[i + length]
        {
            length += 1;
        }
        if length > best_length {
            best_length = length;
            best_offset = i - j;
        }
    }
    if best_length >= 3 {
        Some((best_offset, best_length))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn roundtrip_when_compressible(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
            if let Ok(compressed) = compress(&data) {
                let decompressed = decompress(&compressed).unwrap();
                prop_assert_eq!(decompressed, data);
            }
        }

        #[test]
        fn decompress_never_panics_or_ooms(data in proptest::collection::vec(any::<u8>(), 0..4096)) {
            let _ = decompress(&data);
        }
    }

    #[test]
    fn roundtrip_multichunk_boundaries() {
        for len in [1, 3640, 3641, 4095, 4096, 4097, 8192, 8193, 12000] {
            let original: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
            let compressed = compress(&original).unwrap();
            let decompressed = decompress(&compressed).unwrap();
            assert_eq!(decompressed, original, "length {len} failed");
        }
    }

    #[test]
    fn roundtrip_repetitive_text() {
        let original = b"Sub Test()\n    Dim x As Integer\n    x = 1\nEnd Sub\n".repeat(200);
        let compressed = compress(&original).unwrap();
        let decompressed = decompress(&compressed).unwrap();
        assert_eq!(decompressed, original);
        assert!(compressed.len() < original.len());
    }

    #[test]
    fn roundtrip_empty() {
        let compressed = compress(b"").unwrap();
        let decompressed = decompress(&compressed).unwrap();
        assert_eq!(decompressed, b"");
    }

    #[test]
    fn compress_errors_instead_of_panicking_on_incompressible_chunk() {
        let mut state = 0x2463_9A11u32;
        let original: Vec<u8> = (0..4096)
            .map(|_| {
                state ^= state << 13;
                state ^= state >> 17;
                state ^= state << 5;
                state as u8
            })
            .collect();
        assert!(compress(&original).is_err());
    }

    #[test]
    fn decompress_errors_instead_of_panicking_on_malformed_input() {
        assert!(decompress(&[]).is_err());
        assert!(decompress(&[0x02]).is_err());
        assert!(decompress(&[0x01, 0x00]).is_err());
        assert!(decompress(&[0x01, 0x00, 0x00]).is_err());
        let mut malformed = vec![0x01u8];
        let body = [0x01u8, 0xFF, 0xFF];
        let total_size = 2 + body.len();
        let header: u16 = (1 << 15) | (0b011 << 12) | (total_size - 3) as u16;
        malformed.extend_from_slice(&header.to_le_bytes());
        malformed.extend_from_slice(&body);
        assert!(decompress(&malformed).is_err());
    }
}