#![deny(clippy::arithmetic_side_effects)]
use rayon::prelude::*;
use std::fmt;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChunkRange {
pub offset: u64,
pub size: u64,
}
impl ChunkRange {
#[must_use]
pub const fn new(offset: u64, size: u64) -> Self {
Self { offset, size }
}
#[must_use]
pub const fn end(&self) -> Option<u64> {
self.offset.checked_add(self.size)
}
}
impl From<(u64, u64)> for ChunkRange {
fn from((offset, size): (u64, u64)) -> Self {
Self { offset, size }
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LayoutError {
OutOfBounds {
index: usize,
range: ChunkRange,
len: u64,
},
Unsorted {
index: usize,
range: ChunkRange,
previous_offset: u64,
},
Overlap {
index: usize,
range: ChunkRange,
previous_end: u64,
},
FieldOutOfBounds {
range: ChunkRange,
len: u64,
},
}
impl fmt::Display for LayoutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfBounds { index, range, len } => write!(
f,
"output chunk {index} (offset {:#x}, size {:#x}) extends past the end of \
the {len:#x}-byte output",
range.offset, range.size
),
Self::Unsorted {
index,
range,
previous_offset,
} => write!(
f,
"output chunk {index} at offset {:#x} precedes the previous chunk at {previous_offset:#x}",
range.offset
),
Self::Overlap {
index,
range,
previous_end,
} => write!(
f,
"output chunk {index} at offset {:#x} overlaps the previous chunk, which ends at \
{previous_end:#x}",
range.offset
),
Self::FieldOutOfBounds { range, len } => write!(
f,
"{}-byte field at offset {:#x} does not fit in the {len:#x}-byte output",
range.size, range.offset
),
}
}
}
impl std::error::Error for LayoutError {}
pub fn validate_layout(ranges: &[ChunkRange], len: u64) -> Result<(), LayoutError> {
let mut previous = ChunkRange::default();
let mut previous_end = 0u64;
for (index, &range) in ranges.iter().enumerate() {
let end = match range.end() {
Some(end) if end <= len => end,
_ => return Err(LayoutError::OutOfBounds { index, range, len }),
};
if index != 0 {
if range.offset < previous.offset {
return Err(LayoutError::Unsorted {
index,
range,
previous_offset: previous.offset,
});
}
if range.offset < previous_end {
return Err(LayoutError::Overlap {
index,
range,
previous_end,
});
}
}
previous = range;
previous_end = end;
}
Ok(())
}
pub fn split_chunks<'a>(
buf: &'a mut [u8],
ranges: &[ChunkRange],
) -> Result<Vec<&'a mut [u8]>, LayoutError> {
let len = buf.len() as u64;
validate_layout(ranges, len)?;
let mut out = Vec::with_capacity(ranges.len());
let mut rest = buf;
let mut cursor = 0u64;
for (index, &range) in ranges.iter().enumerate() {
let oob = || LayoutError::OutOfBounds { index, range, len };
let gap = range
.offset
.checked_sub(cursor)
.and_then(|gap| usize::try_from(gap).ok())
.ok_or_else(oob)?;
let size = usize::try_from(range.size).map_err(|_| oob())?;
let (_, after_gap) = std::mem::take(&mut rest)
.split_at_mut_checked(gap)
.ok_or_else(oob)?;
let (chunk, after_chunk) = after_gap.split_at_mut_checked(size).ok_or_else(oob)?;
out.push(chunk);
rest = after_chunk;
cursor = range.end().ok_or_else(oob)?;
}
Ok(out)
}
pub fn write_chunks<E, F>(buf: &mut [u8], ranges: &[ChunkRange], write: F) -> Result<(), E>
where
E: From<LayoutError> + Send,
F: Fn(usize, &mut [u8]) -> Result<(), E> + Sync,
{
let chunks = split_chunks(buf, ranges)?;
let results: Vec<Result<(), E>> = chunks
.into_par_iter()
.enumerate()
.map(|(index, chunk)| write(index, chunk))
.collect();
results.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn layout(pairs: &[(u64, u64)]) -> Vec<ChunkRange> {
pairs.iter().copied().map(ChunkRange::from).collect()
}
#[test]
fn splits_with_gaps() {
let mut buf: Vec<u8> = (0..20).collect();
let chunks = split_chunks(&mut buf, &layout(&[(2, 3), (5, 0), (5, 1), (10, 10)])).unwrap();
let lens: Vec<usize> = chunks.iter().map(|c| c.len()).collect();
assert_eq!(lens, [3, 0, 1, 10]);
assert_eq!(chunks[0], [2, 3, 4]);
assert_eq!(chunks[2], [5]);
assert_eq!(chunks[3][0], 10);
}
#[test]
fn empty_layout_and_empty_buffer() {
let mut buf = [0u8; 0];
assert!(split_chunks(&mut buf, &[]).unwrap().is_empty());
assert_eq!(split_chunks(&mut buf, &layout(&[(0, 0)])).unwrap().len(), 1);
}
#[test]
fn rejects_bad_layouts() {
let mut buf = [0u8; 16];
assert!(matches!(
split_chunks(&mut buf, &layout(&[(0, 17)])),
Err(LayoutError::OutOfBounds { index: 0, .. })
));
assert!(matches!(
split_chunks(&mut buf, &layout(&[(0, 4), (17, 0)])),
Err(LayoutError::OutOfBounds { index: 1, .. })
));
assert!(matches!(
split_chunks(&mut buf, &layout(&[(u64::MAX, 2)])),
Err(LayoutError::OutOfBounds { index: 0, .. })
));
assert!(matches!(
split_chunks(&mut buf, &layout(&[(0, 8), (4, 4)])),
Err(LayoutError::Overlap {
index: 1,
previous_end: 8,
..
})
));
assert!(matches!(
split_chunks(&mut buf, &layout(&[(8, 4), (0, 4)])),
Err(LayoutError::Unsorted { index: 1, .. })
));
assert!(matches!(
split_chunks(&mut buf, &layout(&[(0, 8), (3, 0)])),
Err(LayoutError::Overlap { index: 1, .. })
));
}
#[test]
fn errors_display() {
let err = LayoutError::Overlap {
index: 3,
range: ChunkRange::new(0x10, 4),
previous_end: 0x12,
};
assert!(err.to_string().contains("overlaps"));
}
#[test]
fn write_chunks_reports_first_error_in_layout_order() {
#[derive(Debug, PartialEq)]
enum E {
Layout,
Chunk(usize),
}
impl From<LayoutError> for E {
fn from(_: LayoutError) -> Self {
E::Layout
}
}
let mut buf = vec![0u8; 64];
let ranges: Vec<ChunkRange> = (0..16).map(|i| ChunkRange::new(i * 4, 3)).collect();
let result = write_chunks(&mut buf, &ranges, |i, chunk| {
chunk.fill(0xaa);
if i % 5 == 4 { Err(E::Chunk(i)) } else { Ok(()) }
});
assert_eq!(result, Err(E::Chunk(4)));
let result = write_chunks(&mut buf, &layout(&[(0, 65)]), |_, _| Ok::<(), E>(()));
assert_eq!(result, Err(E::Layout));
}
}