use std::io::{self, Read, Seek, SeekFrom};
pub enum CompactSection {
PreBuilt(Vec<u8>),
MappedBlocks {
old_blocks: Vec<u64>,
},
Zeros(u64),
}
impl CompactSection {
fn size(&self, block_size: usize) -> u64 {
match self {
CompactSection::PreBuilt(data) => data.len() as u64,
CompactSection::MappedBlocks { old_blocks } => {
old_blocks.len() as u64 * block_size as u64
}
CompactSection::Zeros(len) => *len,
}
}
}
pub struct CompactLayout {
pub sections: Vec<CompactSection>,
pub block_size: usize,
pub source_data_start: u64,
pub source_partition_offset: u64,
}
impl CompactLayout {
pub fn total_size(&self) -> u64 {
self.sections.iter().map(|s| s.size(self.block_size)).sum()
}
}
struct SectionSpan {
start: u64,
end: u64,
}
pub struct CompactStreamReader<R: Read + Seek> {
source: R,
layout: CompactLayout,
spans: Vec<SectionSpan>,
position: u64,
total_size: u64,
block_buf: Vec<u8>,
cached_block: Option<u64>,
}
impl<R: Read + Seek> CompactStreamReader<R> {
pub fn new(source: R, layout: CompactLayout) -> Self {
let total_size = layout.total_size();
let block_size = layout.block_size;
let mut spans = Vec::with_capacity(layout.sections.len());
let mut offset = 0u64;
for section in &layout.sections {
let size = section.size(block_size);
spans.push(SectionSpan {
start: offset,
end: offset + size,
});
offset += size;
}
Self {
source,
layout,
spans,
position: 0,
total_size,
block_buf: vec![0u8; block_size],
cached_block: None,
}
}
pub fn total_size(&self) -> u64 {
self.total_size
}
fn load_block(&mut self, block_num: u64) -> io::Result<()> {
if self.cached_block == Some(block_num) {
return Ok(());
}
let offset = self.layout.source_partition_offset
+ self.layout.source_data_start
+ block_num * self.layout.block_size as u64;
self.source.seek(SeekFrom::Start(offset))?;
self.source.read_exact(&mut self.block_buf)?;
self.cached_block = Some(block_num);
Ok(())
}
fn find_section(&self, pos: u64) -> Option<(usize, u64)> {
for (i, span) in self.spans.iter().enumerate() {
if pos < span.end {
return Some((i, pos - span.start));
}
}
None
}
}
impl<R: Read + Seek> Read for CompactStreamReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.position >= self.total_size {
return Ok(0);
}
let to_read = buf.len().min((self.total_size - self.position) as usize);
if to_read == 0 {
return Ok(0);
}
let mut filled = 0;
while filled < to_read {
let current_pos = self.position + filled as u64;
if current_pos >= self.total_size {
break;
}
let (section_idx, offset_in_section) = match self.find_section(current_pos) {
Some(v) => v,
None => break,
};
let section = &self.layout.sections[section_idx];
let section_size = section.size(self.layout.block_size);
let remaining_in_section = (section_size - offset_in_section) as usize;
match section {
CompactSection::PreBuilt(data) => {
let off = offset_in_section as usize;
let copy_len = (to_read - filled).min(remaining_in_section);
buf[filled..filled + copy_len].copy_from_slice(&data[off..off + copy_len]);
filled += copy_len;
}
CompactSection::MappedBlocks { old_blocks } => {
let block_size = self.layout.block_size;
let block_idx = offset_in_section as usize / block_size;
let offset_in_block = offset_in_section as usize % block_size;
if block_idx >= old_blocks.len() {
let copy_len = (to_read - filled).min(remaining_in_section);
buf[filled..filled + copy_len].fill(0);
filled += copy_len;
continue;
}
let source_block = old_blocks[block_idx];
self.load_block(source_block)?;
let remaining_in_block = block_size - offset_in_block;
let copy_len = (to_read - filled).min(remaining_in_block);
buf[filled..filled + copy_len].copy_from_slice(
&self.block_buf[offset_in_block..offset_in_block + copy_len],
);
filled += copy_len;
}
CompactSection::Zeros(_) => {
let copy_len = (to_read - filled).min(remaining_in_section);
buf[filled..filled + copy_len].fill(0);
filled += copy_len;
}
}
}
self.position += filled as u64;
Ok(filled)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_prebuilt_section() {
let layout = CompactLayout {
sections: vec![CompactSection::PreBuilt(vec![0xAA; 512])],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let source = Cursor::new(vec![0u8; 0]);
let mut reader = CompactStreamReader::new(source, layout);
assert_eq!(reader.total_size(), 512);
let mut buf = vec![0u8; 512];
reader.read_exact(&mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0xAA));
}
#[test]
fn test_zeros_section() {
let layout = CompactLayout {
sections: vec![CompactSection::Zeros(1024)],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let source = Cursor::new(vec![0u8; 0]);
let mut reader = CompactStreamReader::new(source, layout);
assert_eq!(reader.total_size(), 1024);
let mut buf = vec![0xFFu8; 1024];
reader.read_exact(&mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0x00));
}
#[test]
fn test_mapped_blocks() {
let mut source_data = vec![0u8; 2048];
source_data[512..1024].fill(0xBB); source_data[1536..2048].fill(0xDD); let layout = CompactLayout {
sections: vec![CompactSection::MappedBlocks {
old_blocks: vec![3, 1],
}],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
assert_eq!(reader.total_size(), 1024);
let mut buf = vec![0u8; 1024];
reader.read_exact(&mut buf).unwrap();
assert!(buf[..512].iter().all(|&b| b == 0xDD)); assert!(buf[512..].iter().all(|&b| b == 0xBB)); }
#[test]
fn test_cross_section_read() {
let layout = CompactLayout {
sections: vec![
CompactSection::PreBuilt(vec![0xFF; 4]),
CompactSection::Zeros(4),
],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
assert_eq!(reader.total_size(), 8);
let mut buf = vec![0u8; 8];
reader.read_exact(&mut buf).unwrap();
assert_eq!(&buf, &[0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]);
}
#[test]
fn test_multi_section_layout() {
let mut source_data = vec![0u8; 512];
source_data.fill(0xCC);
let layout = CompactLayout {
sections: vec![
CompactSection::PreBuilt(vec![0xAA; 512]),
CompactSection::MappedBlocks {
old_blocks: vec![0],
},
CompactSection::Zeros(512),
],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
assert_eq!(reader.total_size(), 1536);
let mut buf = vec![0u8; 1536];
reader.read_exact(&mut buf).unwrap();
assert!(buf[..512].iter().all(|&b| b == 0xAA)); assert!(buf[512..1024].iter().all(|&b| b == 0xCC)); assert!(buf[1024..].iter().all(|&b| b == 0x00)); }
#[test]
fn test_partial_read() {
let layout = CompactLayout {
sections: vec![CompactSection::PreBuilt(vec![0xAA; 1024])],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
let mut all = Vec::new();
let mut buf = [0u8; 100];
loop {
let n = reader.read(&mut buf).unwrap();
if n == 0 {
break;
}
all.extend_from_slice(&buf[..n]);
}
assert_eq!(all.len(), 1024);
assert!(all.iter().all(|&b| b == 0xAA));
}
#[test]
fn test_read_past_end() {
let layout = CompactLayout {
sections: vec![CompactSection::PreBuilt(vec![0xAA; 10])],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
let mut buf = vec![0u8; 20];
assert!(reader.read_exact(&mut buf).is_err());
}
#[test]
fn test_empty_layout() {
let layout = CompactLayout {
sections: vec![],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
assert_eq!(reader.total_size(), 0);
let mut buf = [0u8; 1];
assert_eq!(reader.read(&mut buf).unwrap(), 0);
}
#[test]
fn test_source_data_start_offset() {
let mut source_data = vec![0u8; 2048];
source_data[1024..1536].fill(0xEE); let layout = CompactLayout {
sections: vec![CompactSection::MappedBlocks {
old_blocks: vec![0],
}],
block_size: 512,
source_data_start: 1024,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
let mut buf = vec![0u8; 512];
reader.read_exact(&mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0xEE));
}
#[test]
fn test_partition_offset() {
let mut source_data = vec![0u8; 1024];
source_data[512..1024].fill(0xDD);
let layout = CompactLayout {
sections: vec![CompactSection::MappedBlocks {
old_blocks: vec![0],
}],
block_size: 512,
source_data_start: 0,
source_partition_offset: 512,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
let mut buf = vec![0u8; 512];
reader.read_exact(&mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0xDD));
}
#[test]
fn test_cross_block_read_in_mapped_section() {
let mut source_data = vec![0u8; 1024];
source_data[..512].fill(0xAA); source_data[512..1024].fill(0xBB); let layout = CompactLayout {
sections: vec![CompactSection::MappedBlocks {
old_blocks: vec![0, 1],
}],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
let mut buf1 = vec![0u8; 256];
reader.read_exact(&mut buf1).unwrap();
assert!(buf1.iter().all(|&b| b == 0xAA));
let mut buf2 = vec![0u8; 512];
reader.read_exact(&mut buf2).unwrap();
assert!(buf2[..256].iter().all(|&b| b == 0xAA));
assert!(buf2[256..].iter().all(|&b| b == 0xBB));
let mut buf3 = vec![0u8; 256];
reader.read_exact(&mut buf3).unwrap();
assert!(buf3.iter().all(|&b| b == 0xBB));
}
#[test]
fn test_total_size() {
let layout = CompactLayout {
sections: vec![
CompactSection::PreBuilt(vec![0; 100]),
CompactSection::MappedBlocks {
old_blocks: vec![0, 1, 2],
},
CompactSection::Zeros(200),
],
block_size: 4096,
source_data_start: 0,
source_partition_offset: 0,
};
assert_eq!(layout.total_size(), 100 + 3 * 4096 + 200);
}
#[test]
fn test_block_cache_reuse() {
let mut source_data = vec![0u8; 512];
source_data.fill(0xAB);
let layout = CompactLayout {
sections: vec![CompactSection::MappedBlocks {
old_blocks: vec![0, 0],
}],
block_size: 512,
source_data_start: 0,
source_partition_offset: 0,
};
let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
let mut buf = vec![0u8; 1024];
reader.read_exact(&mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0xAB));
}
}