use crate::format::bytes::{read_le_addr as read_addr, read_le_uint as read_uint};
use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
const VERSION: u8 = 1;
pub const UNLIMITED: u64 = u64::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExternalFileSlot {
pub name_offset: u64,
pub offset: u64,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalFileListMessage {
pub heap_addr: u64,
pub slots: Vec<ExternalFileSlot>,
}
impl ExternalFileListMessage {
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
let nused = self.slots.len() as u16;
let mut buf = Vec::with_capacity(8 + sa + self.slots.len() * 3 * ss);
buf.push(VERSION);
buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&nused.to_le_bytes()); buf.extend_from_slice(&nused.to_le_bytes()); buf.extend_from_slice(&self.heap_addr.to_le_bytes()[..sa]);
for slot in &self.slots {
buf.extend_from_slice(&slot.name_offset.to_le_bytes()[..ss]);
buf.extend_from_slice(&slot.offset.to_le_bytes()[..ss]);
buf.extend_from_slice(&slot.size.to_le_bytes()[..ss]);
}
buf
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
let sa = ctx.sizeof_addr as usize;
let ss = ctx.sizeof_size as usize;
if buf.len() < 4 + 4 {
return Err(FormatError::BufferTooShort {
needed: 4 + 4,
available: buf.len(),
});
}
let version = buf[0];
if version != VERSION {
return Err(FormatError::InvalidVersion(version));
}
let nalloc = u16::from_le_bytes([buf[4], buf[5]]);
let nused = u16::from_le_bytes([buf[6], buf[7]]);
if nalloc == 0 {
return Err(FormatError::InvalidData(
"external file list message declares zero allocated slots".into(),
));
}
if nused > nalloc {
return Err(FormatError::InvalidData(format!(
"external file list message has {nused} in-use slots but only {nalloc} allocated"
)));
}
let mut pos = 8;
if buf.len() < pos + sa {
return Err(FormatError::BufferTooShort {
needed: pos + sa,
available: buf.len(),
});
}
let heap_addr = read_addr(&buf[pos..], sa);
pos += sa;
if heap_addr == UNDEF_ADDR {
return Err(FormatError::InvalidData(
"external file list message has an undefined local heap address".into(),
));
}
let slot_len = ss * 3;
let mut slots = Vec::with_capacity(nused as usize);
for _ in 0..nused {
if buf.len() < pos + slot_len {
return Err(FormatError::BufferTooShort {
needed: pos + slot_len,
available: buf.len(),
});
}
let name_offset = read_uint(&buf[pos..], ss);
pos += ss;
let offset = read_uint(&buf[pos..], ss);
pos += ss;
let size = read_uint(&buf[pos..], ss);
pos += ss;
slots.push(ExternalFileSlot {
name_offset,
offset,
size,
});
}
Ok((Self { heap_addr, slots }, pos))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx8() -> FormatContext {
FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
}
}
fn single_slot_buf() -> Vec<u8> {
let mut buf = vec![VERSION, 0, 0, 0];
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1072u64.to_le_bytes()); buf.extend_from_slice(&8u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&64u64.to_le_bytes()); buf
}
#[test]
fn decode_single_slot() {
let buf = single_slot_buf();
let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(msg.heap_addr, 1072);
assert_eq!(
msg.slots,
vec![ExternalFileSlot {
name_offset: 8,
offset: 0,
size: 64,
}]
);
}
#[test]
fn decode_multi_slot() {
let mut buf = vec![VERSION, 0, 0, 0];
buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&2000u64.to_le_bytes()); buf.extend_from_slice(&8u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&32u64.to_le_bytes());
buf.extend_from_slice(&20u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&32u64.to_le_bytes());
let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(msg.slots.len(), 2);
assert_eq!(msg.slots[0].size, 32);
assert_eq!(msg.slots[1].name_offset, 20);
}
#[test]
fn decode_rejects_bad_version() {
let mut buf = single_slot_buf();
buf[0] = 2;
let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::InvalidVersion(2)));
}
#[test]
fn decode_rejects_zero_nalloc() {
let mut buf = single_slot_buf();
buf[4] = 0;
buf[5] = 0;
let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::InvalidData(_)));
}
#[test]
fn decode_rejects_nused_over_nalloc() {
let mut buf = single_slot_buf();
buf[6] = 2; let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::InvalidData(_)));
}
#[test]
fn decode_rejects_undefined_heap_addr() {
let mut buf = single_slot_buf();
buf[8..16].copy_from_slice(&[0xFFu8; 8]);
let err = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::InvalidData(_)));
}
#[test]
fn decode_truncated() {
let buf = single_slot_buf();
let err = ExternalFileListMessage::decode(&buf[..20], &ctx8()).unwrap_err();
assert!(matches!(err, FormatError::BufferTooShort { .. }));
}
#[test]
fn decode_ctx4() {
let ctx = FormatContext {
sizeof_addr: 4,
sizeof_size: 4,
};
let mut buf = vec![VERSION, 0, 0, 0];
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes());
buf.extend_from_slice(&0x800u32.to_le_bytes()); buf.extend_from_slice(&8u32.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
buf.extend_from_slice(&64u32.to_le_bytes());
let (msg, consumed) = ExternalFileListMessage::decode(&buf, &ctx).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(msg.heap_addr, 0x800);
assert_eq!(msg.slots[0].size, 64);
}
#[test]
fn decode_preserves_unlimited_sentinel() {
let mut buf = single_slot_buf();
let last = buf.len() - 8;
buf[last..].copy_from_slice(&[0xFFu8; 8]);
let (msg, _) = ExternalFileListMessage::decode(&buf, &ctx8()).unwrap();
assert_eq!(msg.slots[0].size, UNLIMITED);
}
#[test]
fn encode_matches_the_captured_single_slot_message() {
let msg = ExternalFileListMessage {
heap_addr: 1072,
slots: vec![ExternalFileSlot {
name_offset: 8,
offset: 0,
size: 64,
}],
};
assert_eq!(msg.encode(&ctx8()), single_slot_buf());
}
#[test]
fn encode_roundtrips_multi_slot_at_ctx4() {
let ctx = FormatContext {
sizeof_addr: 4,
sizeof_size: 4,
};
let msg = ExternalFileListMessage {
heap_addr: 0x800,
slots: vec![
ExternalFileSlot {
name_offset: 8,
offset: 0,
size: 32,
},
ExternalFileSlot {
name_offset: 20,
offset: 16,
size: 32,
},
],
};
let buf = msg.encode(&ctx);
let (back, consumed) = ExternalFileListMessage::decode(&buf, &ctx).unwrap();
assert_eq!(consumed, buf.len());
assert_eq!(back, msg);
}
}