#[cfg(test)]
mod tests;
use super::{ArtifactError, ArtifactIdentity, Sha256Digest, visit_reader};
use sha2::{Digest, Sha256};
use std::{collections::TryReserveError, fmt, io::Read, num::NonZeroUsize};
#[derive(Debug)]
pub enum ChunkDigestError {
Input(ArtifactError),
ChunkLimit {
limit: usize,
},
Allocation(TryReserveError),
}
impl fmt::Display for ChunkDigestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Input(source) => write!(f, "chunk input failed: {source}"),
Self::ChunkLimit { limit } => write!(f, "input exceeds {limit} chunks"),
Self::Allocation(source) => write!(f, "chunk digest allocation failed: {source}"),
}
}
}
impl std::error::Error for ChunkDigestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Input(source) => Some(source),
Self::Allocation(source) => Some(source),
Self::ChunkLimit { .. } => None,
}
}
}
impl From<ArtifactError> for ChunkDigestError {
fn from(source: ArtifactError) -> Self {
Self::Input(source)
}
}
pub fn chunk_digests(
mut reader: impl Read,
chunk_bytes: NonZeroUsize,
max_bytes: u64,
max_chunks: usize,
) -> Result<(Vec<Sha256Digest>, ArtifactIdentity), ChunkDigestError> {
let mut digests = Vec::new();
let mut whole = Sha256::new();
let mut chunk = Sha256::new();
let mut filled = 0;
let bytes = visit_reader::<ChunkDigestError>(&mut reader, max_bytes, |mut buffer| {
whole.update(buffer);
while !buffer.is_empty() {
if filled == 0 {
if digests.len() == max_chunks {
return Err(ChunkDigestError::ChunkLimit { limit: max_chunks });
}
digests
.try_reserve_exact(1)
.map_err(ChunkDigestError::Allocation)?;
}
let count = buffer.len().min(chunk_bytes.get() - filled);
chunk.update(&buffer[..count]);
filled += count;
buffer = &buffer[count..];
if filled == chunk_bytes.get() {
digests.push(Sha256Digest::from_bytes(chunk.finalize_reset().into()));
filled = 0;
}
}
Ok(())
})?;
if filled != 0 {
digests.push(Sha256Digest::from_bytes(chunk.finalize().into()));
}
Ok((
digests,
ArtifactIdentity {
bytes,
sha256: Sha256Digest::from_bytes(whole.finalize().into()),
},
))
}