#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::error::FormatError;
const UNDEF: u64 = u64::MAX;
pub(crate) const DEFAULT_THRESHOLD: u64 = 1;
pub(crate) const DEFAULT_PAGE_SIZE: u64 = 4096;
pub(crate) const NUM_FILE_FSM_MANAGERS: usize = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileSpaceStrategy {
FsmAggr,
Page,
Aggr,
None,
}
impl FileSpaceStrategy {
pub(crate) fn to_code(self) -> u8 {
match self {
FileSpaceStrategy::FsmAggr => 0,
FileSpaceStrategy::Page => 1,
FileSpaceStrategy::Aggr => 2,
FileSpaceStrategy::None => 3,
}
}
fn from_code(code: u8) -> Result<Self, FormatError> {
match code {
0 => Ok(FileSpaceStrategy::FsmAggr),
1 => Ok(FileSpaceStrategy::Page),
2 => Ok(FileSpaceStrategy::Aggr),
3 => Ok(FileSpaceStrategy::None),
other => Err(FormatError::InvalidFileSpaceStrategy(other)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileSpaceInfo {
pub strategy: FileSpaceStrategy,
pub persist: bool,
pub threshold: u64,
pub page_size: u64,
pub page_end_meta_threshold: u16,
pub eoa_pre_fsm: u64,
pub manager_addrs: Vec<u64>,
}
impl FileSpaceInfo {
pub(crate) fn non_persistent(
strategy: FileSpaceStrategy,
threshold: u64,
page_size: u64,
) -> Self {
FileSpaceInfo {
strategy,
persist: false,
threshold,
page_size,
page_end_meta_threshold: 0,
eoa_pre_fsm: UNDEF,
manager_addrs: Vec::new(),
}
}
pub(crate) fn persistent_empty(
strategy: FileSpaceStrategy,
threshold: u64,
page_size: u64,
) -> Self {
FileSpaceInfo {
strategy,
persist: true,
threshold,
page_size,
page_end_meta_threshold: 0,
eoa_pre_fsm: UNDEF,
manager_addrs: vec![UNDEF; NUM_FILE_FSM_MANAGERS],
}
}
pub(crate) fn persistent_single_manager(
strategy: FileSpaceStrategy,
threshold: u64,
page_size: u64,
manager0_addr: u64,
eoa_pre_fsm: u64,
) -> Self {
let mut manager_addrs = vec![UNDEF; NUM_FILE_FSM_MANAGERS];
manager_addrs[0] = manager0_addr;
FileSpaceInfo {
strategy,
persist: true,
threshold,
page_size,
page_end_meta_threshold: 0,
eoa_pre_fsm,
manager_addrs,
}
}
pub(crate) fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(29 + self.manager_addrs.len() * 8);
buf.push(1); buf.push(self.strategy.to_code());
buf.push(self.persist as u8);
buf.extend_from_slice(&self.threshold.to_le_bytes());
buf.extend_from_slice(&self.page_size.to_le_bytes());
buf.extend_from_slice(&self.page_end_meta_threshold.to_le_bytes());
buf.extend_from_slice(&self.eoa_pre_fsm.to_le_bytes());
if self.persist {
for &addr in &self.manager_addrs {
buf.extend_from_slice(&addr.to_le_bytes());
}
}
buf
}
pub(crate) fn parse(
data: &[u8],
offset_size: u8,
length_size: u8,
) -> Result<FileSpaceInfo, FormatError> {
let os = offset_size as usize;
let ls = length_size as usize;
let fixed = 3 + ls + ls + 2 + os;
if data.len() < fixed {
return Err(FormatError::UnexpectedEof {
expected: fixed,
available: data.len(),
});
}
let version = data[0];
if version != 1 {
return Err(FormatError::UnsupportedFileSpaceInfoVersion(version));
}
let strategy = FileSpaceStrategy::from_code(data[1])?;
let persist = data[2] != 0;
let mut pos = 3;
let threshold = read_uint_le(&data[pos..pos + ls]);
pos += ls;
let page_size = read_uint_le(&data[pos..pos + ls]);
pos += ls;
let page_end_meta_threshold = u16::from_le_bytes([data[pos], data[pos + 1]]);
pos += 2;
let eoa_pre_fsm = read_uint_le(&data[pos..pos + os]);
pos += os;
let mut manager_addrs = Vec::new();
if persist {
while pos + os <= data.len() {
manager_addrs.push(read_uint_le(&data[pos..pos + os]));
pos += os;
}
}
Ok(FileSpaceInfo {
strategy,
persist,
threshold,
page_size,
page_end_meta_threshold,
eoa_pre_fsm,
manager_addrs,
})
}
}
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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_persistent_roundtrip_29_bytes() {
for strategy in [
FileSpaceStrategy::FsmAggr,
FileSpaceStrategy::Page,
FileSpaceStrategy::Aggr,
FileSpaceStrategy::None,
] {
let info = FileSpaceInfo::non_persistent(strategy, 1, 4096);
let bytes = info.serialize();
assert_eq!(bytes.len(), 29, "non-persistent message is 29 bytes");
let parsed = FileSpaceInfo::parse(&bytes, 8, 8).unwrap();
assert_eq!(parsed, info);
assert_eq!(parsed.eoa_pre_fsm, u64::MAX);
assert!(parsed.manager_addrs.is_empty());
}
}
#[test]
fn matches_c_library_none_bytes() {
let expected = [
0x01u8, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x10, 0, 0, 0, 0, 0, 0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ];
let info = FileSpaceInfo::non_persistent(FileSpaceStrategy::None, 1, 4096);
assert_eq!(info.serialize(), expected);
}
#[test]
fn parses_persistent_manager_addresses() {
let mut bytes = FileSpaceInfo {
strategy: FileSpaceStrategy::FsmAggr,
persist: true,
threshold: 1,
page_size: 4096,
page_end_meta_threshold: 0,
eoa_pre_fsm: 2072,
manager_addrs: vec![619, u64::MAX, u64::MAX],
}
.serialize();
assert_eq!(bytes.len(), 29 + 3 * 8);
let parsed = FileSpaceInfo::parse(&bytes, 8, 8).unwrap();
assert_eq!(parsed.manager_addrs, vec![619, u64::MAX, u64::MAX]);
assert_eq!(parsed.eoa_pre_fsm, 2072);
assert!(parsed.persist);
bytes[0] = 0;
assert!(matches!(
FileSpaceInfo::parse(&bytes, 8, 8),
Err(FormatError::UnsupportedFileSpaceInfoVersion(0))
));
}
#[test]
fn rejects_bad_strategy_code() {
assert!(matches!(
FileSpaceStrategy::from_code(4),
Err(FormatError::InvalidFileSpaceStrategy(4))
));
}
}