use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
const VERSION: u8 = 0;
const FLAG_MAX_CREATION_ORDER: u8 = 0x01;
const FLAG_CREATION_ORDER_INDEXED: u8 = 0x02;
#[derive(Debug, Clone, PartialEq)]
pub struct LinkInfoMessage {
pub max_creation_order: Option<u64>,
pub fractal_heap_address: u64,
pub name_btree_address: u64,
pub creation_order_btree_address: Option<u64>,
}
impl LinkInfoMessage {
pub fn compact() -> Self {
Self {
max_creation_order: None,
fractal_heap_address: UNDEF_ADDR,
name_btree_address: UNDEF_ADDR,
creation_order_btree_address: None,
}
}
pub fn compact_with_creation_order() -> Self {
Self {
max_creation_order: Some(0),
fractal_heap_address: UNDEF_ADDR,
name_btree_address: UNDEF_ADDR,
creation_order_btree_address: None,
}
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let sa = ctx.sizeof_addr as usize;
let mut flags: u8 = 0;
if self.max_creation_order.is_some() {
flags |= FLAG_MAX_CREATION_ORDER;
}
if self.creation_order_btree_address.is_some() {
flags |= FLAG_CREATION_ORDER_INDEXED;
}
let mut buf = Vec::with_capacity(2 + 8 + 3 * sa);
buf.push(VERSION);
buf.push(flags);
if let Some(max_co) = self.max_creation_order {
buf.extend_from_slice(&max_co.to_le_bytes());
}
buf.extend_from_slice(&self.fractal_heap_address.to_le_bytes()[..sa]);
buf.extend_from_slice(&self.name_btree_address.to_le_bytes()[..sa]);
if let Some(co_addr) = self.creation_order_btree_address {
buf.extend_from_slice(&co_addr.to_le_bytes()[..sa]);
}
buf
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
if buf.len() < 2 {
return Err(FormatError::BufferTooShort {
needed: 2,
available: buf.len(),
});
}
let version = buf[0];
if version != VERSION {
return Err(FormatError::InvalidVersion(version));
}
let flags = buf[1];
let has_max_co = (flags & FLAG_MAX_CREATION_ORDER) != 0;
let has_co_btree = (flags & FLAG_CREATION_ORDER_INDEXED) != 0;
let sa = ctx.sizeof_addr as usize;
let mut pos = 2;
let max_creation_order = if has_max_co {
check_len(buf, pos, 8)?;
let v = u64::from_le_bytes([
buf[pos],
buf[pos + 1],
buf[pos + 2],
buf[pos + 3],
buf[pos + 4],
buf[pos + 5],
buf[pos + 6],
buf[pos + 7],
]);
pos += 8;
Some(v)
} else {
None
};
check_len(buf, pos, sa)?;
let fractal_heap_address = read_addr(&buf[pos..], sa);
pos += sa;
check_len(buf, pos, sa)?;
let name_btree_address = read_addr(&buf[pos..], sa);
pos += sa;
let creation_order_btree_address = if has_co_btree {
check_len(buf, pos, sa)?;
let v = read_addr(&buf[pos..], sa);
pos += sa;
Some(v)
} else {
None
};
Ok((
Self {
max_creation_order,
fractal_heap_address,
name_btree_address,
creation_order_btree_address,
},
pos,
))
}
}
fn check_len(buf: &[u8], pos: usize, need: usize) -> FormatResult<()> {
if buf.len() < pos + need {
Err(FormatError::BufferTooShort {
needed: pos + need,
available: buf.len(),
})
} else {
Ok(())
}
}
fn read_addr(buf: &[u8], n: usize) -> u64 {
if buf[..n].iter().all(|&b| b == 0xFF) {
UNDEF_ADDR
} else {
let mut tmp = [0u8; 8];
tmp[..n].copy_from_slice(&buf[..n]);
u64::from_le_bytes(tmp)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx8() -> FormatContext {
FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
}
}
fn ctx4() -> FormatContext {
FormatContext {
sizeof_addr: 4,
sizeof_size: 4,
}
}
#[test]
fn roundtrip_compact() {
let msg = LinkInfoMessage::compact();
let encoded = msg.encode(&ctx8());
assert_eq!(encoded.len(), 18);
let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
assert_eq!(consumed, 18);
assert_eq!(decoded, msg);
}
#[test]
fn roundtrip_compact_ctx4() {
let msg = LinkInfoMessage::compact();
let encoded = msg.encode(&ctx4());
assert_eq!(encoded.len(), 10);
let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx4()).unwrap();
assert_eq!(consumed, 10);
assert_eq!(decoded, msg);
}
#[test]
fn roundtrip_with_creation_order() {
let msg = LinkInfoMessage::compact_with_creation_order();
let encoded = msg.encode(&ctx8());
assert_eq!(encoded.len(), 26);
let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
assert_eq!(consumed, 26);
assert_eq!(decoded, msg);
}
#[test]
fn roundtrip_full() {
let msg = LinkInfoMessage {
max_creation_order: Some(42),
fractal_heap_address: 0x1000,
name_btree_address: 0x2000,
creation_order_btree_address: Some(0x3000),
};
let encoded = msg.encode(&ctx8());
assert_eq!(encoded.len(), 34);
let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
assert_eq!(consumed, 34);
assert_eq!(decoded, msg);
}
#[test]
fn roundtrip_undef_addresses() {
let msg = LinkInfoMessage {
max_creation_order: None,
fractal_heap_address: UNDEF_ADDR,
name_btree_address: UNDEF_ADDR,
creation_order_btree_address: None,
};
let encoded = msg.encode(&ctx4());
let (decoded, _) = LinkInfoMessage::decode(&encoded, &ctx4()).unwrap();
assert_eq!(decoded.fractal_heap_address, UNDEF_ADDR);
assert_eq!(decoded.name_btree_address, UNDEF_ADDR);
}
#[test]
fn decode_bad_version() {
let buf = [1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let err = LinkInfoMessage::decode(&buf, &ctx8()).unwrap_err();
match err {
FormatError::InvalidVersion(1) => {}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn decode_buffer_too_short() {
let buf = [0u8];
let err = LinkInfoMessage::decode(&buf, &ctx8()).unwrap_err();
match err {
FormatError::BufferTooShort { .. } => {}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn version_byte() {
let encoded = LinkInfoMessage::compact().encode(&ctx8());
assert_eq!(encoded[0], 0);
}
}