#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::convert::TryToUsize;
use crate::error::FormatError;
const FSHD_SIGNATURE: &[u8; 4] = b"FSHD";
const FSSE_SIGNATURE: &[u8; 4] = b"FSSE";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FreeSection {
pub addr: u64,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FsmHeader {
pub total_space: u64,
pub total_sections: u64,
pub addr_space_bits: u16,
pub max_section_size: u64,
pub fsse_addr: u64,
pub fsse_used: u64,
}
fn enc_size(value: u64) -> usize {
let bits = 64 - value.leading_zeros() as usize;
bits.div_ceil(8).max(1)
}
fn offset_width(addr_space_bits: u16) -> usize {
(addr_space_bits as usize).div_ceil(8)
}
fn read_uint_le(bytes: &[u8]) -> u64 {
let mut v = 0u64;
for (i, &b) in bytes.iter().enumerate() {
v |= (b as u64) << (8 * i);
}
v
}
impl FsmHeader {
pub(crate) fn parse(data: &[u8], offset_size: u8) -> Result<FsmHeader, FormatError> {
let os = offset_size as usize;
let need = 4 + 1 + 1 + 4 * 8 + 2 * 4 + 8 + os + 8 + 8 + 4;
if data.len() < need {
return Err(FormatError::UnexpectedEof {
expected: need,
available: data.len(),
});
}
if &data[0..4] != FSHD_SIGNATURE {
return Err(FormatError::InvalidFreeSpaceManager);
}
let mut pos = 6;
let total_space = read_uint_le(&data[pos..pos + 8]);
pos += 8;
let total_sections = read_uint_le(&data[pos..pos + 8]);
pos += 8;
pos += 16;
pos += 6;
let addr_space_bits = u16::from_le_bytes([data[pos], data[pos + 1]]);
pos += 2;
let max_section_size = read_uint_le(&data[pos..pos + 8]);
pos += 8;
let fsse_addr = read_uint_le(&data[pos..pos + os]);
pos += os;
let fsse_used = read_uint_le(&data[pos..pos + 8]);
Ok(FsmHeader {
total_space,
total_sections,
addr_space_bits,
max_section_size,
fsse_addr,
fsse_used,
})
}
}
const FILE_FSM_NUM_CLASSES: u16 = 3;
const FILE_FSM_SHRINK_PCT: u16 = 80;
const FILE_FSM_EXPAND_PCT: u16 = 120;
const FILE_FSM_CLIENT_ID: u8 = 1;
const FILE_FSM_MAX_SECTION_SIZE: u64 = (1u64 << 63) - 1;
fn push_uint_le(buf: &mut Vec<u8>, value: u64, width: usize) {
buf.extend_from_slice(&value.to_le_bytes()[..width]);
}
pub(crate) fn serialize_file_fsm(
sections: &[FreeSection],
fshd_addr: u64,
fsse_addr: u64,
offset_size: u8,
) -> (Vec<u8>, Vec<u8>) {
let os = offset_size as usize;
let addr_space_bits = (offset_size as u16) * 8 - 1;
let total_sections = sections.len() as u64;
let total_space: u64 = sections.iter().map(|s| s.size).sum();
let off_w = offset_width(addr_space_bits);
let size_w = enc_size(FILE_FSM_MAX_SECTION_SIZE);
let count_w = enc_size(total_sections);
let mut fsse = Vec::new();
fsse.extend_from_slice(FSSE_SIGNATURE);
fsse.push(0); push_uint_le(&mut fsse, fshd_addr, os);
let mut by_size: Vec<(u64, Vec<u64>)> = Vec::new();
for s in sections {
match by_size.iter_mut().find(|(size, _)| *size == s.size) {
Some((_, offsets)) => offsets.push(s.addr),
None => by_size.push((s.size, vec![s.addr])),
}
}
by_size.sort_by_key(|(size, _)| *size);
for (size, mut offsets) in by_size {
offsets.sort_unstable();
push_uint_le(&mut fsse, offsets.len() as u64, count_w);
push_uint_le(&mut fsse, size, size_w);
for addr in offsets {
push_uint_le(&mut fsse, addr, off_w);
fsse.push(0); }
}
let checksum = crate::checksum::jenkins_lookup3(&fsse);
fsse.extend_from_slice(&checksum.to_le_bytes());
let fsse_len = fsse.len() as u64;
let mut fshd = Vec::with_capacity(4 + 1 + 1 + 4 * 8 + 2 * 4 + 8 + os + 8 + 8 + 4);
fshd.extend_from_slice(FSHD_SIGNATURE);
fshd.push(0); fshd.push(FILE_FSM_CLIENT_ID);
push_uint_le(&mut fshd, total_space, 8);
push_uint_le(&mut fshd, total_sections, 8);
push_uint_le(&mut fshd, total_sections, 8); push_uint_le(&mut fshd, 0, 8); fshd.extend_from_slice(&FILE_FSM_NUM_CLASSES.to_le_bytes());
fshd.extend_from_slice(&FILE_FSM_SHRINK_PCT.to_le_bytes());
fshd.extend_from_slice(&FILE_FSM_EXPAND_PCT.to_le_bytes());
fshd.extend_from_slice(&addr_space_bits.to_le_bytes());
push_uint_le(&mut fshd, FILE_FSM_MAX_SECTION_SIZE, 8);
push_uint_le(&mut fshd, fsse_addr, os);
push_uint_le(&mut fshd, fsse_len, 8); push_uint_le(&mut fshd, fsse_len, 8); let checksum = crate::checksum::jenkins_lookup3(&fshd);
fshd.extend_from_slice(&checksum.to_le_bytes());
(fshd, fsse)
}
pub(crate) fn fshd_len(offset_size: u8) -> u64 {
(4 + 1 + 1 + 4 * 8 + 2 * 4 + 8 + offset_size as usize + 8 + 8 + 4) as u64
}
pub(crate) fn parse_fsse(
data: &[u8],
header: &FsmHeader,
offset_size: u8,
) -> Result<Vec<FreeSection>, FormatError> {
let os = offset_size as usize;
let header_len = 4 + 1 + os; if data.len() < header_len + 4 {
return Err(FormatError::UnexpectedEof {
expected: header_len + 4,
available: data.len(),
});
}
if &data[0..4] != FSSE_SIGNATURE {
return Err(FormatError::InvalidFreeSpaceManager);
}
let off_w = offset_width(header.addr_space_bits);
let size_w = enc_size(header.max_section_size);
let count_w = enc_size(header.total_sections);
if off_w == 0 || size_w == 0 || count_w == 0 {
return Err(FormatError::InvalidFreeSpaceManager);
}
let mut pos = header_len;
let payload_end = data.len() - 4; let total = header.total_sections.to_usize()?;
let mut sections = Vec::with_capacity(total);
while sections.len() < total {
if pos + count_w + size_w > payload_end {
return Err(FormatError::InvalidFreeSpaceManager);
}
let count = read_uint_le(&data[pos..pos + count_w]);
pos += count_w;
let size = read_uint_le(&data[pos..pos + size_w]);
pos += size_w;
for _ in 0..count {
if pos + off_w + 1 > payload_end || sections.len() >= total {
return Err(FormatError::InvalidFreeSpaceManager);
}
let addr = read_uint_le(&data[pos..pos + off_w]);
pos += off_w;
pos += 1;
sections.push(FreeSection { addr, size });
}
}
Ok(sections)
}
pub(crate) fn read_persisted_sections(
data: &[u8],
manager_addrs: &[u64],
base: u64,
offset_size: u8,
) -> Result<Vec<FreeSection>, FormatError> {
let bad = || FormatError::InvalidFreeSpaceManager;
let mut sections = Vec::new();
for &addr in manager_addrs {
if addr == u64::MAX {
continue;
}
let a = base.checked_add(addr).ok_or_else(bad)?.to_usize()?;
let header = FsmHeader::parse(data.get(a..).ok_or_else(bad)?, offset_size)?;
if header.fsse_addr == u64::MAX {
continue;
}
let fa = base
.checked_add(header.fsse_addr)
.ok_or_else(bad)?
.to_usize()?;
let end = fa
.checked_add(header.fsse_used.to_usize()?)
.ok_or_else(bad)?;
let block = data.get(fa..end).ok_or_else(bad)?;
sections.extend(parse_fsse(block, &header, offset_size)?);
}
Ok(sections)
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn parses_c_library_single_section() {
let fshd = bytes(
"46534844000140060000000000000100000000000000010000000000000000000000000000000300500078003f00ffffffffffffff7fbd0200000000000023000000000000002300000000000000ea133710",
);
let header = FsmHeader::parse(&fshd, 8).unwrap();
assert_eq!(header.total_space, 1600);
assert_eq!(header.total_sections, 1);
assert_eq!(header.addr_space_bits, 63);
assert_eq!(header.fsse_addr, 701);
assert_eq!(header.fsse_used, 35);
let fsse = bytes("46535345006b02000000000000014006000000000000200b000000000000005c797631");
let sections = parse_fsse(&fsse, &header, 8).unwrap();
assert_eq!(
sections,
vec![FreeSection {
addr: 2848,
size: 1600
}]
);
}
#[test]
fn parses_c_library_two_sections() {
let fshd = bytes(
"4653484400018d030000000000000200000000000000020000000000000000000000000000000300500078003f00ffffffffffffff7f320300000000000035000000000000003500000000000000d681e354",
);
let header = FsmHeader::parse(&fshd, 8).unwrap();
assert_eq!(header.total_space, 909);
assert_eq!(header.total_sections, 2);
assert_eq!(header.fsse_addr, 818);
assert_eq!(header.fsse_used, 53);
let fsse = bytes(
"4653534500e002000000000000011000000000000000670300000000000000017d03000000000000830400000000000000910245b2",
);
let mut sections = parse_fsse(&fsse, &header, 8).unwrap();
sections.sort_by_key(|s| s.addr);
assert_eq!(
sections,
vec![
FreeSection {
addr: 871,
size: 16
},
FreeSection {
addr: 1155,
size: 893
},
]
);
let total: u64 = sections.iter().map(|s| s.size).sum();
assert_eq!(header.total_space, total);
}
#[test]
fn read_persisted_sections_follows_managers() {
let fshd = bytes(
"46534844000140060000000000000100000000000000010000000000000000000000000000000300500078003f00ffffffffffffff7fbd0200000000000023000000000000002300000000000000ea133710",
);
let fsse = bytes("46535345006b02000000000000014006000000000000200b000000000000005c797631");
let mut buf = vec![0u8; 701 + fsse.len()];
buf[619..619 + fshd.len()].copy_from_slice(&fshd);
buf[701..701 + fsse.len()].copy_from_slice(&fsse);
let got = read_persisted_sections(&buf, &[619, u64::MAX, u64::MAX], 0, 8).unwrap();
assert_eq!(
got,
vec![FreeSection {
addr: 2848,
size: 1600
}]
);
assert!(
read_persisted_sections(&buf, &[u64::MAX], 0, 8)
.unwrap()
.is_empty()
);
}
#[test]
fn serialize_matches_c_library_single_section() {
let fshd_fixture = bytes(
"46534844000140060000000000000100000000000000010000000000000000000000000000000300500078003f00ffffffffffffff7fbd0200000000000023000000000000002300000000000000ea133710",
);
let fsse_fixture =
bytes("46535345006b02000000000000014006000000000000200b000000000000005c797631");
let (fshd, fsse) = serialize_file_fsm(
&[FreeSection {
addr: 2848,
size: 1600,
}],
619,
701,
8,
);
assert_eq!(fshd, fshd_fixture, "FSHD bytes match the C library");
assert_eq!(fsse, fsse_fixture, "FSSE bytes match the C library");
assert_eq!(fshd_len(8), fshd.len() as u64);
}
#[test]
fn serialize_matches_c_library_two_sections() {
let fshd_fixture = bytes(
"4653484400018d030000000000000200000000000000020000000000000000000000000000000300500078003f00ffffffffffffff7f320300000000000035000000000000003500000000000000d681e354",
);
let fsse_fixture = bytes(
"4653534500e002000000000000011000000000000000670300000000000000017d03000000000000830400000000000000910245b2",
);
let (fshd, fsse) = serialize_file_fsm(
&[
FreeSection {
addr: 1155,
size: 893,
},
FreeSection {
addr: 871,
size: 16,
},
],
736,
818,
8,
);
assert_eq!(fshd, fshd_fixture, "FSHD bytes match the C library");
assert_eq!(fsse, fsse_fixture, "FSSE bytes match the C library");
}
#[test]
fn serialize_roundtrips_through_parse() {
let sections = [
FreeSection {
addr: 4096,
size: 512,
},
FreeSection {
addr: 9000,
size: 512,
},
FreeSection {
addr: 20000,
size: 70000,
},
];
let (fshd, _fsse) = serialize_file_fsm(§ions, 1000, 1100, 8);
let header = FsmHeader::parse(&fshd, 8).unwrap();
assert_eq!(header.total_sections, 3);
assert_eq!(header.total_space, 512 + 512 + 70000);
assert_eq!(header.fsse_addr, 1100);
let (fshd, fsse) = serialize_file_fsm(§ions, 1000, 1100, 8);
let mut buf = vec![0u8; 1100 + fsse.len()];
buf[1000..1000 + fshd.len()].copy_from_slice(&fshd);
buf[1100..1100 + fsse.len()].copy_from_slice(&fsse);
let mut got = read_persisted_sections(&buf, &[1000], 0, 8).unwrap();
got.sort_by_key(|s| s.addr);
let mut want = sections.to_vec();
want.sort_by_key(|s| s.addr);
assert_eq!(got, want);
}
#[test]
fn enc_size_matches_reference() {
assert_eq!(enc_size(0), 1);
assert_eq!(enc_size(255), 1);
assert_eq!(enc_size(256), 2);
assert_eq!(enc_size((1 << 63) - 1), 8);
}
#[test]
fn rejects_bad_signature() {
let mut fshd = bytes(
"46534844000140060000000000000100000000000000010000000000000000000000000000000300500078003f00ffffffffffffff7fbd0200000000000023000000000000002300000000000000ea133710",
);
fshd[0] = b'X';
assert!(matches!(
FsmHeader::parse(&fshd, 8),
Err(FormatError::InvalidFreeSpaceManager)
));
}
}