use crate::format::bytes::read_le_uint as read_uint;
use crate::format::checksum::checksum_metadata;
use crate::format::messages::superblock_ext::{
FileSpaceInfoMessage, FileSpaceStrategy, PAGE_SIZE_MIN,
};
use crate::format::{FormatContext, FormatError, FormatResult};
pub const FSHD_SIGNATURE: [u8; 4] = *b"FSHD";
pub const FSSE_SIGNATURE: [u8; 4] = *b"FSSE";
const FS_VERSION: u8 = 0;
pub const CLIENT_FILE: u8 = 1;
pub const SECT_CLASS_SIMPLE: u8 = 0;
pub const SECT_CLASS_SMALL: u8 = 1;
pub const SECT_CLASS_LARGE: u8 = 2;
pub const FILE_SECT_CLASSES: u16 = 3;
pub const SHRINK_PERCENT: u16 = 80;
pub const EXPAND_PERCENT: u16 = 120;
pub const SEC2_MAXADDR: u64 = (1 << 63) - 1;
pub const SEC2_MAX_SECT_ADDR: u16 = 63;
fn log2_gen(n: u64) -> u32 {
63u32.saturating_sub(n.leading_zeros().min(63))
}
fn limit_enc_size(limit: u64) -> usize {
(log2_gen(limit) / 8) as usize + 1
}
fn read_var(buf: &[u8], n: usize) -> u64 {
read_uint(buf, n)
}
fn write_var(buf: &mut Vec<u8>, v: u64, n: usize) {
buf.extend_from_slice(&v.to_le_bytes()[..n]);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FreeSpaceClass {
Metadata,
RawData,
}
impl FreeSpaceClass {
pub const ALL: [Self; 2] = [Self::Metadata, Self::RawData];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FreeSpaceManager {
Metadata,
RawData,
Large,
}
impl FreeSpaceManager {
pub const ALL: [Self; 3] = [Self::Metadata, Self::RawData, Self::Large];
pub fn message_slot(self) -> usize {
match self {
Self::Metadata => 0,
Self::RawData => 2,
Self::Large => 6,
}
}
pub fn from_message_slot(slot: usize) -> Option<Self> {
Self::ALL.into_iter().find(|m| m.message_slot() == slot)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpacePolicy {
Aggr,
Paged {
page: u64,
},
}
impl SpacePolicy {
pub fn for_message(info: &FileSpaceInfoMessage) -> Self {
match info.strategy {
FileSpaceStrategy::Page if info.page_size >= PAGE_SIZE_MIN => Self::Paged {
page: info.page_size,
},
_ => Self::Aggr,
}
}
pub fn page(self) -> Option<u64> {
match self {
Self::Aggr => None,
Self::Paged { page } => Some(page),
}
}
pub fn manager(self, class: FreeSpaceClass, size: u64) -> FreeSpaceManager {
match self {
Self::Paged { page } if size >= page => FreeSpaceManager::Large,
_ => match class {
FreeSpaceClass::Metadata => FreeSpaceManager::Metadata,
FreeSpaceClass::RawData => FreeSpaceManager::RawData,
},
}
}
pub fn section_class(self, manager: FreeSpaceManager) -> u8 {
match (self, manager) {
(Self::Aggr, _) => SECT_CLASS_SIMPLE,
(Self::Paged { .. }, FreeSpaceManager::Large) => SECT_CLASS_LARGE,
(Self::Paged { .. }, _) => SECT_CLASS_SMALL,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FreeSection {
pub addr: u64,
pub len: u64,
pub class: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FreeSpaceHeader {
pub client: u8,
pub total_space: u64,
pub total_sections: u64,
pub serial_sections: u64,
pub ghost_sections: u64,
pub nclasses: u16,
pub shrink_percent: u16,
pub expand_percent: u16,
pub max_sect_addr: u16,
pub max_sect_size: u64,
pub sect_addr: u64,
pub sect_size: u64,
pub alloc_sect_size: u64,
}
impl FreeSpaceHeader {
pub fn encoded_size(ctx: &FormatContext) -> usize {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
4 + 1 + 4 + 1 + 4 * ss + 4 * 2 + ss + sa + ss + ss
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let size = Self::encoded_size(ctx);
need(buf, size)?;
if buf[..4] != FSHD_SIGNATURE {
return Err(FormatError::InvalidData(
"free-space header does not start with FSHD".into(),
));
}
if buf[4] != FS_VERSION {
return Err(FormatError::InvalidVersion(buf[4]));
}
verify_checksum(&buf[..size], "free-space header")?;
let mut pos = 5;
let client = buf[pos];
pos += 1;
let take_size = |pos: &mut usize| {
let v = read_uint(&buf[*pos..], ss);
*pos += ss;
v
};
let total_space = take_size(&mut pos);
let total_sections = take_size(&mut pos);
let serial_sections = take_size(&mut pos);
let ghost_sections = take_size(&mut pos);
let take_u16 = |pos: &mut usize| {
let v = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]);
*pos += 2;
v
};
let nclasses = take_u16(&mut pos);
let shrink_percent = take_u16(&mut pos);
let expand_percent = take_u16(&mut pos);
let max_sect_addr = take_u16(&mut pos);
let max_sect_size = read_uint(&buf[pos..], ss);
pos += ss;
let sect_addr = read_uint(&buf[pos..], sa);
pos += sa;
let sect_size = read_uint(&buf[pos..], ss);
pos += ss;
let alloc_sect_size = read_uint(&buf[pos..], ss);
Ok(Self {
client,
total_space,
total_sections,
serial_sections,
ghost_sections,
nclasses,
shrink_percent,
expand_percent,
max_sect_addr,
max_sect_size,
sect_addr,
sect_size,
alloc_sect_size,
})
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let mut buf = Vec::with_capacity(Self::encoded_size(ctx));
buf.extend_from_slice(&FSHD_SIGNATURE);
buf.push(FS_VERSION);
buf.push(self.client);
for v in [
self.total_space,
self.total_sections,
self.serial_sections,
self.ghost_sections,
] {
buf.extend_from_slice(&v.to_le_bytes()[..ss]);
}
for v in [
self.nclasses,
self.shrink_percent,
self.expand_percent,
self.max_sect_addr,
] {
buf.extend_from_slice(&v.to_le_bytes());
}
buf.extend_from_slice(&self.max_sect_size.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.sect_addr.to_le_bytes()[..sa]);
buf.extend_from_slice(&self.sect_size.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.alloc_sect_size.to_le_bytes()[..ss]);
let sum = checksum_metadata(&buf);
buf.extend_from_slice(&sum.to_le_bytes());
buf
}
fn sect_off_size(&self) -> usize {
(self.max_sect_addr as usize).div_ceil(8)
}
fn sect_len_size(&self) -> usize {
limit_enc_size(self.max_sect_size)
}
fn sect_cnt_size(&self) -> usize {
limit_enc_size(self.serial_sections)
}
}
pub fn serialization_order(sections: &[FreeSection]) -> Vec<FreeSection> {
let mut ordered = sections.to_vec();
ordered.sort_unstable_by_key(|s| (s.len, s.addr));
ordered
}
pub fn sinfo_encoded_size(
hdr: &FreeSpaceHeader,
sections: &[FreeSection],
ctx: &FormatContext,
) -> u64 {
let prefix = 4 + 1 + ctx.sizeof_addr as u64 + 4;
if sections.is_empty() {
return prefix;
}
let distinct = distinct_sizes(sections) as u64;
let n = sections.len() as u64;
prefix
+ distinct * (hdr.sect_cnt_size() + hdr.sect_len_size()) as u64
+ n * (hdr.sect_off_size() as u64 + 1)
}
fn distinct_sizes(sections: &[FreeSection]) -> usize {
let mut count = 0;
let mut last = None;
for s in sections {
if last != Some(s.len) {
count += 1;
last = Some(s.len);
}
}
count
}
pub fn encode_sections(
hdr: &FreeSpaceHeader,
hdr_addr: u64,
sections: &[FreeSection],
image_len: usize,
ctx: &FormatContext,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(image_len);
buf.extend_from_slice(&FSSE_SIGNATURE);
buf.push(FS_VERSION);
buf.extend_from_slice(&hdr_addr.to_le_bytes()[..ctx.sizeof_addr as usize]);
let cnt_size = hdr.sect_cnt_size();
let len_size = hdr.sect_len_size();
let off_size = hdr.sect_off_size();
let mut i = 0;
while i < sections.len() {
let size = sections[i].len;
let run = sections[i..].iter().take_while(|s| s.len == size).count();
write_var(&mut buf, run as u64, cnt_size);
write_var(&mut buf, size, len_size);
for s in §ions[i..i + run] {
write_var(&mut buf, s.addr, off_size);
buf.push(s.class);
}
i += run;
}
buf.resize(image_len - 4, 0);
let sum = checksum_metadata(&buf);
buf.extend_from_slice(&sum.to_le_bytes());
buf
}
pub fn decode_sections(
buf: &[u8],
hdr: &FreeSpaceHeader,
hdr_addr: u64,
ctx: &FormatContext,
) -> FormatResult<Vec<FreeSection>> {
let sa = ctx.sizeof_addr as usize;
need(buf, 4 + 1 + sa + 4)?;
if buf[..4] != FSSE_SIGNATURE {
return Err(FormatError::InvalidData(
"free-space sections block does not start with FSSE".into(),
));
}
if buf[4] != FS_VERSION {
return Err(FormatError::InvalidVersion(buf[4]));
}
verify_checksum(buf, "free-space sections block")?;
let named = read_uint(&buf[5..], sa);
if named != hdr_addr {
return Err(FormatError::InvalidData(format!(
"free-space sections at name header {named:#x}, not {hdr_addr:#x}"
)));
}
let mut sections = Vec::with_capacity(
usize::try_from(hdr.serial_sections)
.unwrap_or(usize::MAX)
.min(buf.len() / (hdr.sect_off_size() + 1)),
);
if hdr.serial_sections == 0 {
return Ok(sections);
}
let cnt_size = hdr.sect_cnt_size();
let len_size = hdr.sect_len_size();
let off_size = hdr.sect_off_size();
let end = buf.len() - 4;
let mut pos = 5 + sa;
while sections.len() < hdr.serial_sections as usize {
need(buf, pos + cnt_size + len_size)?;
let run = read_var(&buf[pos..], cnt_size);
pos += cnt_size;
let size = read_var(&buf[pos..], len_size);
pos += len_size;
if run == 0 || size == 0 {
return Err(FormatError::InvalidData(
"free-space sections block declares an empty size node".into(),
));
}
for _ in 0..run {
if pos + off_size + 1 > end {
return Err(FormatError::InvalidData(
"free-space sections block ends inside a section record".into(),
));
}
let addr = read_var(&buf[pos..], off_size);
pos += off_size;
let class = buf[pos];
pos += 1;
if class >= hdr.nclasses as u8 && hdr.nclasses != 0 {
return Err(FormatError::InvalidData(format!(
"free-space section class {class} is beyond the {} the manager declares",
hdr.nclasses
)));
}
sections.push(FreeSection {
addr,
len: size,
class,
});
}
}
Ok(sections)
}
pub fn check_disjoint(blocks: &[(u64, u64)]) -> Result<(), String> {
let mut sorted: Vec<(u64, u64)> = blocks.iter().copied().filter(|b| b.1 > 0).collect();
sorted.sort_unstable();
for pair in sorted.windows(2) {
let ((addr, len), (next, next_len)) = (pair[0], pair[1]);
if addr + len > next {
return Err(format!(
"free block {next:#x}+{next_len} overlaps {addr:#x}+{len}"
));
}
}
Ok(())
}
fn verify_checksum(buf: &[u8], what: &str) -> FormatResult<()> {
let split = buf.len() - 4;
let stored = u32::from_le_bytes([buf[split], buf[split + 1], buf[split + 2], buf[split + 3]]);
let computed = checksum_metadata(&buf[..split]);
if stored != computed {
return Err(FormatError::InvalidData(format!(
"{what} checksum {stored:#010x} does not match the computed {computed:#010x}"
)));
}
Ok(())
}
fn need(buf: &[u8], n: usize) -> FormatResult<()> {
if buf.len() < n {
Err(FormatError::BufferTooShort {
needed: n,
available: buf.len(),
})
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> FormatContext {
FormatContext::default_v3()
}
const FIXTURE_HDR: [u8; 82] = [
0x46, 0x53, 0x48, 0x44, 0x00, 0x01, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0x00, 0x78, 0x00, 0x3f,
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x84, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xe2, 0xcf, 0x1b, 0xf4,
];
const FIXTURE_SINFO: [u8; 71] = [
0x46, 0x53, 0x53, 0x45, 0x00, 0x32, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xf2, 0xb9, 0x34,
];
fn fixture_header() -> FreeSpaceHeader {
FreeSpaceHeader::decode(&FIXTURE_HDR, &ctx()).unwrap()
}
#[test]
fn header_size_matches_upstream() {
assert_eq!(FreeSpaceHeader::encoded_size(&ctx()), 82);
}
#[test]
fn decodes_the_libhdf5_header() {
let h = fixture_header();
assert_eq!(h.client, CLIENT_FILE);
assert_eq!(h.total_space, 62);
assert_eq!(h.total_sections, 3);
assert_eq!(h.serial_sections, 3);
assert_eq!(h.ghost_sections, 0);
assert_eq!(h.nclasses, FILE_SECT_CLASSES);
assert_eq!(h.shrink_percent, SHRINK_PERCENT);
assert_eq!(h.expand_percent, EXPAND_PERCENT);
assert_eq!(h.max_sect_addr, SEC2_MAX_SECT_ADDR);
assert_eq!(h.max_sect_size, SEC2_MAXADDR);
assert_eq!(h.sect_addr, 1668);
assert_eq!(h.sect_size, 71);
assert_eq!(h.alloc_sect_size, 71);
}
#[test]
fn header_reencodes_byte_for_byte() {
assert_eq!(fixture_header().encode(&ctx()), FIXTURE_HDR);
}
#[test]
fn decodes_the_libhdf5_sections() {
let h = fixture_header();
let s = decode_sections(&FIXTURE_SINFO, &h, 1586, &ctx()).unwrap();
assert_eq!(
s,
vec![
FreeSection {
addr: 2038,
len: 10,
class: SECT_CLASS_SIMPLE
},
FreeSection {
addr: 185,
len: 15,
class: SECT_CLASS_SIMPLE
},
FreeSection {
addr: 1739,
len: 37,
class: SECT_CLASS_SIMPLE
},
]
);
assert_eq!(s.iter().map(|x| x.len).sum::<u64>(), h.total_space);
}
#[test]
fn sections_reencode_byte_for_byte() {
let h = fixture_header();
let s = decode_sections(&FIXTURE_SINFO, &h, 1586, &ctx()).unwrap();
let ordered = serialization_order(&s);
assert_eq!(sinfo_encoded_size(&h, &ordered, &ctx()), h.sect_size);
assert_eq!(
encode_sections(&h, 1586, &ordered, h.sect_size as usize, &ctx()),
FIXTURE_SINFO
);
}
#[test]
fn a_corrupt_header_checksum_is_rejected() {
let mut buf = FIXTURE_HDR;
buf[10] ^= 0xff;
assert!(matches!(
FreeSpaceHeader::decode(&buf, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
}
#[test]
fn a_sections_block_naming_the_wrong_header_is_rejected() {
let h = fixture_header();
assert!(matches!(
decode_sections(&FIXTURE_SINFO, &h, 99, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
}
#[test]
fn empty_managers_serialize_to_the_prefix_alone() {
let h = fixture_header();
assert_eq!(sinfo_encoded_size(&h, &[], &ctx()), 17);
}
#[test]
fn disjoint_accepts_adjacent_blocks_and_rejects_overlapping_ones() {
assert!(check_disjoint(&[(64, 16), (16, 16), (32, 32), (200, 8)]).is_ok());
assert!(check_disjoint(&[(16, 32), (32, 8)]).is_err());
assert!(check_disjoint(&[(16, 0), (16, 8)]).is_ok());
}
#[test]
fn encoding_widths_follow_the_header() {
let h = fixture_header();
assert_eq!(h.sect_off_size(), 8);
assert_eq!(h.sect_len_size(), 8);
assert_eq!(h.sect_cnt_size(), 1);
assert_eq!(limit_enc_size(0), 1);
assert_eq!(limit_enc_size(255), 1);
assert_eq!(limit_enc_size(256), 2);
assert_eq!(log2_gen(0), 0);
assert_eq!(log2_gen(1), 0);
assert_eq!(log2_gen(SEC2_MAXADDR), 62);
}
#[test]
fn a_padded_image_checksums_over_the_whole_block() {
let h = fixture_header();
let s = vec![FreeSection {
addr: 4096,
len: 24,
class: SECT_CLASS_SIMPLE,
}];
let image = encode_sections(&h, 1586, &s, 64, &ctx());
assert_eq!(image.len(), 64);
let mut padded = h;
padded.serial_sections = 1;
padded.sect_size = 64;
assert_eq!(decode_sections(&image, &padded, 1586, &ctx()).unwrap(), s);
}
}