ic-host-artifacts 0.4.5

Bounded host artifact streams, identities, archives and Wasm inspection
Documentation
use super::*;
use std::io::{self, Cursor};

struct Fragmented {
    input: Cursor<Vec<u8>>,
    fragment: usize,
    interrupt: bool,
}

impl Read for Fragmented {
    fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
        if std::mem::replace(&mut self.interrupt, false) {
            return Err(io::ErrorKind::Interrupted.into());
        }
        self.interrupt = true;
        let count = buffer.len().min(self.fragment);
        self.input.read(&mut buffer[..count])
    }
}

#[test]
fn chunk_boundaries_and_whole_identity_ignore_read_fragmentation() {
    let input = (0_u8..251).cycle().take(40_001).collect::<Vec<_>>();
    for chunk_size in [1, 3, 16_384, 16_385, input.len(), usize::MAX] {
        let expected = input
            .chunks(chunk_size)
            .map(Sha256Digest::compute)
            .collect::<Vec<_>>();
        for fragment in [1, 17, 16_384] {
            let reader = Fragmented {
                input: Cursor::new(input.clone()),
                fragment,
                interrupt: true,
            };
            let (chunks, whole) = chunk_digests(
                reader,
                NonZeroUsize::new(chunk_size).unwrap(),
                input.len() as u64,
                expected.len(),
            )
            .unwrap();
            assert_eq!(chunks, expected);
            assert_eq!(
                whole,
                ArtifactIdentity {
                    bytes: input.len() as u64,
                    sha256: Sha256Digest::compute(&input)
                }
            );
        }
    }
}

#[test]
fn empty_input_and_exact_multiples_have_no_empty_final_chunk() {
    let size = NonZeroUsize::new(3).unwrap();
    let (chunks, whole) = chunk_digests(b"".as_slice(), size, 0, 0).unwrap();
    assert_eq!(chunks, [] as [Sha256Digest; 0]);
    assert_eq!(
        whole,
        ArtifactIdentity {
            bytes: 0,
            sha256: Sha256Digest::compute(b"")
        }
    );
    for input in [b"abc".as_slice(), b"abcdef", b"abcdefg"] {
        let expected = input
            .chunks(3)
            .map(Sha256Digest::compute)
            .collect::<Vec<_>>();
        let (chunks, whole) =
            chunk_digests(input, size, input.len() as u64, expected.len()).unwrap();
        assert_eq!(chunks, expected);
        assert_eq!(whole.sha256, Sha256Digest::compute(input));
    }
}

#[test]
fn byte_and_chunk_budgets_reject_without_partial_identities() {
    let size = NonZeroUsize::new(3).unwrap();
    let mut reader = Cursor::new(b"abcdefg");
    assert!(matches!(
        chunk_digests(&mut reader, size, 6, 3),
        Err(ChunkDigestError::Input(ArtifactError::LimitExceeded {
            limit: 6
        }))
    ));
    assert_eq!(reader.position(), 7);
    for (input, count) in [
        (b"a".as_slice(), 0),
        (b"abcd".as_slice(), 1),
        (b"abcdefg".as_slice(), 2),
    ] {
        assert!(matches!(chunk_digests(input, size, 100, count),
            Err(ChunkDigestError::ChunkLimit { limit }) if limit == count));
    }
}

#[test]
fn source_errors_after_complete_chunks_remain_typed() {
    struct InvalidReader;
    impl Read for InvalidReader {
        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
            Ok(buffer.len() + 1)
        }
    }
    struct FailingReader(bool);
    impl Read for FailingReader {
        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
            if std::mem::replace(&mut self.0, false) {
                buffer[..3].copy_from_slice(b"abc");
                Ok(3)
            } else {
                Err(io::Error::from_raw_os_error(13))
            }
        }
    }
    let error =
        chunk_digests(FailingReader(true), NonZeroUsize::new(3).unwrap(), 100, 4).unwrap_err();
    assert!(matches!(error,
        ChunkDigestError::Input(ArtifactError::Io(ref source)) if source.raw_os_error() == Some(13)));
    assert!(
        std::error::Error::source(&error)
            .unwrap()
            .is::<ArtifactError>()
    );

    assert!(
        matches!(chunk_digests(InvalidReader, NonZeroUsize::new(3).unwrap(), 100, 4),
        Err(ChunkDigestError::Input(ArtifactError::Io(source))) if source.kind() == io::ErrorKind::InvalidData)
    );
}