|
|
|
@@ -7,7 +7,7 @@ extern crate alloc;
|
|
|
|
|
use alloc::{vec, vec::Vec};
|
|
|
|
|
|
|
|
|
|
use crate::checksum::jenkins_lookup3;
|
|
|
|
|
use crate::chunked_write::WrittenChunk;
|
|
|
|
|
use crate::chunked_write::{WrittenChunk, filtered_chunk_size_len, push_addr, push_index_element};
|
|
|
|
|
|
|
|
|
|
/// Serialize a v4 Extensible Array layout message.
|
|
|
|
|
pub(crate) fn serialize_v4_extensible_array(
|
|
|
|
@@ -58,11 +58,11 @@ pub(crate) fn serialize_v4_extensible_array(
|
|
|
|
|
buf.push(4);
|
|
|
|
|
|
|
|
|
|
// EA creation parameters (must match AEHD and HDF5 C library defaults)
|
|
|
|
|
buf.push(32); // max_nelmts_bits
|
|
|
|
|
buf.push(4); // idx_blk_elmts
|
|
|
|
|
buf.push(4); // super_blk_min_data_ptrs
|
|
|
|
|
buf.push(16); // data_blk_min_elmts
|
|
|
|
|
buf.push(10); // max_dblk_page_nelmts_bits
|
|
|
|
|
buf.push(MAX_NELMTS_BITS);
|
|
|
|
|
buf.push(IDX_BLK_ELMTS);
|
|
|
|
|
buf.push(SUP_BLK_MIN_DATA_PTRS);
|
|
|
|
|
buf.push(DATA_BLK_MIN_ELMTS);
|
|
|
|
|
buf.push(MAX_DBLK_PAGE_NELMTS_BITS);
|
|
|
|
|
|
|
|
|
|
// EA header address
|
|
|
|
|
match offset_size {
|
|
|
|
@@ -74,304 +74,281 @@ pub(crate) fn serialize_v4_extensible_array(
|
|
|
|
|
buf
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EA creation parameters — the HDF5 library's defaults for chunk indexes
|
|
|
|
|
// (`H5D_EARRAY_*`); the layout message above and the header must agree.
|
|
|
|
|
const MAX_NELMTS_BITS: u8 = 32;
|
|
|
|
|
const IDX_BLK_ELMTS: u8 = 4;
|
|
|
|
|
const SUP_BLK_MIN_DATA_PTRS: u8 = 4;
|
|
|
|
|
const DATA_BLK_MIN_ELMTS: u8 = 16;
|
|
|
|
|
const MAX_DBLK_PAGE_NELMTS_BITS: u8 = 10;
|
|
|
|
|
|
|
|
|
|
/// One data block of the array: its first element (relative to the end of
|
|
|
|
|
/// the index block's own elements), element count, and address when it is
|
|
|
|
|
/// allocated.
|
|
|
|
|
struct DataBlock {
|
|
|
|
|
start: usize,
|
|
|
|
|
nelmts: usize,
|
|
|
|
|
addr: Option<u64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build a complete Extensible Array at a known absolute address.
|
|
|
|
|
///
|
|
|
|
|
/// For simplicity, we put all elements inline in the index block when the
|
|
|
|
|
/// number of chunks is small (up to idx_blk_elmts), otherwise use inline +
|
|
|
|
|
/// direct data blocks.
|
|
|
|
|
/// `slots[i]` is the element at linear index `i` (see `chunk_grid`); `None`
|
|
|
|
|
/// marks an unallocated chunk. The first `IDX_BLK_ELMTS` elements live in
|
|
|
|
|
/// the index block, the rest in data blocks grouped by super block level
|
|
|
|
|
/// exactly as `H5EA__hdr_init` sizes them: level `u` has `2^(u/2)` data
|
|
|
|
|
/// blocks of `DATA_BLK_MIN_ELMTS * 2^ceil(u/2)` elements. The data blocks of
|
|
|
|
|
/// the first levels are addressed straight from the index block; later
|
|
|
|
|
/// levels go through a super block (EASB). Data blocks larger than a page
|
|
|
|
|
/// (`2^MAX_DBLK_PAGE_NELMTS_BITS` elements) are paged, with their page-init
|
|
|
|
|
/// bits kept in the owning super block. Only blocks holding a defined element
|
|
|
|
|
/// are allocated; the rest keep the undefined address, as in a file the
|
|
|
|
|
/// library wrote.
|
|
|
|
|
pub fn build_extensible_array_at(
|
|
|
|
|
chunks: &[WrittenChunk],
|
|
|
|
|
slots: &[Option<WrittenChunk>],
|
|
|
|
|
offset_size: u8,
|
|
|
|
|
length_size: u8,
|
|
|
|
|
has_filters: bool,
|
|
|
|
|
ea_base_address: u64,
|
|
|
|
|
) -> Vec<u8> {
|
|
|
|
|
let os = offset_size as usize;
|
|
|
|
|
let num_elements = chunks.len();
|
|
|
|
|
|
|
|
|
|
// Compute element encoding size (same logic as Fixed Array)
|
|
|
|
|
let chunk_size_bytes: usize = if has_filters {
|
|
|
|
|
let max_raw = chunks.iter().map(|c| c.raw_size).max().unwrap_or(1);
|
|
|
|
|
let log2_val = if max_raw <= 1 {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
63 - max_raw.leading_zeros()
|
|
|
|
|
};
|
|
|
|
|
let len = 1 + ((log2_val + 8) / 8) as usize;
|
|
|
|
|
len.min(8)
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let elem_size = if has_filters {
|
|
|
|
|
os + chunk_size_bytes + 4
|
|
|
|
|
} else {
|
|
|
|
|
os
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let chunk_size_bytes = has_filters.then(|| filtered_chunk_size_len(slots));
|
|
|
|
|
let elem_size = os + chunk_size_bytes.map_or(0, |n| n + 4);
|
|
|
|
|
let client_id: u8 = if has_filters { 1 } else { 0 };
|
|
|
|
|
let arr_off_size = (MAX_NELMTS_BITS as usize).div_ceil(8);
|
|
|
|
|
let page_nelmts = 1usize << MAX_DBLK_PAGE_NELMTS_BITS;
|
|
|
|
|
let idx_blk = IDX_BLK_ELMTS as usize;
|
|
|
|
|
|
|
|
|
|
// EA creation parameters — must match HDF5 C library defaults exactly
|
|
|
|
|
let max_nelmts_bits: u8 = 32;
|
|
|
|
|
let idx_blk_elmts: u8 = 4;
|
|
|
|
|
let min_dblk_nelmts: u8 = 16;
|
|
|
|
|
let super_blk_min_nelmts: u8 = 4;
|
|
|
|
|
let max_dblk_nelmts_bits: u8 = 10;
|
|
|
|
|
// Elements past the last defined one are never realised
|
|
|
|
|
// (`max_idx_set` is one past the highest index ever set).
|
|
|
|
|
let max_idx_set = slots.iter().rposition(Option::is_some).map_or(0, |i| i + 1);
|
|
|
|
|
let slots = &slots[..max_idx_set];
|
|
|
|
|
let defined_in = |start: usize, n: usize| -> bool {
|
|
|
|
|
let lo = idx_blk.saturating_add(start).min(slots.len());
|
|
|
|
|
let hi = idx_blk
|
|
|
|
|
.saturating_add(start)
|
|
|
|
|
.saturating_add(n)
|
|
|
|
|
.min(slots.len());
|
|
|
|
|
slots[lo..hi].iter().any(Option::is_some)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// EAHD size: fixed(12) + 6 stats(6*length_size) + addr(offset_size) + checksum(4)
|
|
|
|
|
// Super block levels: (ndblks, dblk_nelmts, first element).
|
|
|
|
|
let log2_dmin = (DATA_BLK_MIN_ELMTS as u32).trailing_zeros() as usize;
|
|
|
|
|
let nsblks = 1 + MAX_NELMTS_BITS as usize - log2_dmin;
|
|
|
|
|
let ndblk_addrs = 2 * (SUP_BLK_MIN_DATA_PTRS as usize - 1);
|
|
|
|
|
let mut levels: Vec<(usize, usize, usize)> = Vec::with_capacity(nsblks);
|
|
|
|
|
let mut start = 0usize;
|
|
|
|
|
for u in 0..nsblks {
|
|
|
|
|
let ndblks = 1usize << (u / 2);
|
|
|
|
|
let nelmts = (DATA_BLK_MIN_ELMTS as usize) << u.div_ceil(2);
|
|
|
|
|
levels.push((ndblks, nelmts, start));
|
|
|
|
|
// Saturate: on 32-bit targets the last levels only need to compare
|
|
|
|
|
// as "beyond the end".
|
|
|
|
|
start = start.saturating_add(ndblks.saturating_mul(nelmts));
|
|
|
|
|
}
|
|
|
|
|
// Levels whose data blocks the index block addresses directly.
|
|
|
|
|
let mut direct_levels = 0;
|
|
|
|
|
let mut n = 0;
|
|
|
|
|
while n < ndblk_addrs {
|
|
|
|
|
n += levels[direct_levels].0;
|
|
|
|
|
direct_levels += 1;
|
|
|
|
|
}
|
|
|
|
|
let nsblk_addrs = nsblks - direct_levels;
|
|
|
|
|
|
|
|
|
|
let dblk_size = |nelmts: usize| -> usize {
|
|
|
|
|
let prefix = 4 + 1 + 1 + os + arr_off_size + 4;
|
|
|
|
|
if nelmts > page_nelmts {
|
|
|
|
|
prefix + (nelmts / page_nelmts) * (page_nelmts * elem_size + 4)
|
|
|
|
|
} else {
|
|
|
|
|
prefix + nelmts * elem_size
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let sblk_bitmap_len = |ndblks: usize, nelmts: usize| -> usize {
|
|
|
|
|
if nelmts > page_nelmts {
|
|
|
|
|
ndblks * (nelmts / page_nelmts).div_ceil(8)
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Plan addresses: header, index block, the direct data blocks, then each
|
|
|
|
|
// allocated super block followed by its allocated data blocks.
|
|
|
|
|
let aehd_size = 4 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 6 * length_size as usize + os + 4;
|
|
|
|
|
let aeib_address = ea_base_address + aehd_size as u64;
|
|
|
|
|
let aeib_size = 4 + 1 + 1 + os + idx_blk * elem_size + ndblk_addrs * os + nsblk_addrs * os + 4;
|
|
|
|
|
let mut cursor = aeib_address + aeib_size as u64;
|
|
|
|
|
|
|
|
|
|
// Determine how many elements go inline vs data blocks
|
|
|
|
|
let n_inline = (idx_blk_elmts as usize).min(num_elements);
|
|
|
|
|
let remaining_after_inline = num_elements.saturating_sub(n_inline);
|
|
|
|
|
let mut ndata_blks = 0u64;
|
|
|
|
|
let mut data_blk_size = 0u64;
|
|
|
|
|
let mut nsuper_blks = 0u64;
|
|
|
|
|
let mut super_blk_size = 0u64;
|
|
|
|
|
let mut realized = idx_blk as u64;
|
|
|
|
|
|
|
|
|
|
// Compute super block layout per HDF5 spec
|
|
|
|
|
let sblk_min = super_blk_min_nelmts as usize;
|
|
|
|
|
let log2_dblk_min = if min_dblk_nelmts <= 1 {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
(min_dblk_nelmts as u32).trailing_zeros() as usize
|
|
|
|
|
};
|
|
|
|
|
let nsblks = (max_nelmts_bits as usize).saturating_sub(log2_dblk_min) + 1;
|
|
|
|
|
|
|
|
|
|
// Direct data block addresses (from super blocks 0..sblk_min-1)
|
|
|
|
|
let mut dblk_sizes: Vec<usize> = Vec::new();
|
|
|
|
|
for sblk_idx in 0..sblk_min.min(nsblks) {
|
|
|
|
|
let ndblks = 1usize << (sblk_idx / 2);
|
|
|
|
|
let dblk_nelmts = (min_dblk_nelmts as usize) * (1 << sblk_idx.div_ceil(2));
|
|
|
|
|
for _ in 0..ndblks {
|
|
|
|
|
dblk_sizes.push(dblk_nelmts);
|
|
|
|
|
let mut plan_dblk = |cursor: &mut u64, start: usize, nelmts: usize| -> DataBlock {
|
|
|
|
|
let addr = defined_in(start, nelmts).then(|| {
|
|
|
|
|
let a = *cursor;
|
|
|
|
|
let size = dblk_size(nelmts) as u64;
|
|
|
|
|
*cursor += size;
|
|
|
|
|
ndata_blks += 1;
|
|
|
|
|
data_blk_size += size;
|
|
|
|
|
realized += nelmts as u64;
|
|
|
|
|
a
|
|
|
|
|
});
|
|
|
|
|
DataBlock {
|
|
|
|
|
start,
|
|
|
|
|
nelmts,
|
|
|
|
|
addr,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let n_direct_dblks = dblk_sizes.len();
|
|
|
|
|
|
|
|
|
|
// Super block addresses (for super blocks sblk_min..nsblks-1)
|
|
|
|
|
let n_sblk_addrs = nsblks.saturating_sub(sblk_min);
|
|
|
|
|
|
|
|
|
|
// EAIB size
|
|
|
|
|
let aeib_size = 4
|
|
|
|
|
+ 1
|
|
|
|
|
+ 1
|
|
|
|
|
+ os
|
|
|
|
|
+ idx_blk_elmts as usize * elem_size
|
|
|
|
|
+ n_direct_dblks * os
|
|
|
|
|
+ n_sblk_addrs * os
|
|
|
|
|
+ 4;
|
|
|
|
|
|
|
|
|
|
// Build AEHD
|
|
|
|
|
let mut aehd = Vec::with_capacity(aehd_size);
|
|
|
|
|
aehd.extend_from_slice(b"EAHD");
|
|
|
|
|
aehd.push(0); // version
|
|
|
|
|
aehd.push(client_id);
|
|
|
|
|
aehd.push(elem_size as u8);
|
|
|
|
|
aehd.push(max_nelmts_bits);
|
|
|
|
|
aehd.push(idx_blk_elmts);
|
|
|
|
|
aehd.push(min_dblk_nelmts);
|
|
|
|
|
aehd.push(super_blk_min_nelmts);
|
|
|
|
|
aehd.push(max_dblk_nelmts_bits);
|
|
|
|
|
|
|
|
|
|
// Count data blocks that will have chunks
|
|
|
|
|
let n_active_dblks: u64 = if remaining_after_inline > 0 {
|
|
|
|
|
let mut count = 0u64;
|
|
|
|
|
let mut ci = n_inline;
|
|
|
|
|
for &sz in &dblk_sizes {
|
|
|
|
|
if ci < num_elements {
|
|
|
|
|
count += 1;
|
|
|
|
|
ci += sz;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
count
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
};
|
|
|
|
|
let blk_off_size = (max_nelmts_bits as usize).div_ceil(8);
|
|
|
|
|
let aedb_header_overhead = 4 + 1 + 1 + os + blk_off_size + 4;
|
|
|
|
|
let data_blk_total_size: u64 = if remaining_after_inline > 0 {
|
|
|
|
|
let mut total = 0u64;
|
|
|
|
|
let mut ci = n_inline;
|
|
|
|
|
for &sz in &dblk_sizes {
|
|
|
|
|
if ci < num_elements {
|
|
|
|
|
total += (aedb_header_overhead + sz * elem_size) as u64;
|
|
|
|
|
ci += sz;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
total
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
};
|
|
|
|
|
let max_idx_set: u64 = if remaining_after_inline > 0 {
|
|
|
|
|
let mut max_set = idx_blk_elmts as u64;
|
|
|
|
|
let mut ci = n_inline;
|
|
|
|
|
for &sz in &dblk_sizes {
|
|
|
|
|
if ci < num_elements {
|
|
|
|
|
max_set += sz as u64;
|
|
|
|
|
ci += sz;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
max_set
|
|
|
|
|
} else {
|
|
|
|
|
idx_blk_elmts as u64
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut direct: Vec<DataBlock> = Vec::with_capacity(ndblk_addrs);
|
|
|
|
|
for &(ndblks, nelmts, first) in &levels[..direct_levels] {
|
|
|
|
|
for k in 0..ndblks {
|
|
|
|
|
direct.push(plan_dblk(&mut cursor, first + k * nelmts, nelmts));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// (super block address, level, its data blocks)
|
|
|
|
|
let mut supers: Vec<(Option<u64>, usize, Vec<DataBlock>)> = Vec::with_capacity(nsblk_addrs);
|
|
|
|
|
for (u, &(ndblks, nelmts, first)) in levels.iter().enumerate().skip(direct_levels) {
|
|
|
|
|
if !defined_in(first, ndblks.saturating_mul(nelmts)) {
|
|
|
|
|
supers.push((None, u, Vec::new()));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let sb_size =
|
|
|
|
|
4 + 1 + 1 + os + arr_off_size + sblk_bitmap_len(ndblks, nelmts) + ndblks * os + 4;
|
|
|
|
|
let sb_addr = cursor;
|
|
|
|
|
cursor += sb_size as u64;
|
|
|
|
|
nsuper_blks += 1;
|
|
|
|
|
super_blk_size += sb_size as u64;
|
|
|
|
|
let dblks = (0..ndblks)
|
|
|
|
|
.map(|k| plan_dblk(&mut cursor, first + k * nelmts, nelmts))
|
|
|
|
|
.collect();
|
|
|
|
|
supers.push((Some(sb_addr), u, dblks));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let slot = |i: usize| slots.get(i).and_then(Option::as_ref);
|
|
|
|
|
let write_length = |buf: &mut Vec<u8>, val: u64| match length_size {
|
|
|
|
|
4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
|
|
|
|
|
_ => buf.extend_from_slice(&val.to_le_bytes()),
|
|
|
|
|
};
|
|
|
|
|
let write_addr = |buf: &mut Vec<u8>, val: u64| match offset_size {
|
|
|
|
|
4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
|
|
|
|
|
_ => buf.extend_from_slice(&val.to_le_bytes()),
|
|
|
|
|
let write_addr_opt = |buf: &mut Vec<u8>, addr: Option<u64>| match addr {
|
|
|
|
|
Some(a) => push_addr(buf, a, offset_size),
|
|
|
|
|
None => buf.extend(core::iter::repeat_n(0xFF, os)),
|
|
|
|
|
};
|
|
|
|
|
let block_prefix = |buf: &mut Vec<u8>, sig: &[u8; 4], block_off: usize| {
|
|
|
|
|
buf.extend_from_slice(sig);
|
|
|
|
|
buf.push(0); // version
|
|
|
|
|
buf.push(client_id);
|
|
|
|
|
push_addr(buf, ea_base_address, offset_size);
|
|
|
|
|
buf.extend_from_slice(&(block_off as u64).to_le_bytes()[..arr_off_size]);
|
|
|
|
|
};
|
|
|
|
|
// Serialise one data block (paged or not) onto `out`.
|
|
|
|
|
let write_dblk = |out: &mut Vec<u8>, db: &DataBlock| {
|
|
|
|
|
let at = out.len();
|
|
|
|
|
block_prefix(out, b"EADB", db.start);
|
|
|
|
|
let first = idx_blk + db.start;
|
|
|
|
|
if db.nelmts > page_nelmts {
|
|
|
|
|
// Paged: the prefix carries only its own checksum; each page
|
|
|
|
|
// follows with one of its own.
|
|
|
|
|
let sum = jenkins_lookup3(&out[at..]);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
for p in 0..db.nelmts / page_nelmts {
|
|
|
|
|
let page_at = out.len();
|
|
|
|
|
for e in 0..page_nelmts {
|
|
|
|
|
let i = first + p * page_nelmts + e;
|
|
|
|
|
push_index_element(out, slot(i), offset_size, chunk_size_bytes);
|
|
|
|
|
}
|
|
|
|
|
let sum = jenkins_lookup3(&out[page_at..]);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for i in first..first + db.nelmts {
|
|
|
|
|
push_index_element(out, slot(i), offset_size, chunk_size_bytes);
|
|
|
|
|
}
|
|
|
|
|
let sum = jenkins_lookup3(&out[at..]);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
debug_assert_eq!(out.len() - at, dblk_size(db.nelmts));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
write_length(&mut aehd, 0);
|
|
|
|
|
write_length(&mut aehd, 0);
|
|
|
|
|
write_length(&mut aehd, n_active_dblks);
|
|
|
|
|
write_length(&mut aehd, data_blk_total_size);
|
|
|
|
|
write_length(&mut aehd, num_elements as u64);
|
|
|
|
|
write_length(&mut aehd, max_idx_set);
|
|
|
|
|
// Header (EAHD). The six statistics are, in order: super blocks, their
|
|
|
|
|
// bytes, data blocks, their bytes, max index set, elements realised.
|
|
|
|
|
let mut out = Vec::with_capacity((cursor - ea_base_address) as usize);
|
|
|
|
|
out.extend_from_slice(b"EAHD");
|
|
|
|
|
out.push(0); // version
|
|
|
|
|
out.push(client_id);
|
|
|
|
|
out.push(elem_size as u8);
|
|
|
|
|
out.push(MAX_NELMTS_BITS);
|
|
|
|
|
out.push(IDX_BLK_ELMTS);
|
|
|
|
|
out.push(DATA_BLK_MIN_ELMTS);
|
|
|
|
|
out.push(SUP_BLK_MIN_DATA_PTRS);
|
|
|
|
|
out.push(MAX_DBLK_PAGE_NELMTS_BITS);
|
|
|
|
|
write_length(&mut out, nsuper_blks);
|
|
|
|
|
write_length(&mut out, super_blk_size);
|
|
|
|
|
write_length(&mut out, ndata_blks);
|
|
|
|
|
write_length(&mut out, data_blk_size);
|
|
|
|
|
write_length(&mut out, max_idx_set as u64);
|
|
|
|
|
write_length(&mut out, realized);
|
|
|
|
|
push_addr(&mut out, aeib_address, offset_size);
|
|
|
|
|
let sum = jenkins_lookup3(&out);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
debug_assert_eq!(out.len(), aehd_size);
|
|
|
|
|
|
|
|
|
|
write_addr(&mut aehd, aeib_address);
|
|
|
|
|
|
|
|
|
|
let aehd_checksum = jenkins_lookup3(&aehd);
|
|
|
|
|
aehd.extend_from_slice(&aehd_checksum.to_le_bytes());
|
|
|
|
|
debug_assert_eq!(aehd.len(), aehd_size);
|
|
|
|
|
|
|
|
|
|
// Build AEIB
|
|
|
|
|
let mut aeib = Vec::with_capacity(aeib_size);
|
|
|
|
|
aeib.extend_from_slice(b"EAIB");
|
|
|
|
|
aeib.push(0);
|
|
|
|
|
aeib.push(client_id);
|
|
|
|
|
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => aeib.extend_from_slice(&(ea_base_address as u32).to_le_bytes()),
|
|
|
|
|
8 => aeib.extend_from_slice(&ea_base_address.to_le_bytes()),
|
|
|
|
|
_ => aeib.extend_from_slice(&ea_base_address.to_le_bytes()),
|
|
|
|
|
// Index block (EAIB): inline elements, data block and super block
|
|
|
|
|
// addresses.
|
|
|
|
|
let ib_start = out.len();
|
|
|
|
|
out.extend_from_slice(b"EAIB");
|
|
|
|
|
out.push(0);
|
|
|
|
|
out.push(client_id);
|
|
|
|
|
push_addr(&mut out, ea_base_address, offset_size);
|
|
|
|
|
for i in 0..idx_blk {
|
|
|
|
|
push_index_element(&mut out, slot(i), offset_size, chunk_size_bytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inline elements
|
|
|
|
|
#[allow(clippy::needless_range_loop)]
|
|
|
|
|
for i in 0..idx_blk_elmts as usize {
|
|
|
|
|
if i < n_inline {
|
|
|
|
|
write_chunk_element(
|
|
|
|
|
&mut aeib,
|
|
|
|
|
&chunks[i],
|
|
|
|
|
offset_size,
|
|
|
|
|
has_filters,
|
|
|
|
|
chunk_size_bytes,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
write_undefined_element(&mut aeib, offset_size, has_filters, chunk_size_bytes);
|
|
|
|
|
for db in &direct {
|
|
|
|
|
write_addr_opt(&mut out, db.addr);
|
|
|
|
|
}
|
|
|
|
|
for (sb_addr, _, _) in &supers {
|
|
|
|
|
write_addr_opt(&mut out, *sb_addr);
|
|
|
|
|
}
|
|
|
|
|
let sum = jenkins_lookup3(&out[ib_start..]);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
debug_assert_eq!(out.len() - ib_start, aeib_size);
|
|
|
|
|
|
|
|
|
|
// Data block addresses + build data blocks
|
|
|
|
|
let mut data_blocks_buf = Vec::new();
|
|
|
|
|
let dblks_base = aeib_address + aeib_size as u64;
|
|
|
|
|
let mut dblk_cursor = dblks_base;
|
|
|
|
|
let mut chunk_idx = n_inline;
|
|
|
|
|
|
|
|
|
|
for &nelmts in &dblk_sizes {
|
|
|
|
|
if chunk_idx >= num_elements {
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => aeib.extend_from_slice(&u32::MAX.to_le_bytes()),
|
|
|
|
|
8 => aeib.extend_from_slice(&u64::MAX.to_le_bytes()),
|
|
|
|
|
_ => aeib.extend_from_slice(&u64::MAX.to_le_bytes()),
|
|
|
|
|
for db in direct.iter().filter(|d| d.addr.is_some()) {
|
|
|
|
|
write_dblk(&mut out, db);
|
|
|
|
|
}
|
|
|
|
|
for (sb_addr, u, dblks) in &supers {
|
|
|
|
|
if sb_addr.is_none() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => aeib.extend_from_slice(&(dblk_cursor as u32).to_le_bytes()),
|
|
|
|
|
8 => aeib.extend_from_slice(&dblk_cursor.to_le_bytes()),
|
|
|
|
|
_ => aeib.extend_from_slice(&dblk_cursor.to_le_bytes()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build EADB
|
|
|
|
|
let mut aedb = Vec::new();
|
|
|
|
|
aedb.extend_from_slice(b"EADB");
|
|
|
|
|
aedb.push(0);
|
|
|
|
|
aedb.push(client_id);
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => aedb.extend_from_slice(&(ea_base_address as u32).to_le_bytes()),
|
|
|
|
|
8 => aedb.extend_from_slice(&ea_base_address.to_le_bytes()),
|
|
|
|
|
_ => aedb.extend_from_slice(&ea_base_address.to_le_bytes()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let blk_off_size = (max_nelmts_bits as usize).div_ceil(8);
|
|
|
|
|
let blk_off_val = (chunk_idx - n_inline) as u64;
|
|
|
|
|
aedb.extend_from_slice(&blk_off_val.to_le_bytes()[..blk_off_size]);
|
|
|
|
|
|
|
|
|
|
for slot in 0..nelmts {
|
|
|
|
|
if chunk_idx + slot < num_elements {
|
|
|
|
|
write_chunk_element(
|
|
|
|
|
&mut aedb,
|
|
|
|
|
&chunks[chunk_idx + slot],
|
|
|
|
|
offset_size,
|
|
|
|
|
has_filters,
|
|
|
|
|
chunk_size_bytes,
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
write_undefined_element(&mut aedb, offset_size, has_filters, chunk_size_bytes);
|
|
|
|
|
let (ndblks, nelmts, first) = levels[*u];
|
|
|
|
|
let sb_start = out.len();
|
|
|
|
|
block_prefix(&mut out, b"EASB", first);
|
|
|
|
|
if nelmts > page_nelmts {
|
|
|
|
|
// Page-init bits, `npages` per data block, packed MSB-first
|
|
|
|
|
// (`H5VM_bit_set`): every page of an allocated data block is
|
|
|
|
|
// written.
|
|
|
|
|
let npages = nelmts / page_nelmts;
|
|
|
|
|
let mut bitmap = vec![0u8; sblk_bitmap_len(ndblks, nelmts)];
|
|
|
|
|
for (k, db) in dblks.iter().enumerate() {
|
|
|
|
|
if db.addr.is_some() {
|
|
|
|
|
for p in 0..npages {
|
|
|
|
|
let bit = k * npages + p;
|
|
|
|
|
bitmap[bit / 8] |= 0x80 >> (bit % 8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let aedb_checksum = jenkins_lookup3(&aedb);
|
|
|
|
|
aedb.extend_from_slice(&aedb_checksum.to_le_bytes());
|
|
|
|
|
|
|
|
|
|
dblk_cursor += aedb.len() as u64;
|
|
|
|
|
data_blocks_buf.extend_from_slice(&aedb);
|
|
|
|
|
chunk_idx += nelmts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Super block addresses (all undefined)
|
|
|
|
|
for _ in 0..n_sblk_addrs {
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => aeib.extend_from_slice(&u32::MAX.to_le_bytes()),
|
|
|
|
|
8 => aeib.extend_from_slice(&u64::MAX.to_le_bytes()),
|
|
|
|
|
_ => aeib.extend_from_slice(&u64::MAX.to_le_bytes()),
|
|
|
|
|
out.extend_from_slice(&bitmap);
|
|
|
|
|
}
|
|
|
|
|
for db in dblks {
|
|
|
|
|
write_addr_opt(&mut out, db.addr);
|
|
|
|
|
}
|
|
|
|
|
let sum = jenkins_lookup3(&out[sb_start..]);
|
|
|
|
|
out.extend_from_slice(&sum.to_le_bytes());
|
|
|
|
|
for db in dblks.iter().filter(|d| d.addr.is_some()) {
|
|
|
|
|
write_dblk(&mut out, db);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let aeib_checksum = jenkins_lookup3(&aeib);
|
|
|
|
|
aeib.extend_from_slice(&aeib_checksum.to_le_bytes());
|
|
|
|
|
debug_assert_eq!(aeib.len(), aeib_size);
|
|
|
|
|
|
|
|
|
|
let mut combined = aehd;
|
|
|
|
|
combined.extend_from_slice(&aeib);
|
|
|
|
|
combined.extend_from_slice(&data_blocks_buf);
|
|
|
|
|
combined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_chunk_element(
|
|
|
|
|
buf: &mut Vec<u8>,
|
|
|
|
|
chunk: &WrittenChunk,
|
|
|
|
|
offset_size: u8,
|
|
|
|
|
has_filters: bool,
|
|
|
|
|
chunk_size_bytes: usize,
|
|
|
|
|
) {
|
|
|
|
|
match offset_size {
|
|
|
|
|
4 => buf.extend_from_slice(&(chunk.address as u32).to_le_bytes()),
|
|
|
|
|
8 => buf.extend_from_slice(&chunk.address.to_le_bytes()),
|
|
|
|
|
_ => buf.extend_from_slice(&chunk.address.to_le_bytes()),
|
|
|
|
|
}
|
|
|
|
|
if has_filters {
|
|
|
|
|
let cs_bytes = chunk.compressed_size.to_le_bytes();
|
|
|
|
|
buf.extend_from_slice(&cs_bytes[..chunk_size_bytes]);
|
|
|
|
|
buf.extend_from_slice(&chunk.filter_mask.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_undefined_element(
|
|
|
|
|
buf: &mut Vec<u8>,
|
|
|
|
|
offset_size: u8,
|
|
|
|
|
has_filters: bool,
|
|
|
|
|
chunk_size_bytes: usize,
|
|
|
|
|
) {
|
|
|
|
|
let os = offset_size as usize;
|
|
|
|
|
// Use extend with repeat to avoid heap-allocating a temporary Vec on each call.
|
|
|
|
|
buf.extend(core::iter::repeat_n(0xFF, os));
|
|
|
|
|
if has_filters {
|
|
|
|
|
buf.extend(core::iter::repeat_n(0x00, chunk_size_bytes));
|
|
|
|
|
buf.extend_from_slice(&0u32.to_le_bytes());
|
|
|
|
|
}
|
|
|
|
|
debug_assert_eq!(out.len() as u64, cursor - ea_base_address);
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|