use crate::format::messages::dataspace::DataspaceMessage;
use crate::format::messages::datatype::DatatypeMessage;
use crate::format::{FormatContext, FormatError, FormatResult};
const ATTR_VERSION: u8 = 3;
#[derive(Debug, Clone, PartialEq)]
pub struct AttributeMessage {
pub name: String,
pub datatype: DatatypeMessage,
pub dataspace: DataspaceMessage,
pub data: Vec<u8>,
}
impl AttributeMessage {
pub fn scalar_string(name: &str, value: &str) -> Self {
let str_size = (value.len() + 1) as u32; let datatype = DatatypeMessage::fixed_string_utf8(str_size);
let dataspace = DataspaceMessage::scalar();
let mut data = Vec::with_capacity(str_size as usize);
data.extend_from_slice(value.as_bytes());
data.push(0);
Self {
name: name.to_string(),
datatype,
dataspace,
data,
}
}
pub fn scalar_numeric(name: &str, datatype: DatatypeMessage, data: Vec<u8>) -> Self {
Self {
name: name.to_string(),
datatype,
dataspace: DataspaceMessage::scalar(),
data,
}
}
pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
let encoded_dt = self.datatype.encode(ctx);
let encoded_ds = self.dataspace.encode(ctx);
let name_bytes = self.name.as_bytes();
let name_size = name_bytes.len() + 1;
let total = 9 + name_size + encoded_dt.len() + encoded_ds.len() + self.data.len();
let mut buf = Vec::with_capacity(total);
buf.push(ATTR_VERSION);
buf.push(0x00);
buf.extend_from_slice(&(name_size as u16).to_le_bytes());
buf.extend_from_slice(&(encoded_dt.len() as u16).to_le_bytes());
buf.extend_from_slice(&(encoded_ds.len() as u16).to_le_bytes());
buf.push(0x01);
buf.extend_from_slice(name_bytes);
buf.push(0x00);
buf.extend_from_slice(&encoded_dt);
buf.extend_from_slice(&encoded_ds);
buf.extend_from_slice(&self.data);
debug_assert_eq!(buf.len(), total);
buf
}
pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
if buf.len() < 8 {
return Err(FormatError::BufferTooShort {
needed: 8,
available: buf.len(),
});
}
let version = buf[0];
if !(1..=ATTR_VERSION).contains(&version) {
return Err(FormatError::InvalidVersion(version));
}
let name_size = u16::from_le_bytes([buf[2], buf[3]]) as usize;
let datatype_size = u16::from_le_bytes([buf[4], buf[5]]) as usize;
let dataspace_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
let mut pos = if version >= 3 {
9
} else {
8
};
let align = if version == 1 { 8 } else { 1 };
let needed = pos + name_size;
if buf.len() < needed {
return Err(FormatError::BufferTooShort {
needed,
available: buf.len(),
});
}
let name_end = if name_size > 0 && buf[pos + name_size - 1] == 0 {
pos + name_size - 1
} else {
pos + name_size
};
let name = String::from_utf8_lossy(&buf[pos..name_end]).to_string();
pos += name_size;
if align > 1 {
pos = (pos + align - 1) & !(align - 1);
}
let needed = pos + datatype_size;
if buf.len() < needed {
return Err(FormatError::BufferTooShort {
needed,
available: buf.len(),
});
}
let (datatype, _) = DatatypeMessage::decode(&buf[pos..pos + datatype_size], ctx)?;
pos += datatype_size;
if align > 1 {
pos = (pos + align - 1) & !(align - 1);
}
let needed = pos + dataspace_size;
if buf.len() < needed {
return Err(FormatError::BufferTooShort {
needed,
available: buf.len(),
});
}
let (dataspace, _) = DataspaceMessage::decode(&buf[pos..pos + dataspace_size], ctx)?;
pos += dataspace_size;
if align > 1 {
pos = (pos + align - 1) & !(align - 1);
}
let num_elements: u64 = if dataspace.dims.is_empty() {
1 } else {
dataspace.dims.iter().product()
};
let data_size = (num_elements * datatype.element_size() as u64) as usize;
let needed = pos + data_size;
if buf.len() < needed {
return Err(FormatError::BufferTooShort {
needed,
available: buf.len(),
});
}
let data = buf[pos..pos + data_size].to_vec();
pos += data_size;
Ok((
Self {
name,
datatype,
dataspace,
data,
},
pos,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx() -> FormatContext {
FormatContext {
sizeof_addr: 8,
sizeof_size: 8,
}
}
#[test]
fn scalar_string_roundtrip() {
let msg = AttributeMessage::scalar_string("my_attr", "hello");
let encoded = msg.encode(&ctx());
let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
assert_eq!(consumed, encoded.len());
assert_eq!(decoded.name, "my_attr");
assert_eq!(decoded.data, b"hello\0");
assert_eq!(decoded, msg);
}
#[test]
fn scalar_string_empty() {
let msg = AttributeMessage::scalar_string("empty", "");
let encoded = msg.encode(&ctx());
let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
assert_eq!(consumed, encoded.len());
assert_eq!(decoded.name, "empty");
assert_eq!(decoded.data, b"\0");
assert_eq!(decoded, msg);
}
#[test]
fn version_is_three() {
let msg = AttributeMessage::scalar_string("test", "val");
let encoded = msg.encode(&ctx());
assert_eq!(encoded[0], 3);
}
#[test]
fn decode_buffer_too_short() {
let buf = [0u8; 4];
let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
match err {
FormatError::BufferTooShort { .. } => {}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn decode_bad_version() {
let msg = AttributeMessage::scalar_string("x", "y");
let mut encoded = msg.encode(&ctx());
encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
match err {
FormatError::InvalidVersion(0) => {}
other => panic!("unexpected error: {:?}", other),
}
}
#[test]
fn scalar_string_utf8_content() {
let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
let encoded = msg.encode(&ctx());
let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
assert_eq!(decoded.name, "desc");
assert_eq!(decoded.data.len(), 6);
assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
assert_eq!(decoded.data[5], 0);
}
}