#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::attribute_info::AttributeInfoMessage;
use crate::btree_v2::{collect_btree_v2_records, BTreeV2Header};
use crate::data_read;
use crate::dataspace::Dataspace;
use crate::datatype::Datatype;
use crate::error::FormatError;
use crate::fractal_heap::FractalHeapHeader;
use crate::message_type::MessageType;
use crate::object_header::ObjectHeader;
use crate::shared_message;
use crate::vl_data;
#[derive(Debug, Clone)]
pub struct AttributeMessage {
pub name: String,
pub datatype: Datatype,
pub dataspace: Dataspace,
pub raw_data: Vec<u8>,
}
fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
match offset.checked_add(needed) {
Some(end) if end <= data.len() => Ok(()),
_ => Err(FormatError::UnexpectedEof {
expected: offset.saturating_add(needed),
available: data.len(),
}),
}
}
fn pad8(x: usize) -> usize {
(x + 7) & !7
}
impl AttributeMessage {
pub fn parse(data: &[u8], length_size: u8) -> Result<AttributeMessage, FormatError> {
ensure_len(data, 0, 2)?;
let version = data[0];
match version {
1 => Self::parse_v1(data, length_size),
2 => Self::parse_v2(data, length_size),
3 => Self::parse_v3(data, length_size),
_ => Err(FormatError::InvalidAttributeVersion(version)),
}
}
fn parse_v1(data: &[u8], length_size: u8) -> Result<AttributeMessage, FormatError> {
ensure_len(data, 0, 8)?;
let name_size = u16::from_le_bytes([data[2], data[3]]) as usize;
let datatype_size = u16::from_le_bytes([data[4], data[5]]) as usize;
let dataspace_size = u16::from_le_bytes([data[6], data[7]]) as usize;
let mut pos = 8;
ensure_len(data, pos, name_size)?;
let name = extract_name(&data[pos..pos + name_size]);
pos += pad8(name_size);
ensure_len(data, pos, datatype_size)?;
let (datatype, _) = Datatype::parse(&data[pos..pos + datatype_size])?;
pos += pad8(datatype_size);
ensure_len(data, pos, dataspace_size)?;
let dataspace = Dataspace::parse(&data[pos..pos + dataspace_size], length_size)?;
pos += pad8(dataspace_size);
let raw_data = compute_raw_data(data, pos, &dataspace, &datatype);
Ok(AttributeMessage {
name,
datatype,
dataspace,
raw_data,
})
}
fn parse_v2(data: &[u8], length_size: u8) -> Result<AttributeMessage, FormatError> {
ensure_len(data, 0, 8)?;
let name_size = u16::from_le_bytes([data[2], data[3]]) as usize;
let datatype_size = u16::from_le_bytes([data[4], data[5]]) as usize;
let dataspace_size = u16::from_le_bytes([data[6], data[7]]) as usize;
let mut pos = 8;
ensure_len(data, pos, name_size)?;
let name = extract_name(&data[pos..pos + name_size]);
pos += name_size;
ensure_len(data, pos, datatype_size)?;
let (datatype, _) = Datatype::parse(&data[pos..pos + datatype_size])?;
pos += datatype_size;
ensure_len(data, pos, dataspace_size)?;
let dataspace = Dataspace::parse(&data[pos..pos + dataspace_size], length_size)?;
pos += dataspace_size;
let raw_data = compute_raw_data(data, pos, &dataspace, &datatype);
Ok(AttributeMessage {
name,
datatype,
dataspace,
raw_data,
})
}
fn parse_v3(data: &[u8], length_size: u8) -> Result<AttributeMessage, FormatError> {
ensure_len(data, 0, 9)?;
let name_size = u16::from_le_bytes([data[2], data[3]]) as usize;
let datatype_size = u16::from_le_bytes([data[4], data[5]]) as usize;
let dataspace_size = u16::from_le_bytes([data[6], data[7]]) as usize;
let _encoding = data[8];
let mut pos = 9;
ensure_len(data, pos, name_size)?;
let name = extract_name(&data[pos..pos + name_size]);
pos += name_size;
ensure_len(data, pos, datatype_size)?;
let (datatype, _) = Datatype::parse(&data[pos..pos + datatype_size])?;
pos += datatype_size;
ensure_len(data, pos, dataspace_size)?;
let dataspace = Dataspace::parse(&data[pos..pos + dataspace_size], length_size)?;
pos += dataspace_size;
let raw_data = compute_raw_data(data, pos, &dataspace, &datatype);
Ok(AttributeMessage {
name,
datatype,
dataspace,
raw_data,
})
}
pub fn serialize(&self, length_size: u8) -> Vec<u8> {
self.serialize_version(2, length_size)
}
pub fn serialize_v3(&self, length_size: u8) -> Vec<u8> {
self.serialize_version(3, length_size)
}
fn serialize_version(&self, version: u8, length_size: u8) -> Vec<u8> {
let name_bytes = {
let mut n = self.name.as_bytes().to_vec();
n.push(0); n
};
let dt_bytes = self.datatype.serialize();
let ds_bytes = self.dataspace.serialize(length_size);
let mut buf = Vec::new();
buf.push(version);
buf.push(0); buf.extend_from_slice(&(name_bytes.len() as u16).to_le_bytes());
buf.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
buf.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
if version >= 3 {
buf.push(0x00); }
buf.extend_from_slice(&name_bytes);
buf.extend_from_slice(&dt_bytes);
buf.extend_from_slice(&ds_bytes);
buf.extend_from_slice(&self.raw_data);
buf
}
pub fn read_as_f64(&self) -> Result<Vec<f64>, FormatError> {
data_read::read_as_f64(&self.raw_data, &self.datatype)
}
pub fn read_as_i64(&self) -> Result<Vec<i64>, FormatError> {
data_read::read_as_i64(&self.raw_data, &self.datatype)
}
pub fn read_as_u64(&self) -> Result<Vec<u64>, FormatError> {
data_read::read_as_u64(&self.raw_data, &self.datatype)
}
pub fn read_as_string(&self) -> Result<String, FormatError> {
let strings = data_read::read_as_strings(&self.raw_data, &self.datatype)?;
Ok(strings.into_iter().next().unwrap_or_default())
}
pub fn read_as_strings(&self) -> Result<Vec<String>, FormatError> {
data_read::read_as_strings(&self.raw_data, &self.datatype)
}
pub fn read_vl_strings(
&self,
file_data: &[u8],
offset_size: u8,
length_size: u8,
) -> Result<Vec<String>, FormatError> {
let num_elements = self.dataspace.num_elements();
vl_data::read_vl_strings(
file_data,
&self.raw_data,
num_elements,
offset_size,
length_size,
)
}
}
fn compute_raw_data(data: &[u8], pos: usize, dataspace: &Dataspace, datatype: &Datatype) -> Vec<u8> {
let num_elements = dataspace.num_elements() as usize;
let elem_size = datatype.type_size() as usize;
let expected_size = num_elements * elem_size;
let available = data.len().saturating_sub(pos);
let take = expected_size.min(available);
if take > 0 {
data[pos..pos + take].to_vec()
} else if available > 0 {
data[pos..].to_vec()
} else {
Vec::new()
}
}
fn extract_name(bytes: &[u8]) -> String {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).into_owned()
}
pub fn extract_attributes(
header: &ObjectHeader,
length_size: u8,
) -> Result<Vec<AttributeMessage>, FormatError> {
let mut attrs = Vec::new();
for msg in &header.messages {
if msg.msg_type == MessageType::Attribute {
let attr = AttributeMessage::parse(&msg.data, length_size)?;
attrs.push(attr);
}
}
Ok(attrs)
}
pub fn find_attribute<'a>(
attrs: &'a [AttributeMessage],
name: &str,
) -> Option<&'a AttributeMessage> {
attrs.iter().find(|a| a.name == name)
}
pub fn extract_attributes_full(
file_data: &[u8],
header: &ObjectHeader,
offset_size: u8,
length_size: u8,
) -> Result<Vec<AttributeMessage>, FormatError> {
let mut attrs = Vec::new();
for msg in &header.messages {
if msg.msg_type == MessageType::Attribute {
if shared_message::is_shared(msg.flags) {
let shared_ref = shared_message::parse_shared_ref(&msg.data, offset_size)?;
let resolved_data = shared_message::resolve_shared_message(
file_data,
&shared_ref,
MessageType::Attribute,
offset_size,
length_size,
)?;
let attr = AttributeMessage::parse(&resolved_data, length_size)?;
attrs.push(attr);
} else {
let attr = AttributeMessage::parse(&msg.data, length_size)?;
attrs.push(attr);
}
}
}
let attr_info = find_attribute_info(header, offset_size)?;
if let Some(info) = attr_info {
if let Some(fh_addr) = info.fractal_heap_address {
let dense_attrs =
extract_dense_attributes(file_data, &info, fh_addr, offset_size, length_size)?;
attrs.extend(dense_attrs);
}
}
Ok(attrs)
}
fn find_attribute_info(
header: &ObjectHeader,
offset_size: u8,
) -> Result<Option<AttributeInfoMessage>, FormatError> {
for msg in &header.messages {
if msg.msg_type == MessageType::AttributeInfo {
let info = AttributeInfoMessage::parse(&msg.data, offset_size)?;
return Ok(Some(info));
}
}
Ok(None)
}
fn extract_dense_attributes(
file_data: &[u8],
attr_info: &AttributeInfoMessage,
fh_addr: u64,
offset_size: u8,
length_size: u8,
) -> Result<Vec<AttributeMessage>, FormatError> {
let fh = FractalHeapHeader::parse(file_data, fh_addr as usize, offset_size, length_size)?;
let btree_addr = attr_info.btree_name_index_address.ok_or(
FormatError::UnexpectedEof {
expected: 1,
available: 0,
}
)?;
let btree_hdr =
BTreeV2Header::parse(file_data, btree_addr as usize, offset_size, length_size)?;
let records = collect_btree_v2_records(file_data, &btree_hdr, offset_size, length_size)?;
let mut attrs = Vec::new();
for record in &records {
let id_offset = 0;
if record.data.len() < id_offset + fh.heap_id_length as usize {
continue;
}
let id_bytes = &record.data[id_offset..id_offset + fh.heap_id_length as usize];
let attr_data = fh.read_managed_object(file_data, id_bytes, offset_size)?;
let attr = AttributeMessage::parse(&attr_data, length_size)?;
attrs.push(attr);
}
Ok(attrs)
}
#[cfg(test)]
mod tests {
use super::*;
fn build_dt_header(class: u8, version: u8, bf: [u8; 3], size: u32) -> Vec<u8> {
let mut buf = vec![0u8; 8];
buf[0] = (class & 0x0F) | ((version & 0x0F) << 4);
buf[1] = bf[0];
buf[2] = bf[1];
buf[3] = bf[2];
buf[4..8].copy_from_slice(&size.to_le_bytes());
buf
}
fn build_f64_dt() -> Vec<u8> {
let mut buf = build_dt_header(1, 1, [0x00, 0x00, 0x02], 8);
let mut props = [0u8; 12];
props[2..4].copy_from_slice(&64u16.to_le_bytes()); props[4] = 52; props[5] = 11; props[6] = 0; props[7] = 52; props[8..12].copy_from_slice(&1023u32.to_le_bytes()); buf.extend_from_slice(&props);
buf
}
fn build_scalar_ds() -> Vec<u8> {
vec![2, 0, 0, 0] }
fn build_simple_ds_v1(dim: u64) -> Vec<u8> {
let mut buf = vec![1u8, 1, 0, 0, 0, 0, 0, 0]; buf.extend_from_slice(&dim.to_le_bytes());
buf
}
fn build_string_dt(size: u32) -> Vec<u8> {
build_dt_header(3, 1, [0x01, 0, 0], size)
}
#[test]
fn parse_v1_attribute_f64_scalar() {
let name = b"temp\0";
let dt_bytes = build_f64_dt();
let ds_bytes = build_scalar_ds();
let name_size = name.len();
let dt_size = dt_bytes.len();
let ds_size = ds_bytes.len();
let mut data = Vec::new();
data.push(1); data.push(0); data.extend_from_slice(&(name_size as u16).to_le_bytes());
data.extend_from_slice(&(dt_size as u16).to_le_bytes());
data.extend_from_slice(&(ds_size as u16).to_le_bytes());
data.extend_from_slice(name);
while data.len() % 8 != 0 || data.len() == 8 {
let name_start = 8;
let name_padded = pad8(name_size);
while data.len() < name_start + name_padded {
data.push(0);
}
break;
}
let dt_start = data.len();
data.extend_from_slice(&dt_bytes);
let dt_padded = pad8(dt_size);
while data.len() < dt_start + dt_padded {
data.push(0);
}
let ds_start = data.len();
data.extend_from_slice(&ds_bytes);
let ds_padded = pad8(ds_size);
while data.len() < ds_start + ds_padded {
data.push(0);
}
data.extend_from_slice(&98.6f64.to_le_bytes());
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "temp");
assert_eq!(attr.dataspace.num_elements(), 1);
let vals = attr.read_as_f64().unwrap();
assert_eq!(vals.len(), 1);
assert!((vals[0] - 98.6).abs() < 1e-10);
}
#[test]
fn parse_v2_attribute_fixed_string() {
let name = b"label\0";
let dt_bytes = build_string_dt(5);
let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(2); data.push(0); data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(b"hello");
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "label");
let s = attr.read_as_string().unwrap();
assert_eq!(s, "hello");
}
#[test]
fn parse_v3_attribute_utf8() {
let name = b"note\0";
let dt_bytes = build_string_dt(3);
let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(3); data.push(0); data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.push(1);
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(b"abc");
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "note");
let s = attr.read_as_string().unwrap();
assert_eq!(s, "abc");
}
#[test]
fn parse_v2_attribute_1d_array() {
let name = b"vals\0";
let dt_bytes = build_f64_dt();
let ds_bytes = build_simple_ds_v1(3);
let mut data = Vec::new();
data.push(2); data.push(0); data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(&1.0f64.to_le_bytes());
data.extend_from_slice(&2.0f64.to_le_bytes());
data.extend_from_slice(&3.0f64.to_le_bytes());
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "vals");
let vals = attr.read_as_f64().unwrap();
assert_eq!(vals, vec![1.0, 2.0, 3.0]);
}
#[test]
fn parse_v1_padding_alignment() {
let name = b"x\0"; let dt_bytes = build_f64_dt(); let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(1); data.push(0); data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.resize(8 + pad8(name.len()), 0);
let dt_start = data.len();
data.extend_from_slice(&dt_bytes);
data.resize(dt_start + pad8(dt_bytes.len()), 0);
let ds_start = data.len();
data.extend_from_slice(&ds_bytes);
data.resize(ds_start + pad8(ds_bytes.len()), 0);
data.extend_from_slice(&42.0f64.to_le_bytes());
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "x");
let vals = attr.read_as_f64().unwrap();
assert_eq!(vals, vec![42.0]);
}
#[test]
fn parse_v2_no_padding() {
let name = b"ab\0"; let dt_bytes = build_string_dt(2); let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(2);
data.push(0);
data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(b"hi");
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.name, "ab");
assert_eq!(attr.read_as_string().unwrap(), "hi");
}
#[test]
fn truncated_attribute_error() {
let data = [1u8]; let err = AttributeMessage::parse(&data, 8).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
}
#[test]
fn invalid_version_error() {
let data = [5u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let err = AttributeMessage::parse(&data, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidAttributeVersion(5));
}
#[test]
fn extract_attributes_from_header() {
let mut msgs = Vec::new();
for i in 0..3 {
let name = format!("attr{}\0", i);
let dt_bytes = build_f64_dt();
let ds_bytes = build_scalar_ds();
let mut attr_data = Vec::new();
attr_data.push(2); attr_data.push(0);
attr_data.extend_from_slice(&(name.len() as u16).to_le_bytes());
attr_data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
attr_data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
attr_data.extend_from_slice(name.as_bytes());
attr_data.extend_from_slice(&dt_bytes);
attr_data.extend_from_slice(&ds_bytes);
attr_data.extend_from_slice(&((i as f64) * 1.0).to_le_bytes());
msgs.push(crate::object_header::HeaderMessage {
msg_type: MessageType::Attribute,
size: attr_data.len(),
flags: 0,
creation_order: None,
data: attr_data,
});
}
let header = ObjectHeader {
version: 2,
messages: msgs,
reference_count: None,
flags: 0,
access_time: None,
modification_time: None,
change_time: None,
birth_time: None,
};
let attrs = extract_attributes(&header, 8).unwrap();
assert_eq!(attrs.len(), 3);
assert_eq!(attrs[0].name, "attr0");
assert_eq!(attrs[1].name, "attr1");
assert_eq!(attrs[2].name, "attr2");
}
#[test]
fn find_attribute_by_name() {
let name = b"target\0";
let dt_bytes = build_f64_dt();
let ds_bytes = build_scalar_ds();
let mut attr_data = Vec::new();
attr_data.push(2);
attr_data.push(0);
attr_data.extend_from_slice(&(name.len() as u16).to_le_bytes());
attr_data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
attr_data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
attr_data.extend_from_slice(name);
attr_data.extend_from_slice(&dt_bytes);
attr_data.extend_from_slice(&ds_bytes);
attr_data.extend_from_slice(&99.0f64.to_le_bytes());
let attr = AttributeMessage::parse(&attr_data, 8).unwrap();
let attrs = vec![attr];
assert!(find_attribute(&attrs, "target").is_some());
assert!(find_attribute(&attrs, "missing").is_none());
}
#[test]
fn read_as_f64_scalar() {
let name = b"v\0";
let dt_bytes = build_f64_dt();
let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(2);
data.push(0);
data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(&3.14f64.to_le_bytes());
let attr = AttributeMessage::parse(&data, 8).unwrap();
let vals = attr.read_as_f64().unwrap();
assert_eq!(vals, vec![3.14]);
}
#[test]
fn read_as_string_fixed() {
let name = b"s\0";
let dt_bytes = build_string_dt(5);
let ds_bytes = build_scalar_ds();
let mut data = Vec::new();
data.push(2);
data.push(0);
data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(b"world");
let attr = AttributeMessage::parse(&data, 8).unwrap();
assert_eq!(attr.read_as_string().unwrap(), "world");
}
#[test]
fn read_as_strings_array() {
let name = b"arr\0";
let dt_bytes = build_string_dt(4);
let ds_bytes = build_simple_ds_v1(2);
let mut data = Vec::new();
data.push(2);
data.push(0);
data.extend_from_slice(&(name.len() as u16).to_le_bytes());
data.extend_from_slice(&(dt_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(&(ds_bytes.len() as u16).to_le_bytes());
data.extend_from_slice(name);
data.extend_from_slice(&dt_bytes);
data.extend_from_slice(&ds_bytes);
data.extend_from_slice(b"abcdEFGH");
let attr = AttributeMessage::parse(&data, 8).unwrap();
let strs = attr.read_as_strings().unwrap();
assert_eq!(strs, vec!["abcd", "EFGH"]);
}
}