#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::bytes::{ensure_len, read_optional_offset};
use crate::error::FormatError;
#[derive(Debug, Clone, PartialEq)]
pub struct AttributeInfoMessage {
pub max_creation_index: Option<u16>,
pub indexes_creation_order: bool,
pub fractal_heap_address: Option<u64>,
pub btree_name_index_address: Option<u64>,
pub btree_creation_order_address: Option<u64>,
}
impl AttributeInfoMessage {
pub fn parse(data: &[u8], offset_size: u8) -> Result<AttributeInfoMessage, FormatError> {
ensure_len(data, 0, 2)?;
let version = data[0];
if version != 0 {
return Err(FormatError::InvalidAttributeInfoVersion(version));
}
let flags = data[1];
let has_max_creation_index = flags & 0x01 != 0;
let has_creation_order_index = flags & 0x02 != 0;
let mut pos = 2;
let max_creation_index = if has_max_creation_index {
ensure_len(data, pos, 2)?;
let v = u16::from_le_bytes([data[pos], data[pos + 1]]);
pos += 2;
Some(v)
} else {
None
};
let fractal_heap_address = read_optional_offset(data, pos, offset_size)?;
pos += offset_size as usize;
let btree_name_index_address = read_optional_offset(data, pos, offset_size)?;
pos += offset_size as usize;
let btree_creation_order_address = if has_creation_order_index {
read_optional_offset(data, pos, offset_size)?
} else {
None
};
Ok(AttributeInfoMessage {
max_creation_index,
indexes_creation_order: has_creation_order_index,
fractal_heap_address,
btree_name_index_address,
btree_creation_order_address,
})
}
pub(crate) fn serialize(&self, offset_size: u8) -> Vec<u8> {
let mut data = Vec::with_capacity(2 + 2 + 3 * offset_size as usize);
data.push(0); let flags = u8::from(self.max_creation_index.is_some())
| (u8::from(self.indexes_creation_order) << 1);
data.push(flags);
if let Some(max) = self.max_creation_index {
data.extend_from_slice(&max.to_le_bytes());
}
write_offset(&mut data, self.fractal_heap_address, offset_size);
write_offset(&mut data, self.btree_name_index_address, offset_size);
if self.indexes_creation_order {
write_offset(&mut data, self.btree_creation_order_address, offset_size);
}
data
}
}
fn write_offset(data: &mut Vec<u8>, address: Option<u64>, offset_size: u8) {
let value = address.unwrap_or(u64::MAX);
for i in 0..offset_size as usize {
#[expect(
clippy::cast_possible_truncation,
reason = "one byte of the address at a time"
)]
data.push((value >> (8 * i)) as u8);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_compact_storage() {
let mut data = vec![0u8; 2 + 8 + 8];
data[0] = 0; data[1] = 0; data[2..10].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_le_bytes());
data[10..18].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_le_bytes());
let msg = AttributeInfoMessage::parse(&data, 8).unwrap();
assert_eq!(msg.fractal_heap_address, None);
assert_eq!(msg.btree_name_index_address, None);
assert_eq!(msg.max_creation_index, None);
assert_eq!(msg.btree_creation_order_address, None);
}
#[test]
fn parse_dense_storage() {
let mut data = Vec::new();
data.push(0); data.push(0x00); data.extend_from_slice(&0x1000u64.to_le_bytes()); data.extend_from_slice(&0x2000u64.to_le_bytes());
let msg = AttributeInfoMessage::parse(&data, 8).unwrap();
assert_eq!(msg.fractal_heap_address, Some(0x1000));
assert_eq!(msg.btree_name_index_address, Some(0x2000));
assert_eq!(msg.max_creation_index, None);
assert_eq!(msg.btree_creation_order_address, None);
}
#[test]
fn parse_dense_with_creation_order() {
let mut data = Vec::new();
data.push(0); data.push(0x03); data.extend_from_slice(&42u16.to_le_bytes()); data.extend_from_slice(&0x1000u64.to_le_bytes()); data.extend_from_slice(&0x2000u64.to_le_bytes()); data.extend_from_slice(&0x3000u64.to_le_bytes());
let msg = AttributeInfoMessage::parse(&data, 8).unwrap();
assert_eq!(msg.max_creation_index, Some(42));
assert!(msg.indexes_creation_order);
assert_eq!(msg.fractal_heap_address, Some(0x1000));
assert_eq!(msg.btree_name_index_address, Some(0x2000));
assert_eq!(msg.btree_creation_order_address, Some(0x3000));
}
#[test]
fn serialize_round_trips_every_flag_combination() {
for max in [None, Some(7u16)] {
for indexed in [false, true] {
for heap in [None, Some(0x1000u64)] {
let msg = AttributeInfoMessage {
max_creation_index: max,
indexes_creation_order: indexed,
fractal_heap_address: heap,
btree_name_index_address: heap.map(|_| 0x2000),
btree_creation_order_address: (indexed && heap.is_some()).then_some(0x3000),
};
let bytes = msg.serialize(8);
assert_eq!(
AttributeInfoMessage::parse(&bytes, 8).unwrap(),
msg,
"round trip for max={max:?} indexed={indexed} heap={heap:?}"
);
}
}
}
}
#[test]
fn parse_four_byte_offsets() {
let mut data = Vec::new();
data.push(0); data.push(0x00); data.extend_from_slice(&0x100u32.to_le_bytes()); data.extend_from_slice(&0x200u32.to_le_bytes());
let msg = AttributeInfoMessage::parse(&data, 4).unwrap();
assert_eq!(msg.fractal_heap_address, Some(0x100));
assert_eq!(msg.btree_name_index_address, Some(0x200));
}
#[test]
fn invalid_version() {
let data = vec![1, 0, 0, 0];
let err = AttributeInfoMessage::parse(&data, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidAttributeInfoVersion(1));
}
#[test]
fn truncated_data() {
let data = vec![0u8]; let err = AttributeInfoMessage::parse(&data, 8).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
}
#[test]
fn undefined_addresses_four_byte() {
let mut data = Vec::new();
data.push(0); data.push(0x00); data.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); data.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
let msg = AttributeInfoMessage::parse(&data, 4).unwrap();
assert_eq!(msg.fractal_heap_address, None);
assert_eq!(msg.btree_name_index_address, None);
}
}