#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::convert::TryToUsize;
use crate::error::FormatError;
use crate::source::Source;
pub(crate) const MAX_SPAN_BYTES: u64 = 256 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Span {
start: u64,
end: u64,
}
impl Span {
fn covers(&self, start: u64, end: u64) -> bool {
start >= self.start && end <= self.end
}
fn len(&self) -> u64 {
self.end - self.start
}
}
pub(crate) struct ChunkSpanReader {
spans: Vec<Span>,
held: Option<Span>,
buf: Vec<u8>,
scratch: Vec<u8>,
}
impl ChunkSpanReader {
pub(crate) fn new(chunks: impl IntoIterator<Item = (u64, u32)>) -> Option<Self> {
let mut ranges: Vec<Span> = chunks
.into_iter()
.map(|(address, size)| Span {
start: address,
end: address.saturating_add(u64::from(size)),
})
.collect();
if ranges.len() < 2 {
return None;
}
ranges.sort_unstable_by_key(|s| s.start);
let mut spans: Vec<Span> = Vec::new();
for range in ranges.iter().copied() {
match spans.last_mut() {
Some(last)
if range.start <= last.end
&& range.end.saturating_sub(last.start) <= MAX_SPAN_BYTES =>
{
last.end = last.end.max(range.end);
}
_ => spans.push(range),
}
}
if spans.len() == ranges.len() {
return None;
}
Some(Self {
spans,
held: None,
buf: Vec::new(),
scratch: Vec::new(),
})
}
pub(crate) fn chunk_bytes<S: Source + ?Sized>(
&mut self,
source: &S,
address: u64,
len: usize,
) -> Result<&[u8], FormatError> {
let end = address.saturating_add(len as u64);
if !matches!(self.held, Some(span) if span.covers(address, end)) {
let Some(span) = self.span_containing(address, end) else {
self.scratch = source.read_exact_at(address, len)?;
return Ok(&self.scratch);
};
self.held = None;
self.buf.resize(span.len().to_usize()?, 0);
source.read_at(span.start, &mut self.buf)?;
self.held = Some(span);
}
let span = self.held.expect("held above, or set just above");
let lo = (address - span.start).to_usize()?;
lo.checked_add(len)
.and_then(|hi| self.buf.get(lo..hi))
.ok_or(FormatError::UnexpectedEof {
expected: lo.saturating_add(len),
available: self.buf.len(),
})
}
fn span_containing(&self, start: u64, end: u64) -> Option<Span> {
let idx = match self.spans.binary_search_by_key(&start, |s| s.start) {
Ok(i) => i,
Err(0) => return None,
Err(i) => i - 1,
};
self.spans
.get(idx)
.copied()
.filter(|s| s.covers(start, end))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::BytesSource;
use core::cell::Cell;
struct CountingSource {
bytes: Vec<u8>,
reads: Cell<usize>,
bytes_read: Cell<usize>,
largest: Cell<usize>,
}
impl CountingSource {
fn new(len: usize) -> Self {
Self {
bytes: (0..len).map(|i| i as u8).collect(),
reads: Cell::new(0),
bytes_read: Cell::new(0),
largest: Cell::new(0),
}
}
}
impl Source for CountingSource {
fn len(&self) -> u64 {
self.bytes.len() as u64
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.reads.set(self.reads.get() + 1);
self.bytes_read.set(self.bytes_read.get() + buf.len());
self.largest.set(self.largest.get().max(buf.len()));
BytesSource::new(&self.bytes).read_at(offset, buf)
}
}
fn run(start: u64, size: u32, count: usize) -> Vec<(u64, u32)> {
(0..count)
.map(|i| (start + i as u64 * u64::from(size), size))
.collect()
}
#[test]
fn one_read_serves_a_whole_run_of_chunks() {
let chunks = run(16, 8, 100);
let source = CountingSource::new(4096);
let mut reader = ChunkSpanReader::new(chunks.clone()).expect("a run of chunks coalesces");
for &(address, _) in &chunks {
let bytes = reader.chunk_bytes(&source, address, 8).unwrap();
let expected: Vec<u8> = (address..address + 8).map(|b| b as u8).collect();
assert_eq!(bytes, &expected[..], "chunk at {address}");
}
assert_eq!(source.reads.get(), 1);
assert_eq!(source.bytes_read.get(), 800);
}
#[test]
fn a_long_run_is_split_at_the_budget() {
let chunks = run(0, 8192, 40);
let source = CountingSource::new(40 * 8192);
let mut reader = ChunkSpanReader::new(chunks.clone()).expect("adjacent chunks coalesce");
for &(address, size) in &chunks {
let bytes = reader.chunk_bytes(&source, address, size as usize).unwrap();
assert_eq!(bytes.len(), 8192);
assert_eq!(bytes[0], address as u8);
}
assert_eq!(source.reads.get(), 2, "320 KiB of chunks is two spans");
assert_eq!(source.largest.get(), 32 * 8192, "the first span is full");
assert_eq!(source.bytes_read.get(), 40 * 8192);
}
#[test]
fn chunks_that_are_not_adjacent_are_left_to_the_direct_path() {
let scattered: Vec<(u64, u32)> = (0..16).map(|i| (i * 4096, 8)).collect();
assert!(ChunkSpanReader::new(scattered).is_none());
}
#[test]
fn a_one_byte_gap_is_not_bridged() {
let gapped: Vec<(u64, u32)> = (0..16).map(|i| (i * 33, 32)).collect();
assert!(
ChunkSpanReader::new(gapped).is_none(),
"chunks one byte apart are not adjacent"
);
let mut mixed = run(0, 32, 4);
mixed.extend(run(4 * 32 + 1, 32, 4));
let source = CountingSource::new(1024);
let mut reader = ChunkSpanReader::new(mixed.clone()).expect("the runs coalesce");
for &(address, size) in &mixed {
reader.chunk_bytes(&source, address, size as usize).unwrap();
}
assert_eq!(source.reads.get(), 2, "the gap splits the run");
assert_eq!(
source.bytes_read.get(),
8 * 32,
"the byte between the runs was not read"
);
}
#[test]
fn a_single_chunk_is_left_to_the_direct_path() {
assert!(ChunkSpanReader::new(run(0, 64, 1)).is_none());
}
#[test]
fn a_partly_adjacent_layout_still_coalesces_its_runs() {
let mut chunks = run(0, 32, 4);
chunks.extend(run(100_000, 32, 4));
let source = CountingSource::new(200_000);
let mut reader = ChunkSpanReader::new(chunks.clone()).expect("two runs coalesce");
for &(address, _) in &chunks {
reader.chunk_bytes(&source, address, 32).unwrap();
}
assert_eq!(source.reads.get(), 2);
assert_eq!(source.bytes_read.get(), 8 * 32);
}
#[test]
fn a_chunk_outside_every_span_is_read_directly() {
let chunks = run(0, 32, 8);
let source = CountingSource::new(200_000);
let mut reader = ChunkSpanReader::new(chunks).expect("adjacent chunks coalesce");
reader.chunk_bytes(&source, 0, 32).unwrap();
assert_eq!(source.reads.get(), 1);
let stray = reader.chunk_bytes(&source, 100_000, 4).unwrap().to_vec();
assert_eq!(stray, vec![160u8, 161, 162, 163]);
assert_eq!(source.reads.get(), 2);
reader.chunk_bytes(&source, 224, 32).unwrap();
assert_eq!(source.reads.get(), 2);
}
#[test]
fn a_chunk_larger_than_the_budget_is_read_on_its_own() {
let big = MAX_SPAN_BYTES + 40 * 1024;
let big_usize = big.to_usize().unwrap();
let mut chunks = vec![(0u64, u32::try_from(big).unwrap())];
chunks.extend(run(big, 64, 8));
let source = CountingSource::new(big_usize + 8 * 64);
let mut reader = ChunkSpanReader::new(chunks.clone()).expect("the small chunks coalesce");
assert_eq!(
reader.chunk_bytes(&source, 0, big_usize).unwrap().len(),
big_usize
);
assert_eq!(source.largest.get(), big_usize, "the big chunk read alone");
for &(address, _) in &chunks[1..] {
reader.chunk_bytes(&source, address, 64).unwrap();
}
assert_eq!(source.reads.get(), 2);
assert_eq!(source.bytes_read.get(), big_usize + 8 * 64);
}
#[test]
fn chunks_are_served_whatever_order_they_are_asked_for() {
let chunks = run(64, 16, 8);
let source = CountingSource::new(1024);
let mut reader = ChunkSpanReader::new(chunks.clone()).expect("adjacent chunks coalesce");
for &(address, _) in chunks.iter().rev() {
let bytes = reader.chunk_bytes(&source, address, 16).unwrap();
assert_eq!(bytes[0], address as u8);
}
assert_eq!(source.reads.get(), 1, "one span serves both directions");
}
#[test]
fn a_short_span_reads_its_own_length_after_a_longer_one() {
let mut chunks = run(0, 64, 8); chunks.extend(run(100_000, 8, 2)); let source = CountingSource::new(200_000);
let mut reader = ChunkSpanReader::new(chunks).expect("two runs coalesce");
reader.chunk_bytes(&source, 0, 64).unwrap();
let before = source.bytes_read.get();
let short = reader.chunk_bytes(&source, 100_000, 8).unwrap();
assert_eq!(short, &[160u8, 161, 162, 163, 164, 165, 166, 167]);
assert_eq!(source.bytes_read.get() - before, 16, "the short span's own");
}
}