ic-host-tools 0.1.10

Shared Internet Computer host artifact tooling
Documentation
use super::*;
use flate2::{Compression, GzBuilder};
use std::io::Write as _;

fn gzip(bytes: &[u8]) -> Vec<u8> {
    let mut encoder = GzBuilder::new()
        .mtime(0)
        .write(Vec::new(), Compression::fast());
    encoder.write_all(bytes).unwrap();
    encoder.finish().unwrap()
}

#[test]
fn inclusive_compressed_and_decoded_limits_preserve_input() {
    let payload = vec![42; 40_001];
    let compressed = gzip(&payload);
    let original = compressed.clone();
    assert_eq!(
        decode_gzip(&compressed, compressed.len(), payload.len()).unwrap(),
        payload
    );
    assert!(
        matches!(decode_gzip(&compressed, compressed.len() - 1, payload.len()),
        Err(GzipError::InputLimit { actual, limit })
            if actual == compressed.len() && limit == compressed.len() - 1)
    );
    assert!(matches!(
        decode_gzip(&compressed, compressed.len(), payload.len() - 1),
        Err(GzipError::Decode(ArtifactError::LimitExceeded {
            limit: 40_000
        }))
    ));
    assert_eq!(compressed, original);
}

#[test]
fn empty_payloads_and_predecode_input_limits_are_distinct() {
    let compressed = gzip(b"");
    assert_eq!(
        decode_gzip(&compressed, compressed.len(), 0).unwrap(),
        [] as [u8; 0]
    );
    assert!(matches!(
        decode_gzip(&[], 0, 0),
        Err(GzipError::Decode(ArtifactError::Io(_)))
    ));
    assert!(matches!(
        decode_gzip(b"invalid gzip", 0, 0),
        Err(GzipError::InputLimit {
            actual: 12,
            limit: 0
        })
    ));
    let nonempty = gzip(b"a");
    assert!(matches!(
        decode_gzip(&nonempty, nonempty.len(), 0),
        Err(GzipError::Decode(ArtifactError::LimitExceeded { limit: 0 }))
    ));
}

#[test]
fn truncated_headers_payloads_and_footers_never_return_partial_bytes() {
    let compressed = gzip(b"retained artifact payload");
    for end in 0..compressed.len() {
        assert!(matches!(
            decode_gzip(&compressed[..end], compressed.len(), 1024),
            Err(GzipError::Decode(ArtifactError::Io(_)))
        ));
    }
}

#[test]
fn malformed_headers_crc_and_length_fail_with_structured_errors() {
    let compressed = gzip(b"retained artifact payload");
    for index in [0, 2, compressed.len() - 8, compressed.len() - 4] {
        let mut corrupt = compressed.clone();
        corrupt[index] ^= 1;
        assert!(matches!(
            decode_gzip(&corrupt, corrupt.len(), 1024),
            Err(GzipError::Decode(ArtifactError::Io(_)))
        ));
    }
}

#[test]
fn trailing_bytes_and_concatenated_members_are_rejected() {
    let compressed = gzip(b"payload");
    for tail in [vec![0], b"unrelated".to_vec(), gzip(b""), gzip(b"second")] {
        let mut joined = compressed.clone();
        joined.extend_from_slice(&tail);
        assert!(matches!(
            decode_gzip(&joined, joined.len(), 1024),
            Err(GzipError::TrailingData)
        ));
    }
    let mut corrupt = compressed;
    let crc = corrupt.len() - 8;
    corrupt[crc] ^= 1;
    corrupt.push(0);
    assert!(matches!(
        decode_gzip(&corrupt, corrupt.len(), 1024),
        Err(GzipError::Decode(ArtifactError::Io(_)))
    ));
}

#[test]
fn inflation_is_bounded_independently_of_small_compressed_input() {
    let compressed = gzip(&vec![0; 1_000_000]);
    assert!(matches!(
        decode_gzip(&compressed, compressed.len(), 16),
        Err(GzipError::Decode(ArtifactError::LimitExceeded {
            limit: 16
        }))
    ));
}

#[test]
fn optional_header_fields_count_against_the_complete_input_allowance() {
    let mut encoder = GzBuilder::new()
        .mtime(0)
        .filename("fixture.wasm")
        .comment("saved artifact")
        .extra(vec![1, 2, 3])
        .write(Vec::new(), Compression::fast());
    encoder.write_all(b"payload").unwrap();
    let compressed = encoder.finish().unwrap();
    assert_eq!(
        decode_gzip(&compressed, compressed.len(), 7).unwrap(),
        b"payload"
    );
    assert!(matches!(
        decode_gzip(&compressed, compressed.len() - 1, 7),
        Err(GzipError::InputLimit { .. })
    ));
}