use crate::format::checksum::checksum_metadata;
use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
pub const EAHD_SIGNATURE: [u8; 4] = *b"EAHD";
pub const EAIB_SIGNATURE: [u8; 4] = *b"EAIB";
pub const EADB_SIGNATURE: [u8; 4] = *b"EADB";
pub const EA_VERSION: u8 = 0;
pub const EA_CLS_CHUNK: u8 = 0;
pub const EA_CLS_FILT_CHUNK: u8 = 1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FilteredChunkEntry {
pub addr: u64,
pub nbytes: u64,
pub filter_mask: u32,
}
impl FilteredChunkEntry {
pub fn undef() -> Self {
Self {
addr: UNDEF_ADDR,
nbytes: 0,
filter_mask: 0,
}
}
pub fn is_undef(&self) -> bool {
self.addr == UNDEF_ADDR
}
pub fn raw_size(sizeof_addr: u8, chunk_size_len: u8) -> u8 {
sizeof_addr + chunk_size_len + 4
}
pub fn encode(&self, sizeof_addr: usize, chunk_size_len: usize) -> Vec<u8> {
let mut buf = Vec::with_capacity(sizeof_addr + chunk_size_len + 4);
buf.extend_from_slice(&self.addr.to_le_bytes()[..sizeof_addr]);
buf.extend_from_slice(&self.nbytes.to_le_bytes()[..chunk_size_len]);
buf.extend_from_slice(&self.filter_mask.to_le_bytes());
buf
}
pub fn decode(buf: &[u8], sizeof_addr: usize, chunk_size_len: usize) -> Self {
let addr = read_addr(buf, sizeof_addr);
let nbytes = read_size(&buf[sizeof_addr..], chunk_size_len);
let off = sizeof_addr + chunk_size_len;
let filter_mask = u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]);
Self {
addr,
nbytes,
filter_mask,
}
}
}
pub fn compute_chunk_size_len(uncompressed_chunk_bytes: u64) -> u8 {
if uncompressed_chunk_bytes == 0 {
return 1;
}
let log2 = 63 - uncompressed_chunk_bytes.leading_zeros();
let len = 1 + (log2 + 8) / 8;
std::cmp::min(len, 8) as u8
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtensibleArrayHeader {
pub class_id: u8,
pub raw_elmt_size: u8,
pub max_nelmts_bits: u8,
pub idx_blk_elmts: u8,
pub data_blk_min_elmts: u8,
pub sup_blk_min_data_ptrs: u8,
pub max_dblk_page_nelmts_bits: u8,
pub num_sblks_created: u64,
pub size_sblks_created: u64,
pub num_dblks_created: u64,
pub size_dblks_created: u64,
pub max_idx_set: u64,
pub num_elmts_realized: u64,
pub idx_blk_addr: u64,
}
impl ExtensibleArrayHeader {
pub fn new_for_chunks(ctx: &FormatContext) -> Self {
Self {
class_id: EA_CLS_CHUNK,
raw_elmt_size: ctx.sizeof_addr,
max_nelmts_bits: 32,
idx_blk_elmts: 4,
data_blk_min_elmts: 16,
sup_blk_min_data_ptrs: 4,
max_dblk_page_nelmts_bits: 10,
num_sblks_created: 0,
size_sblks_created: 0,
num_dblks_created: 0,
size_dblks_created: 0,
max_idx_set: 0,
num_elmts_realized: 0,
idx_blk_addr: UNDEF_ADDR,
}
}
pub fn new_for_filtered_chunks(ctx: &FormatContext, chunk_size_len: u8) -> Self {
Self {
class_id: EA_CLS_FILT_CHUNK,
raw_elmt_size: FilteredChunkEntry::raw_size(ctx.sizeof_addr, chunk_size_len),
max_nelmts_bits: 32,
idx_blk_elmts: 4,
data_blk_min_elmts: 16,
sup_blk_min_data_ptrs: 4,
max_dblk_page_nelmts_bits: 10,
num_sblks_created: 0,
size_sblks_created: 0,
num_dblks_created: 0,
size_dblks_created: 0,
max_idx_set: 0,
num_elmts_realized: 0,
idx_blk_addr: UNDEF_ADDR,
}
}
pub fn encoded_size(&self, ctx: &FormatContext) -> usize {
let ss = ctx.sizeof_size as usize;
let sa = ctx.sizeof_addr as usize;
4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 6 * ss + sa + 4
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let ss = ctx.sizeof_size as usize;
let sa = ctx.sizeof_addr as usize;
let size = self.encoded_size(ctx);
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&EAHD_SIGNATURE);
buf.push(EA_VERSION);
buf.push(self.class_id);
buf.push(self.raw_elmt_size);
buf.push(self.max_nelmts_bits);
buf.push(self.idx_blk_elmts);
buf.push(self.data_blk_min_elmts);
buf.push(self.sup_blk_min_data_ptrs);
buf.push(self.max_dblk_page_nelmts_bits);
buf.extend_from_slice(&self.num_sblks_created.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.size_sblks_created.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.num_dblks_created.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.size_dblks_created.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.max_idx_set.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.num_elmts_realized.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.idx_blk_addr.to_le_bytes()[..sa]);
let cksum = checksum_metadata(&buf);
buf.extend_from_slice(&cksum.to_le_bytes());
debug_assert_eq!(buf.len(), size);
buf
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let ss = ctx.sizeof_size as usize;
let sa = ctx.sizeof_addr as usize;
let min_size = 4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 6 * ss + sa + 4;
if buf.len() < min_size {
return Err(FormatError::BufferTooShort {
needed: min_size,
available: buf.len(),
});
}
if buf[0..4] != EAHD_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
let version = buf[4];
if version != EA_VERSION {
return Err(FormatError::InvalidVersion(version));
}
let data_end = min_size - 4;
let stored_cksum = u32::from_le_bytes([
buf[data_end],
buf[data_end + 1],
buf[data_end + 2],
buf[data_end + 3],
]);
let computed_cksum = checksum_metadata(&buf[..data_end]);
if stored_cksum != computed_cksum {
return Err(FormatError::ChecksumMismatch {
expected: stored_cksum,
computed: computed_cksum,
});
}
let mut pos = 5;
let class_id = buf[pos];
pos += 1;
let raw_elmt_size = buf[pos];
pos += 1;
let max_nelmts_bits = buf[pos];
pos += 1;
let idx_blk_elmts = buf[pos];
pos += 1;
let data_blk_min_elmts = buf[pos];
pos += 1;
let sup_blk_min_data_ptrs = buf[pos];
pos += 1;
let max_dblk_page_nelmts_bits = buf[pos];
pos += 1;
let num_sblks_created = read_size(&buf[pos..], ss);
pos += ss;
let size_sblks_created = read_size(&buf[pos..], ss);
pos += ss;
let num_dblks_created = read_size(&buf[pos..], ss);
pos += ss;
let size_dblks_created = read_size(&buf[pos..], ss);
pos += ss;
let max_idx_set = read_size(&buf[pos..], ss);
pos += ss;
let num_elmts_realized = read_size(&buf[pos..], ss);
pos += ss;
let idx_blk_addr = read_addr(&buf[pos..], sa);
Ok(Self {
class_id,
raw_elmt_size,
max_nelmts_bits,
idx_blk_elmts,
data_blk_min_elmts,
sup_blk_min_data_ptrs,
max_dblk_page_nelmts_bits,
num_sblks_created,
size_sblks_created,
num_dblks_created,
size_dblks_created,
max_idx_set,
num_elmts_realized,
idx_blk_addr,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtensibleArrayIndexBlock {
pub class_id: u8,
pub header_addr: u64,
pub elements: Vec<u64>,
pub dblk_addrs: Vec<u64>,
pub sblk_addrs: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilteredIndexBlock {
pub class_id: u8,
pub header_addr: u64,
pub elements: Vec<FilteredChunkEntry>,
pub dblk_addrs: Vec<u64>,
pub sblk_addrs: Vec<u64>,
}
impl FilteredIndexBlock {
pub fn new(
header_addr: u64,
idx_blk_elmts: u8,
ndblk_addrs: usize,
nsblk_addrs: usize,
) -> Self {
Self {
class_id: EA_CLS_FILT_CHUNK,
header_addr,
elements: vec![FilteredChunkEntry::undef(); idx_blk_elmts as usize],
dblk_addrs: vec![UNDEF_ADDR; ndblk_addrs],
sblk_addrs: vec![UNDEF_ADDR; nsblk_addrs],
}
}
pub fn encode(&self, ctx: &FormatContext, chunk_size_len: u8) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let elmt_size = FilteredChunkEntry::raw_size(ctx.sizeof_addr, chunk_size_len) as usize;
let size = 4
+ 1
+ 1
+ sa
+ self.elements.len() * elmt_size
+ self.dblk_addrs.len() * sa
+ self.sblk_addrs.len() * sa
+ 4;
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&EAIB_SIGNATURE);
buf.push(EA_VERSION);
buf.push(self.class_id);
buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
for elem in &self.elements {
buf.extend_from_slice(&elem.encode(sa, chunk_size_len as usize));
}
for &addr in &self.dblk_addrs {
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
for &addr in &self.sblk_addrs {
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
let cksum = checksum_metadata(&buf);
buf.extend_from_slice(&cksum.to_le_bytes());
debug_assert_eq!(buf.len(), size);
buf
}
pub fn decode(
buf: &[u8],
ctx: &FormatContext,
idx_blk_elmts: usize,
ndblk_addrs: usize,
nsblk_addrs: usize,
chunk_size_len: u8,
) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let elmt_size = FilteredChunkEntry::raw_size(ctx.sizeof_addr, chunk_size_len) as usize;
let min_size =
4 + 1 + 1 + sa + idx_blk_elmts * elmt_size + ndblk_addrs * sa + nsblk_addrs * sa + 4;
if buf.len() < min_size {
return Err(FormatError::BufferTooShort {
needed: min_size,
available: buf.len(),
});
}
if buf[0..4] != EAIB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
if buf[4] != EA_VERSION {
return Err(FormatError::InvalidVersion(buf[4]));
}
let data_end = min_size - 4;
let stored = u32::from_le_bytes([
buf[data_end],
buf[data_end + 1],
buf[data_end + 2],
buf[data_end + 3],
]);
let computed = checksum_metadata(&buf[..data_end]);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
let class_id = buf[5];
let mut pos = 6;
let header_addr = read_addr(&buf[pos..], sa);
pos += sa;
let mut elements = Vec::with_capacity(idx_blk_elmts);
for _ in 0..idx_blk_elmts {
elements.push(FilteredChunkEntry::decode(
&buf[pos..],
sa,
chunk_size_len as usize,
));
pos += elmt_size;
}
let mut dblk_addrs = Vec::with_capacity(ndblk_addrs);
for _ in 0..ndblk_addrs {
dblk_addrs.push(read_addr(&buf[pos..], sa));
pos += sa;
}
let mut sblk_addrs = Vec::with_capacity(nsblk_addrs);
for _ in 0..nsblk_addrs {
sblk_addrs.push(read_addr(&buf[pos..], sa));
pos += sa;
}
Ok(Self {
class_id,
header_addr,
elements,
dblk_addrs,
sblk_addrs,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FilteredDataBlock {
pub class_id: u8,
pub header_addr: u64,
pub block_offset: u64,
pub elements: Vec<FilteredChunkEntry>,
}
impl FilteredDataBlock {
pub fn new(header_addr: u64, block_offset: u64, nelmts: usize) -> Self {
Self {
class_id: EA_CLS_FILT_CHUNK,
header_addr,
block_offset,
elements: vec![FilteredChunkEntry::undef(); nelmts],
}
}
pub fn encode(&self, ctx: &FormatContext, max_nelmts_bits: u8, chunk_size_len: u8) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let bo_size = ExtensibleArrayDataBlock::block_offset_size(max_nelmts_bits);
let elmt_size = FilteredChunkEntry::raw_size(ctx.sizeof_addr, chunk_size_len) as usize;
let size = 4 + 1 + 1 + sa + bo_size + self.elements.len() * elmt_size + 4;
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&EADB_SIGNATURE);
buf.push(EA_VERSION);
buf.push(self.class_id);
buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
buf.extend_from_slice(&self.block_offset.to_le_bytes()[..bo_size]);
for elem in &self.elements {
buf.extend_from_slice(&elem.encode(sa, chunk_size_len as usize));
}
let cksum = checksum_metadata(&buf);
buf.extend_from_slice(&cksum.to_le_bytes());
debug_assert_eq!(buf.len(), size);
buf
}
pub fn decode(
buf: &[u8],
ctx: &FormatContext,
max_nelmts_bits: u8,
nelmts: usize,
chunk_size_len: u8,
) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let bo_size = ExtensibleArrayDataBlock::block_offset_size(max_nelmts_bits);
let elmt_size = FilteredChunkEntry::raw_size(ctx.sizeof_addr, chunk_size_len) as usize;
let min_size = 4 + 1 + 1 + sa + bo_size + nelmts * elmt_size + 4;
if buf.len() < min_size {
return Err(FormatError::BufferTooShort {
needed: min_size,
available: buf.len(),
});
}
if buf[0..4] != EADB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
if buf[4] != EA_VERSION {
return Err(FormatError::InvalidVersion(buf[4]));
}
let data_end = min_size - 4;
let stored = u32::from_le_bytes([
buf[data_end],
buf[data_end + 1],
buf[data_end + 2],
buf[data_end + 3],
]);
let computed = checksum_metadata(&buf[..data_end]);
if stored != computed {
return Err(FormatError::ChecksumMismatch {
expected: stored,
computed,
});
}
let class_id = buf[5];
let mut pos = 6;
let header_addr = read_addr(&buf[pos..], sa);
pos += sa;
let block_offset = read_size(&buf[pos..], bo_size);
pos += bo_size;
let mut elements = Vec::with_capacity(nelmts);
for _ in 0..nelmts {
elements.push(FilteredChunkEntry::decode(
&buf[pos..],
sa,
chunk_size_len as usize,
));
pos += elmt_size;
}
Ok(Self {
class_id,
header_addr,
block_offset,
elements,
})
}
}
impl ExtensibleArrayIndexBlock {
pub fn new(
header_addr: u64,
idx_blk_elmts: u8,
ndblk_addrs: usize,
nsblk_addrs: usize,
) -> Self {
Self {
class_id: EA_CLS_CHUNK,
header_addr,
elements: vec![UNDEF_ADDR; idx_blk_elmts as usize],
dblk_addrs: vec![UNDEF_ADDR; ndblk_addrs],
sblk_addrs: vec![UNDEF_ADDR; nsblk_addrs],
}
}
pub fn encoded_size(&self, ctx: &FormatContext) -> usize {
let sa = ctx.sizeof_addr as usize;
4 + 1
+ 1
+ sa
+ self.elements.len() * sa
+ self.dblk_addrs.len() * sa
+ self.sblk_addrs.len() * sa
+ 4
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let size = self.encoded_size(ctx);
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&EAIB_SIGNATURE);
buf.push(EA_VERSION);
buf.push(self.class_id);
buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
for &elem in &self.elements {
buf.extend_from_slice(&elem.to_le_bytes()[..sa]);
}
for &addr in &self.dblk_addrs {
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
for &addr in &self.sblk_addrs {
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
let cksum = checksum_metadata(&buf);
buf.extend_from_slice(&cksum.to_le_bytes());
debug_assert_eq!(buf.len(), size);
buf
}
pub fn decode(
buf: &[u8],
ctx: &FormatContext,
idx_blk_elmts: usize,
ndblk_addrs: usize,
nsblk_addrs: usize,
) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let min_size =
4 + 1 + 1 + sa + idx_blk_elmts * sa + ndblk_addrs * sa + nsblk_addrs * sa + 4;
if buf.len() < min_size {
return Err(FormatError::BufferTooShort {
needed: min_size,
available: buf.len(),
});
}
if buf[0..4] != EAIB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
let version = buf[4];
if version != EA_VERSION {
return Err(FormatError::InvalidVersion(version));
}
let data_end = min_size - 4;
let stored_cksum = u32::from_le_bytes([
buf[data_end],
buf[data_end + 1],
buf[data_end + 2],
buf[data_end + 3],
]);
let computed_cksum = checksum_metadata(&buf[..data_end]);
if stored_cksum != computed_cksum {
return Err(FormatError::ChecksumMismatch {
expected: stored_cksum,
computed: computed_cksum,
});
}
let class_id = buf[5];
let mut pos = 6;
let header_addr = read_addr(&buf[pos..], sa);
pos += sa;
let mut elements = Vec::with_capacity(idx_blk_elmts);
for _ in 0..idx_blk_elmts {
elements.push(read_addr(&buf[pos..], sa));
pos += sa;
}
let mut dblk_addrs = Vec::with_capacity(ndblk_addrs);
for _ in 0..ndblk_addrs {
dblk_addrs.push(read_addr(&buf[pos..], sa));
pos += sa;
}
let mut sblk_addrs = Vec::with_capacity(nsblk_addrs);
for _ in 0..nsblk_addrs {
sblk_addrs.push(read_addr(&buf[pos..], sa));
pos += sa;
}
Ok(Self {
class_id,
header_addr,
elements,
dblk_addrs,
sblk_addrs,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtensibleArrayDataBlock {
pub class_id: u8,
pub header_addr: u64,
pub block_offset: u64,
pub elements: Vec<u64>,
}
impl ExtensibleArrayDataBlock {
pub fn new(header_addr: u64, block_offset: u64, nelmts: usize) -> Self {
Self {
class_id: EA_CLS_CHUNK,
header_addr,
block_offset,
elements: vec![UNDEF_ADDR; nelmts],
}
}
pub fn block_offset_size(max_nelmts_bits: u8) -> usize {
std::cmp::max(1, (max_nelmts_bits as usize).div_ceil(8))
}
pub fn encoded_size(&self, ctx: &FormatContext, max_nelmts_bits: u8) -> usize {
let sa = ctx.sizeof_addr as usize;
let bo_size = Self::block_offset_size(max_nelmts_bits);
4 + 1 + 1 + sa + bo_size + self.elements.len() * sa + 4
}
pub fn encode(&self, ctx: &FormatContext, max_nelmts_bits: u8) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let bo_size = Self::block_offset_size(max_nelmts_bits);
let size = self.encoded_size(ctx, max_nelmts_bits);
let mut buf = Vec::with_capacity(size);
buf.extend_from_slice(&EADB_SIGNATURE);
buf.push(EA_VERSION);
buf.push(self.class_id);
buf.extend_from_slice(&self.header_addr.to_le_bytes()[..sa]);
buf.extend_from_slice(&self.block_offset.to_le_bytes()[..bo_size]);
for &elem in &self.elements {
buf.extend_from_slice(&elem.to_le_bytes()[..sa]);
}
let cksum = checksum_metadata(&buf);
buf.extend_from_slice(&cksum.to_le_bytes());
debug_assert_eq!(buf.len(), size);
buf
}
pub fn decode(
buf: &[u8],
ctx: &FormatContext,
max_nelmts_bits: u8,
nelmts: usize,
) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let bo_size = Self::block_offset_size(max_nelmts_bits);
let min_size = 4 + 1 + 1 + sa + bo_size + nelmts * sa + 4;
if buf.len() < min_size {
return Err(FormatError::BufferTooShort {
needed: min_size,
available: buf.len(),
});
}
if buf[0..4] != EADB_SIGNATURE {
return Err(FormatError::InvalidSignature);
}
let version = buf[4];
if version != EA_VERSION {
return Err(FormatError::InvalidVersion(version));
}
let data_end = min_size - 4;
let stored_cksum = u32::from_le_bytes([
buf[data_end],
buf[data_end + 1],
buf[data_end + 2],
buf[data_end + 3],
]);
let computed_cksum = checksum_metadata(&buf[..data_end]);
if stored_cksum != computed_cksum {
return Err(FormatError::ChecksumMismatch {
expected: stored_cksum,
computed: computed_cksum,
});
}
let class_id = buf[5];
let mut pos = 6;
let header_addr = read_addr(&buf[pos..], sa);
pos += sa;
let block_offset = read_size(&buf[pos..], bo_size);
pos += bo_size;
let mut elements = Vec::with_capacity(nelmts);
for _ in 0..nelmts {
elements.push(read_addr(&buf[pos..], sa));
pos += sa;
}
Ok(Self {
class_id,
header_addr,
block_offset,
elements,
})
}
}
fn read_addr(buf: &[u8], n: usize) -> u64 {
if buf[..n].iter().all(|&b| b == 0xFF) {
UNDEF_ADDR
} else {
let mut tmp = [0u8; 8];
tmp[..n].copy_from_slice(&buf[..n]);
u64::from_le_bytes(tmp)
}
}
fn read_size(buf: &[u8], n: usize) -> u64 {
let mut tmp = [0u8; 8];
tmp[..n].copy_from_slice(&buf[..n]);
u64::from_le_bytes(tmp)
}
pub fn compute_ndblk_addrs(sup_blk_min_data_ptrs: u8) -> usize {
2 * (sup_blk_min_data_ptrs as usize - 1)
}
fn compute_nsblks(idx_blk_elmts: u8, data_blk_min_elmts: u8, max_nelmts_bits: u8) -> usize {
let max_nelmts: u64 = 1u64 << (max_nelmts_bits as u64);
let nelmts_remaining = max_nelmts - idx_blk_elmts as u64;
let mut nsblks = 0usize;
let mut acc = 0u64;
while acc < nelmts_remaining {
let (ndblks_in_sblk, dblk_size) = if nsblks < 2 {
(1u64, data_blk_min_elmts as u64)
} else {
let half = (nsblks - 2) / 2;
(
1u64 << (half + 1),
(data_blk_min_elmts as u64) << (half + 1),
)
};
acc = acc.saturating_add(ndblks_in_sblk.saturating_mul(dblk_size));
nsblks += 1;
}
nsblks
}
fn compute_sblk_idx_start(sup_blk_min_data_ptrs: u8, nsblks: usize) -> usize {
let ndblk_addrs = compute_ndblk_addrs(sup_blk_min_data_ptrs);
let mut dblks_counted = 0usize;
let mut sblk_idx_start = 0usize;
for s in 0..nsblks {
let ndblks_in_sblk = if s < 2 {
1
} else {
let half = (s - 2) / 2;
1 << (half + 1)
};
if dblks_counted + ndblks_in_sblk > ndblk_addrs {
break;
}
dblks_counted += ndblks_in_sblk;
sblk_idx_start = s + 1;
}
sblk_idx_start
}
pub fn compute_nsblk_addrs(
idx_blk_elmts: u8,
data_blk_min_elmts: u8,
sup_blk_min_data_ptrs: u8,
max_nelmts_bits: u8,
) -> usize {
let nsblks = compute_nsblks(idx_blk_elmts, data_blk_min_elmts, max_nelmts_bits);
let sblk_idx_start = compute_sblk_idx_start(sup_blk_min_data_ptrs, nsblks);
nsblks - sblk_idx_start
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx8() -> FormatContext {
FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
}
}
fn ctx4() -> FormatContext {
FormatContext {
sizeof_addr: 4,
sizeof_size: 4,
}
}
#[test]
fn header_roundtrip() {
let mut hdr = ExtensibleArrayHeader::new_for_chunks(&ctx8());
hdr.idx_blk_addr = 0x1000;
hdr.max_idx_set = 3;
hdr.num_elmts_realized = 4;
let encoded = hdr.encode(&ctx8());
assert_eq!(encoded.len(), hdr.encoded_size(&ctx8()));
assert_eq!(&encoded[..4], b"EAHD");
let decoded = ExtensibleArrayHeader::decode(&encoded, &ctx8()).unwrap();
assert_eq!(decoded, hdr);
}
#[test]
fn header_roundtrip_ctx4() {
let mut hdr = ExtensibleArrayHeader::new_for_chunks(&ctx4());
hdr.raw_elmt_size = 4;
hdr.idx_blk_addr = 0x800;
let encoded = hdr.encode(&ctx4());
let decoded = ExtensibleArrayHeader::decode(&encoded, &ctx4()).unwrap();
assert_eq!(decoded, hdr);
}
#[test]
fn header_bad_signature() {
let mut hdr = ExtensibleArrayHeader::new_for_chunks(&ctx8());
hdr.idx_blk_addr = 0x1000;
let mut encoded = hdr.encode(&ctx8());
encoded[0] = b'X';
let err = ExtensibleArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::InvalidSignature));
}
#[test]
fn header_checksum_mismatch() {
let mut hdr = ExtensibleArrayHeader::new_for_chunks(&ctx8());
hdr.idx_blk_addr = 0x1000;
let mut encoded = hdr.encode(&ctx8());
encoded[6] ^= 0xFF; let err = ExtensibleArrayHeader::decode(&encoded, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
}
#[test]
fn index_block_roundtrip() {
let ndblk = compute_ndblk_addrs(4);
assert_eq!(ndblk, 6);
let mut iblk = ExtensibleArrayIndexBlock::new(0x500, 4, ndblk, 0);
iblk.elements[0] = 0x1000;
iblk.elements[1] = 0x2000;
iblk.dblk_addrs[0] = 0x3000;
let encoded = iblk.encode(&ctx8());
assert_eq!(encoded.len(), iblk.encoded_size(&ctx8()));
assert_eq!(&encoded[..4], b"EAIB");
let decoded = ExtensibleArrayIndexBlock::decode(&encoded, &ctx8(), 4, ndblk, 0).unwrap();
assert_eq!(decoded, iblk);
}
#[test]
fn index_block_roundtrip_ctx4() {
let iblk = ExtensibleArrayIndexBlock::new(0x300, 4, 6, 0);
let encoded = iblk.encode(&ctx4());
let decoded = ExtensibleArrayIndexBlock::decode(&encoded, &ctx4(), 4, 6, 0).unwrap();
assert_eq!(decoded, iblk);
}
#[test]
fn index_block_bad_checksum() {
let iblk = ExtensibleArrayIndexBlock::new(0x500, 4, 6, 0);
let mut encoded = iblk.encode(&ctx8());
encoded[8] ^= 0xFF;
let err = ExtensibleArrayIndexBlock::decode(&encoded, &ctx8(), 4, 6, 0).unwrap_err();
assert!(matches!(err, FormatError::ChecksumMismatch { .. }));
}
#[test]
fn data_block_roundtrip() {
let mut dblk = ExtensibleArrayDataBlock::new(0x500, 4, 16);
dblk.elements[0] = 0xA000;
dblk.elements[5] = 0xB000;
let encoded = dblk.encode(&ctx8(), 32);
assert_eq!(encoded.len(), dblk.encoded_size(&ctx8(), 32));
assert_eq!(&encoded[..4], b"EADB");
let decoded = ExtensibleArrayDataBlock::decode(&encoded, &ctx8(), 32, 16).unwrap();
assert_eq!(decoded, dblk);
}
#[test]
fn data_block_offset_size() {
assert_eq!(ExtensibleArrayDataBlock::block_offset_size(8), 1);
assert_eq!(ExtensibleArrayDataBlock::block_offset_size(16), 2);
assert_eq!(ExtensibleArrayDataBlock::block_offset_size(32), 4);
assert_eq!(ExtensibleArrayDataBlock::block_offset_size(0), 1);
}
#[test]
fn compute_ndblk_addrs_default() {
assert_eq!(compute_ndblk_addrs(4), 6);
assert_eq!(compute_ndblk_addrs(2), 2);
}
#[test]
fn compute_nsblk_addrs_default() {
assert_eq!(compute_nsblk_addrs(4, 16, 4, 32), 25);
}
}