use crate::format::bytes::read_le_uint as read_uint;
use crate::format::{FormatContext, FormatError, FormatResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SharedMessageTableMessage {
pub version: u8,
pub table_address: u64,
pub nindexes: u8,
}
impl SharedMessageTableMessage {
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
need(buf, 1 + sa + 1)?;
let version = buf[0];
if version != 0 {
return Err(FormatError::InvalidVersion(version));
}
Ok(Self {
version,
table_address: read_uint(&buf[1..], sa),
nindexes: buf[1 + sa],
})
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let mut buf = Vec::with_capacity(1 + sa + 1);
buf.push(self.version);
buf.extend_from_slice(&self.table_address.to_le_bytes()[..sa]);
buf.push(self.nindexes);
buf
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BtreeKMessage {
pub chunk_internal_k: u16,
pub snode_internal_k: u16,
pub sym_leaf_k: u16,
}
impl BtreeKMessage {
pub fn decode(buf: &[u8]) -> FormatResult<Self> {
need(buf, 1 + 6)?;
let version = buf[0];
if version != 0 {
return Err(FormatError::InvalidVersion(version));
}
Ok(Self {
chunk_internal_k: u16::from_le_bytes([buf[1], buf[2]]),
snode_internal_k: u16::from_le_bytes([buf[3], buf[4]]),
sym_leaf_k: u16::from_le_bytes([buf[5], buf[6]]),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DriverInfoMessage {
pub name: String,
pub data: Vec<u8>,
}
impl DriverInfoMessage {
pub fn decode(buf: &[u8]) -> FormatResult<Self> {
need(buf, 1 + 8 + 2)?;
let version = buf[0];
if version != 0 {
return Err(FormatError::InvalidVersion(version));
}
let name_bytes: Vec<u8> = buf[1..9].iter().copied().take_while(|&b| b != 0).collect();
let name = String::from_utf8_lossy(&name_bytes).into_owned();
let len = u16::from_le_bytes([buf[9], buf[10]]) as usize;
if len == 0 {
return Err(FormatError::InvalidData(
"driver info message declares a zero-length payload".into(),
));
}
need(buf, 11 + len)?;
Ok(Self {
name,
data: buf[11..11 + len].to_vec(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileSpaceStrategy {
FsmAggr,
Page,
Aggr,
None,
Unknown(u8),
}
impl FileSpaceStrategy {
fn from_byte(b: u8) -> Self {
match b {
0 => Self::FsmAggr,
1 => Self::Page,
2 => Self::Aggr,
3 => Self::None,
other => Self::Unknown(other),
}
}
fn to_byte(self) -> u8 {
match self {
Self::FsmAggr => 0,
Self::Page => 1,
Self::Aggr => 2,
Self::None => 3,
Self::Unknown(b) => b,
}
}
}
pub const FS_ADDR_COUNT_V1: usize = 12;
const FS_ADDR_COUNT_V0: usize = 6;
pub const PAGE_SIZE_MAX: u64 = 1024 * 1024 * 1024;
pub const PAGE_SIZE_MIN: u64 = 512;
pub(crate) const DEFAULT_FILE_SPACE_PAGE_SIZE: u64 = 4096;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileSpaceInfoMessage {
pub version: u8,
pub strategy: FileSpaceStrategy,
pub persist: bool,
pub threshold: u64,
pub page_size: u64,
pub pgend_meta_thres: u16,
pub eoa_pre_fsm_fsalloc: u64,
pub fs_addr: Vec<u64>,
}
impl FileSpaceInfoMessage {
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
need(buf, 1)?;
let version = buf[0];
let mut pos = 1;
if version == 0 {
need(buf, pos + 1 + ss)?;
let legacy_strategy = buf[pos];
pos += 1;
let threshold = read_uint(&buf[pos..], ss);
pos += ss;
let (strategy, persist, threshold) = match legacy_strategy {
1 => (FileSpaceStrategy::FsmAggr, true, threshold),
2 => (FileSpaceStrategy::FsmAggr, false, threshold),
3 => (FileSpaceStrategy::Aggr, false, 1),
4 => (FileSpaceStrategy::None, false, 1),
other => {
return Err(FormatError::InvalidData(format!(
"invalid file-space strategy {other} in version-0 file-space info message"
)))
}
};
let mut fs_addr = vec![crate::format::UNDEF_ADDR; FS_ADDR_COUNT_V1];
if persist {
need(buf, pos + FS_ADDR_COUNT_V0 * sa)?;
for slot in fs_addr.iter_mut().take(FS_ADDR_COUNT_V0) {
*slot = read_uint(&buf[pos..], sa);
pos += sa;
}
}
return Ok(Self {
version: 0,
strategy,
persist,
threshold,
page_size: DEFAULT_FILE_SPACE_PAGE_SIZE,
pgend_meta_thres: 0,
eoa_pre_fsm_fsalloc: crate::format::UNDEF_ADDR,
fs_addr,
});
}
if version != 1 {
return Err(FormatError::InvalidVersion(version));
}
need(buf, pos + 2 + ss + ss + 2 + sa)?;
let strategy = FileSpaceStrategy::from_byte(buf[pos]);
pos += 1;
let persist = buf[pos] != 0;
pos += 1;
let threshold = read_uint(&buf[pos..], ss);
pos += ss;
let page_size = read_uint(&buf[pos..], ss);
pos += ss;
if page_size == 0 || page_size > PAGE_SIZE_MAX {
return Err(FormatError::InvalidData(format!(
"invalid file-space page size {page_size}"
)));
}
let pgend_meta_thres = u16::from_le_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
let eoa_pre_fsm_fsalloc = read_uint(&buf[pos..], sa);
pos += sa;
let mut fs_addr = vec![crate::format::UNDEF_ADDR; FS_ADDR_COUNT_V1];
if persist {
need(buf, pos + FS_ADDR_COUNT_V1 * sa)?;
for slot in fs_addr.iter_mut() {
*slot = read_uint(&buf[pos..], sa);
pos += sa;
}
}
Ok(Self {
version: 1,
strategy,
persist,
threshold,
page_size,
pgend_meta_thres,
eoa_pre_fsm_fsalloc,
fs_addr,
})
}
pub fn encode(&self, ctx: &FormatContext) -> FormatResult<Vec<u8>> {
if self.version == 0 {
return self.encode_v0(ctx);
}
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let mut buf = Vec::with_capacity(3 + 2 * ss + 2 + sa + FS_ADDR_COUNT_V1 * sa);
buf.push(1);
buf.push(self.strategy.to_byte());
buf.push(self.persist as u8);
buf.extend_from_slice(&self.threshold.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.page_size.to_le_bytes()[..ss]);
buf.extend_from_slice(&self.pgend_meta_thres.to_le_bytes());
buf.extend_from_slice(&self.eoa_pre_fsm_fsalloc.to_le_bytes()[..sa]);
if self.persist {
for addr in &self.fs_addr {
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
}
Ok(buf)
}
pub fn encode_v0(&self, ctx: &FormatContext) -> FormatResult<Vec<u8>> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let legacy = match (self.strategy, self.persist) {
(FileSpaceStrategy::FsmAggr, true) => 1,
(FileSpaceStrategy::FsmAggr, false) => 2,
(FileSpaceStrategy::Aggr, false) => 3,
(FileSpaceStrategy::None, false) => 4,
(strategy, persist) => {
return Err(FormatError::InvalidData(format!(
"strategy {strategy:?} with persist {persist} has no version-0 \
file-space info encoding"
)))
}
};
if legacy > 2 && self.threshold != 1 {
return Err(FormatError::InvalidData(format!(
"version-0 file-space strategy {legacy} cannot carry threshold {}",
self.threshold
)));
}
if self.fs_addr.len() > FS_ADDR_COUNT_V0
&& self.fs_addr[FS_ADDR_COUNT_V0..]
.iter()
.any(|&a| a != crate::format::UNDEF_ADDR)
{
return Err(FormatError::InvalidData(
"a version-0 file-space info message has no slot for a page-type \
free-space manager"
.into(),
));
}
let mut buf = Vec::with_capacity(2 + ss + FS_ADDR_COUNT_V0 * sa);
buf.push(0);
buf.push(legacy);
buf.extend_from_slice(&self.threshold.to_le_bytes()[..ss]);
if self.persist {
let undef = [crate::format::UNDEF_ADDR];
for slot in 0..FS_ADDR_COUNT_V0 {
let addr = self.fs_addr.get(slot).unwrap_or(&undef[0]);
buf.extend_from_slice(&addr.to_le_bytes()[..sa]);
}
}
Ok(buf)
}
}
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()
}
#[test]
fn shmesg_roundtrip() {
let mut buf = vec![0u8];
buf.extend_from_slice(&88u64.to_le_bytes());
buf.push(3);
let m = SharedMessageTableMessage::decode(&buf, &ctx()).unwrap();
assert_eq!(m.table_address, 88);
assert_eq!(m.nindexes, 3);
}
#[test]
fn shmesg_encodes_the_fixture_body() {
let m = SharedMessageTableMessage {
version: 0,
table_address: 88,
nindexes: 1,
};
assert_eq!(
m.encode(&ctx()),
vec![0, 0x58, 0, 0, 0, 0, 0, 0, 0, 1],
"H5O__shmesg_encode order: version, table address, index count"
);
assert_eq!(
SharedMessageTableMessage::decode(&m.encode(&ctx()), &ctx()).unwrap(),
m
);
}
#[test]
fn shmesg_rejects_bad_version() {
let mut buf = vec![9u8];
buf.extend_from_slice(&88u64.to_le_bytes());
buf.push(1);
assert!(matches!(
SharedMessageTableMessage::decode(&buf, &ctx()).unwrap_err(),
FormatError::InvalidVersion(9)
));
}
#[test]
fn shmesg_rejects_short_buffer() {
assert!(matches!(
SharedMessageTableMessage::decode(&[0u8; 4], &ctx()).unwrap_err(),
FormatError::BufferTooShort { .. }
));
}
#[test]
fn btreek_field_order_matches_upstream() {
let buf = [0u8, 0x40, 0x00, 0x20, 0x00, 0x08, 0x00];
let m = BtreeKMessage::decode(&buf).unwrap();
assert_eq!(m.chunk_internal_k, 64);
assert_eq!(m.snode_internal_k, 32);
assert_eq!(m.sym_leaf_k, 8);
}
#[test]
fn btreek_rejects_bad_version() {
let buf = [1u8, 0, 0, 0, 0, 0, 0];
assert!(matches!(
BtreeKMessage::decode(&buf).unwrap_err(),
FormatError::InvalidVersion(1)
));
}
#[test]
fn drvinfo_decodes_name_and_payload() {
let mut buf = vec![0u8];
buf.extend_from_slice(b"NCSAmult");
buf.extend_from_slice(&4u16.to_le_bytes());
buf.extend_from_slice(&[1, 2, 3, 4]);
let m = DriverInfoMessage::decode(&buf).unwrap();
assert_eq!(m.name, "NCSAmult");
assert_eq!(m.data, vec![1, 2, 3, 4]);
}
#[test]
fn drvinfo_rejects_zero_length() {
let mut buf = vec![0u8];
buf.extend_from_slice(b"NCSAfami");
buf.extend_from_slice(&0u16.to_le_bytes());
assert!(matches!(
DriverInfoMessage::decode(&buf).unwrap_err(),
FormatError::InvalidData(_)
));
}
fn fsinfo_v1(persist: bool) -> Vec<u8> {
let mut buf = vec![1u8, 1u8, persist as u8];
buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&4096u64.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0x1000u64.to_le_bytes()); if persist {
for i in 0..FS_ADDR_COUNT_V1 {
buf.extend_from_slice(&((0x2000 + i as u64) * 8).to_le_bytes());
}
}
buf
}
#[test]
fn fsinfo_v1_paged_non_persisting() {
let m = FileSpaceInfoMessage::decode(&fsinfo_v1(false), &ctx()).unwrap();
assert_eq!(m.strategy, FileSpaceStrategy::Page);
assert!(!m.persist);
assert_eq!(m.page_size, 4096);
assert!(m.fs_addr.iter().all(|&a| a == crate::format::UNDEF_ADDR));
}
#[test]
fn fsinfo_v1_persisting_reads_twelve_addresses() {
let m = FileSpaceInfoMessage::decode(&fsinfo_v1(true), &ctx()).unwrap();
assert!(m.persist);
assert_eq!(m.fs_addr.len(), FS_ADDR_COUNT_V1);
assert_eq!(m.fs_addr[0], 0x2000 * 8);
assert_eq!(m.eoa_pre_fsm_fsalloc, 0x1000);
}
#[test]
fn fsinfo_v1_rejects_absurd_page_size() {
let mut buf = fsinfo_v1(false);
buf[11..19].copy_from_slice(&(PAGE_SIZE_MAX + 1).to_le_bytes());
assert!(matches!(
FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
}
#[test]
fn fsinfo_v0_all_persist_maps_onto_the_version_one_fields() {
let mut buf = vec![0u8, 1u8];
buf.extend_from_slice(&7u64.to_le_bytes()); for i in 0..FS_ADDR_COUNT_V0 {
buf.extend_from_slice(&(0x100u64 + i as u64).to_le_bytes());
}
let m = FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap();
assert_eq!(m.version, 0);
assert_eq!(m.strategy, FileSpaceStrategy::FsmAggr);
assert!(m.persist);
assert_eq!(m.threshold, 7);
assert_eq!(m.fs_addr.len(), FS_ADDR_COUNT_V1);
assert_eq!(m.fs_addr[5], 0x105);
assert!(m.fs_addr[FS_ADDR_COUNT_V0..]
.iter()
.all(|&a| a == crate::format::UNDEF_ADDR));
}
#[test]
fn fsinfo_v0_vfd_maps_to_none() {
let mut buf = vec![0u8, 4u8];
buf.extend_from_slice(&0u64.to_le_bytes());
let m = FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap();
assert_eq!(m.strategy, FileSpaceStrategy::None);
assert!(!m.persist);
assert!(m.fs_addr.iter().all(|&a| a == crate::format::UNDEF_ADDR));
}
fn fsinfo_v0(legacy: u8, threshold: u64) -> Vec<u8> {
let mut buf = vec![0u8, legacy];
buf.extend_from_slice(&threshold.to_le_bytes());
if legacy == 1 {
for i in 0..FS_ADDR_COUNT_V0 {
buf.extend_from_slice(&(0x400u64 + 0x40 * i as u64).to_le_bytes());
}
}
buf
}
#[test]
fn fsinfo_v0_round_trips_as_version_zero() {
for (legacy, threshold) in [(1u8, 9u64), (2, 9), (3, 1), (4, 1)] {
let bytes = fsinfo_v0(legacy, threshold);
let m = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
assert_eq!(m.version, 0, "strategy {legacy}");
assert_eq!(
m.encode(&ctx()).unwrap(),
bytes,
"strategy {legacy} did not re-emit the body it was read from"
);
}
}
#[test]
fn a_version_zero_message_re_emits_a_moved_manager() {
let mut m = FileSpaceInfoMessage::decode(&fsinfo_v0(1, 9), &ctx()).unwrap();
m.fs_addr[0] = 0x2000;
m.fs_addr[2] = 0x3000;
m.eoa_pre_fsm_fsalloc = 0x9000;
let bytes = m.encode(&ctx()).unwrap();
assert_eq!(bytes.len(), fsinfo_v0(1, 9).len());
let again = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
assert_eq!(again.fs_addr[0], 0x2000);
assert_eq!(again.fs_addr[2], 0x3000);
assert_eq!(again.eoa_pre_fsm_fsalloc, crate::format::UNDEF_ADDR);
assert_eq!(
FileSpaceInfoMessage {
eoa_pre_fsm_fsalloc: m.eoa_pre_fsm_fsalloc,
..again
},
m
);
}
#[test]
fn fsinfo_v0_refuses_what_it_cannot_encode() {
let paged = FileSpaceInfoMessage::decode(&fsinfo_v1(true), &ctx()).unwrap();
assert!(matches!(
paged.encode_v0(&ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
let mut aggr = FileSpaceInfoMessage::decode(&fsinfo_v0(3, 1), &ctx()).unwrap();
aggr.threshold = 64;
assert!(matches!(
aggr.encode(&ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
let mut paged_manager = FileSpaceInfoMessage::decode(&fsinfo_v0(1, 9), &ctx()).unwrap();
paged_manager.fs_addr[FS_ADDR_COUNT_V0] = 0x800;
assert!(matches!(
paged_manager.encode(&ctx()).unwrap_err(),
FormatError::InvalidData(_)
));
}
#[test]
fn fsinfo_v1_round_trips() {
for persist in [false, true] {
let bytes = fsinfo_v1(persist);
let m = FileSpaceInfoMessage::decode(&bytes, &ctx()).unwrap();
assert_eq!(m.encode(&ctx()).unwrap(), bytes);
}
}
#[test]
fn fsinfo_rejects_unknown_version() {
let buf = vec![9u8; 40];
assert!(matches!(
FileSpaceInfoMessage::decode(&buf, &ctx()).unwrap_err(),
FormatError::InvalidVersion(9)
));
}
}