use std::io::{Read, Seek, SeekFrom, Write};
use crate::consts::*;
use crate::entry::{encode_label, PartitionEntry};
use crate::error::{Error, Result};
use crate::hash::HashAlgo;
use crate::header::FileHeader;
use crate::table::{compute_table_hash, TableBlockHeader};
#[derive(Debug, Clone, Copy)]
struct BlockInfo {
offset: u64,
capacity: u32,
count: u8,
algo: HashAlgo,
next: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockView {
pub offset: u64,
pub header: TableBlockHeader,
pub entries: Vec<PartitionEntry>,
}
pub struct Container<S: Read + Write + Seek> {
storage: S,
header: FileHeader,
blocks: Vec<BlockInfo>,
data_eof: u64,
default_capacity: u32,
table_hash_algo: HashAlgo,
}
impl<S: Read + Write + Seek> Container<S> {
pub fn create(storage: S) -> Result<Self> {
Self::create_with(storage, 16, HashAlgo::Sha256)
}
pub fn create_with(
mut storage: S,
first_block_capacity: u32,
table_hash_algo: HashAlgo,
) -> Result<Self> {
let cap = first_block_capacity.clamp(1, MAX_ENTRIES_PER_BLOCK);
let header = FileHeader {
version_major: VERSION_MAJOR,
version_minor: VERSION_MINOR,
partition_table_offset: HEADER_SIZE,
};
storage.seek(SeekFrom::Start(0))?;
storage.write_all(&header.to_bytes())?;
let th = compute_table_hash(table_hash_algo, 0, &[]);
let bh = TableBlockHeader {
partition_count: 0,
next_table_offset: 0,
table_hash_algo,
table_hash: th,
};
storage.seek(SeekFrom::Start(HEADER_SIZE))?;
storage.write_all(&bh.to_bytes())?;
let data_eof = HEADER_SIZE + TABLE_HEADER_SIZE + cap as u64 * ENTRY_SIZE;
let blocks = vec![BlockInfo {
offset: HEADER_SIZE,
capacity: cap,
count: 0,
algo: table_hash_algo,
next: 0,
}];
Ok(Self {
storage,
header,
blocks,
data_eof,
default_capacity: cap,
table_hash_algo,
})
}
pub fn open(mut storage: S) -> Result<Self> {
let mut hb = [0u8; 20];
storage.seek(SeekFrom::Start(0))?;
storage.read_exact(&mut hb)?;
let header = FileHeader::from_bytes(&hb)?;
let mut me = Self {
storage,
header,
blocks: Vec::new(),
data_eof: 0,
default_capacity: 16,
table_hash_algo: HashAlgo::Sha256,
};
let mut blocks = Vec::new();
let mut off = header.partition_table_offset;
while off != 0 {
let (h, _entries) = me.read_block(off)?;
blocks.push(BlockInfo {
offset: off,
capacity: h.partition_count as u32, count: h.partition_count,
algo: h.table_hash_algo,
next: h.next_table_offset,
});
off = h.next_table_offset;
}
if let Some(b0) = blocks.first() {
me.table_hash_algo = b0.algo;
}
me.blocks = blocks;
me.data_eof = me.storage.seek(SeekFrom::End(0))?;
Ok(me)
}
pub fn into_storage(self) -> S {
self.storage
}
pub fn header(&self) -> FileHeader {
self.header
}
fn read_at(&mut self, off: u64, buf: &mut [u8]) -> Result<()> {
self.storage.seek(SeekFrom::Start(off))?;
self.storage.read_exact(buf)?;
Ok(())
}
fn write_at(&mut self, off: u64, buf: &[u8]) -> Result<()> {
self.storage.seek(SeekFrom::Start(off))?;
self.storage.write_all(buf)?;
Ok(())
}
fn read_block(&mut self, off: u64) -> Result<(TableBlockHeader, Vec<PartitionEntry>)> {
let mut hb = [0u8; 74];
self.read_at(off, &mut hb)?;
let h = TableBlockHeader::from_bytes(&hb)?;
let mut entries = Vec::with_capacity(h.partition_count as usize);
let mut eb = [0u8; 141];
for i in 0..h.partition_count as u64 {
self.read_at(off + TABLE_HEADER_SIZE + i * ENTRY_SIZE, &mut eb)?;
entries.push(PartitionEntry::from_bytes(&eb)?);
}
Ok((h, entries))
}
fn write_block(
&mut self,
off: u64,
next: u64,
algo: HashAlgo,
entries: &[PartitionEntry],
) -> Result<()> {
let hash = compute_table_hash(algo, next, entries);
let header = TableBlockHeader {
partition_count: entries.len() as u8,
next_table_offset: next,
table_hash_algo: algo,
table_hash: hash,
};
self.write_at(off, &header.to_bytes())?;
let mut buf = Vec::with_capacity(entries.len() * ENTRY_SIZE as usize);
for e in entries {
buf.extend_from_slice(&e.to_bytes());
}
self.write_at(off + TABLE_HEADER_SIZE, &buf)?;
Ok(())
}
pub fn entries(&mut self) -> Result<Vec<PartitionEntry>> {
let mut out = Vec::new();
let mut off = self.header.partition_table_offset;
while off != 0 {
let (h, entries) = self.read_block(off)?;
out.extend(entries);
off = h.next_table_offset;
}
Ok(out)
}
pub fn read_block_at(&mut self, offset: u64) -> Result<BlockView> {
let (header, entries) = self.read_block(offset)?;
Ok(BlockView {
offset,
header,
entries,
})
}
pub fn read_partition_data(&mut self, entry: &PartitionEntry) -> Result<Vec<u8>> {
let mut buf = vec![0u8; entry.used_bytes as usize];
if !buf.is_empty() {
self.read_at(entry.start_offset, &mut buf)?;
}
Ok(buf)
}
fn locate(&mut self, uid: &[u8; UID_SIZE]) -> Result<(u64, usize, PartitionEntry)> {
let mut off = self.header.partition_table_offset;
while off != 0 {
let (h, entries) = self.read_block(off)?;
for (i, e) in entries.iter().enumerate() {
if &e.uid == uid {
return Ok((off, i, e.clone()));
}
}
off = h.next_table_offset;
}
Err(Error::NotFound)
}
fn block_index(&self, offset: u64) -> usize {
self.blocks
.iter()
.position(|b| b.offset == offset)
.expect("block offset must be tracked")
}
pub fn add_partition(
&mut self,
partition_type: u32,
uid: [u8; UID_SIZE],
label: &str,
data: &[u8],
extra_reserve: u64,
data_hash_algo: HashAlgo,
) -> Result<()> {
if partition_type == TYPE_RESERVED {
return Err(Error::ReservedType);
}
if uid == NIL_UID {
return Err(Error::NilUid);
}
if self.entries()?.iter().any(|e| e.uid == uid) {
return Err(Error::DuplicateUid);
}
let label = encode_label(label)?;
let used = data.len() as u64;
let max = used + extra_reserve;
let start = self.data_eof;
if used > 0 {
self.write_at(start, data)?;
}
self.data_eof += max;
let data_hash = data_hash_algo.compute(data);
let entry = PartitionEntry {
partition_type,
uid,
label,
start_offset: start,
max_length: max,
used_bytes: used,
data_hash_algo,
data_hash,
};
let target = self.blocks.iter().position(|b| {
(b.count as u32) < b.capacity && (b.count as u32) < MAX_ENTRIES_PER_BLOCK
});
match target {
Some(i) => {
let boff = self.blocks[i].offset;
let (_h, mut entries) = self.read_block(boff)?;
entries.push(entry);
let algo = self.blocks[i].algo;
let next = self.blocks[i].next;
self.write_block(boff, next, algo, &entries)?;
self.blocks[i].count += 1;
}
None => {
let new_off = self.data_eof;
let cap = self.default_capacity.clamp(1, MAX_ENTRIES_PER_BLOCK);
self.data_eof = new_off + TABLE_HEADER_SIZE + cap as u64 * ENTRY_SIZE;
let algo = self.table_hash_algo;
self.write_block(new_off, 0, algo, &[entry])?;
let tail = *self.blocks.last().expect("at least one block");
let (_h, tentries) = self.read_block(tail.offset)?;
self.write_block(tail.offset, new_off, tail.algo, &tentries)?;
if let Some(last) = self.blocks.last_mut() {
last.next = new_off;
}
self.blocks.push(BlockInfo {
offset: new_off,
capacity: cap,
count: 1,
algo,
next: 0,
});
}
}
Ok(())
}
pub fn update_partition_data(&mut self, uid: &[u8; UID_SIZE], new_data: &[u8]) -> Result<()> {
let (boff, slot, mut entry) = self.locate(uid)?;
if new_data.len() as u64 > entry.max_length {
return Err(Error::DataTooLarge);
}
if !new_data.is_empty() {
self.write_at(entry.start_offset, new_data)?;
}
entry.used_bytes = new_data.len() as u64;
entry.data_hash = entry.data_hash_algo.compute(new_data);
let (_h, mut entries) = self.read_block(boff)?;
entries[slot] = entry;
let bi = self.block_index(boff);
let next = self.blocks[bi].next;
let algo = self.blocks[bi].algo;
self.write_block(boff, next, algo, &entries)?;
Ok(())
}
pub fn remove_partition(&mut self, uid: &[u8; UID_SIZE]) -> Result<()> {
let (boff, slot, _e) = self.locate(uid)?;
let (_h, mut entries) = self.read_block(boff)?;
entries.remove(slot);
let bi = self.block_index(boff);
let next = self.blocks[bi].next;
let algo = self.blocks[bi].algo;
self.write_block(boff, next, algo, &entries)?;
self.blocks[bi].count -= 1;
Ok(())
}
pub fn verify(&mut self) -> Result<()> {
let mut off = self.header.partition_table_offset;
while off != 0 {
let (h, entries) = self.read_block(off)?;
if h.table_hash_algo.verifies() {
let computed = compute_table_hash(h.table_hash_algo, h.next_table_offset, &entries);
let n = h.table_hash_algo.digest_len();
if computed[..n] != h.table_hash[..n] {
return Err(Error::TableHashMismatch);
}
}
for e in &entries {
e.validate()?;
let data = self.read_partition_data(e)?;
if !e.data_hash_algo.verify(&data, &e.data_hash) {
return Err(Error::DataHashMismatch);
}
}
off = h.next_table_offset;
}
Ok(())
}
pub fn compacted_image(&mut self) -> Result<Vec<u8>> {
let mut live: Vec<(PartitionEntry, Vec<u8>)> = Vec::new();
let mut off = self.header.partition_table_offset;
while off != 0 {
let (h, entries) = self.read_block(off)?;
for e in entries {
let data = self.read_partition_data(&e)?;
live.push((e, data));
}
off = h.next_table_offset;
}
let algo = self.table_hash_algo;
let n = live.len();
let num_blocks = if n == 0 { 1 } else { n.div_ceil(255) };
let mut counts = Vec::with_capacity(num_blocks);
let mut rem = n;
for _ in 0..num_blocks {
let c = rem.min(255);
counts.push(c);
rem -= c;
}
let mut block_offsets = Vec::with_capacity(num_blocks);
let mut o = HEADER_SIZE;
for &c in &counts {
block_offsets.push(o);
o += TABLE_HEADER_SIZE + c as u64 * ENTRY_SIZE;
}
let data_start = o;
let mut d = data_start;
for (e, data) in live.iter_mut() {
e.start_offset = d;
e.used_bytes = data.len() as u64;
e.max_length = data.len() as u64;
d += data.len() as u64;
}
let mut image: Vec<u8> = Vec::with_capacity(d as usize);
let header = FileHeader {
version_major: VERSION_MAJOR,
version_minor: VERSION_MINOR,
partition_table_offset: HEADER_SIZE,
};
image.extend_from_slice(&header.to_bytes());
let mut idx = 0usize;
for (bi, &c) in counts.iter().enumerate() {
let next = if bi + 1 < num_blocks {
block_offsets[bi + 1]
} else {
0
};
let slice: Vec<PartitionEntry> =
live[idx..idx + c].iter().map(|(e, _)| e.clone()).collect();
let th = compute_table_hash(algo, next, &slice);
let bh = TableBlockHeader {
partition_count: c as u8,
next_table_offset: next,
table_hash_algo: algo,
table_hash: th,
};
image.extend_from_slice(&bh.to_bytes());
for e in &slice {
image.extend_from_slice(&e.to_bytes());
}
idx += c;
}
debug_assert_eq!(image.len() as u64, data_start);
for (_e, data) in &live {
image.extend_from_slice(data);
}
Ok(image)
}
pub fn compact_into<W: Write>(&mut self, mut out: W) -> Result<()> {
let img = self.compacted_image()?;
out.write_all(&img)?;
Ok(())
}
}