rust_hdf5/format/messages/
attribute.rs1use crate::format::messages::dataspace::DataspaceMessage;
16use crate::format::messages::datatype::DatatypeMessage;
17use crate::format::{FormatContext, FormatError, FormatResult};
18
19const ATTR_VERSION: u8 = 3;
20
21#[derive(Debug, Clone, PartialEq)]
23pub struct AttributeMessage {
24 pub name: String,
26 pub datatype: DatatypeMessage,
28 pub dataspace: DataspaceMessage,
30 pub data: Vec<u8>,
32}
33
34impl AttributeMessage {
35 pub fn scalar_string(name: &str, value: &str) -> Self {
41 let str_size = (value.len() + 1) as u32; let datatype = DatatypeMessage::fixed_string_utf8(str_size);
43 let dataspace = DataspaceMessage::scalar();
44
45 let mut data = Vec::with_capacity(str_size as usize);
47 data.extend_from_slice(value.as_bytes());
48 data.push(0); Self {
51 name: name.to_string(),
52 datatype,
53 dataspace,
54 data,
55 }
56 }
57
58 pub fn scalar_numeric(name: &str, datatype: DatatypeMessage, data: Vec<u8>) -> Self {
60 Self {
61 name: name.to_string(),
62 datatype,
63 dataspace: DataspaceMessage::scalar(),
64 data,
65 }
66 }
67
68 pub fn array_numeric(
76 name: &str,
77 datatype: DatatypeMessage,
78 dims: &[u64],
79 data: Vec<u8>,
80 ) -> Self {
81 debug_assert_eq!(
82 data.len() as u64,
83 dims.iter().product::<u64>() * datatype.element_size() as u64,
84 "array_numeric data length must equal product(dims) * element_size"
85 );
86 Self {
87 name: name.to_string(),
88 datatype,
89 dataspace: DataspaceMessage::simple(dims),
90 data,
91 }
92 }
93
94 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
100 let encoded_dt = self.datatype.encode(ctx);
101 let encoded_ds = self.dataspace.encode(ctx);
102
103 let name_bytes = self.name.as_bytes();
105 let name_size = name_bytes.len() + 1; let total = 9 + name_size + encoded_dt.len() + encoded_ds.len() + self.data.len();
109 let mut buf = Vec::with_capacity(total);
110
111 buf.push(ATTR_VERSION);
113
114 buf.push(0x00);
116
117 buf.extend_from_slice(&(name_size as u16).to_le_bytes());
119
120 buf.extend_from_slice(&(encoded_dt.len() as u16).to_le_bytes());
122
123 buf.extend_from_slice(&(encoded_ds.len() as u16).to_le_bytes());
125
126 buf.push(0x01);
128
129 buf.extend_from_slice(name_bytes);
131 buf.push(0x00);
132
133 buf.extend_from_slice(&encoded_dt);
135
136 buf.extend_from_slice(&encoded_ds);
138
139 buf.extend_from_slice(&self.data);
141
142 debug_assert_eq!(buf.len(), total);
143 buf
144 }
145
146 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
153 if buf.len() < 8 {
154 return Err(FormatError::BufferTooShort {
155 needed: 8,
156 available: buf.len(),
157 });
158 }
159
160 let version = buf[0];
161 if !(1..=ATTR_VERSION).contains(&version) {
162 return Err(FormatError::InvalidVersion(version));
163 }
164
165 let name_size = u16::from_le_bytes([buf[2], buf[3]]) as usize;
167 let datatype_size = u16::from_le_bytes([buf[4], buf[5]]) as usize;
168 let dataspace_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
169
170 let mut pos = if version >= 3 {
171 9
173 } else {
174 8
176 };
177
178 let align = if version == 1 { 8 } else { 1 };
180
181 let needed = pos + name_size;
183 if buf.len() < needed {
184 return Err(FormatError::BufferTooShort {
185 needed,
186 available: buf.len(),
187 });
188 }
189 let name_end = if name_size > 0 && buf[pos + name_size - 1] == 0 {
191 pos + name_size - 1
192 } else {
193 pos + name_size
194 };
195 let name = String::from_utf8_lossy(&buf[pos..name_end]).to_string();
196 pos += name_size;
197 if align > 1 {
199 pos = (pos + align - 1) & !(align - 1);
200 }
201
202 let needed = pos + datatype_size;
204 if buf.len() < needed {
205 return Err(FormatError::BufferTooShort {
206 needed,
207 available: buf.len(),
208 });
209 }
210 let (datatype, _) = DatatypeMessage::decode(&buf[pos..pos + datatype_size], ctx)?;
211 pos += datatype_size;
212 if align > 1 {
213 pos = (pos + align - 1) & !(align - 1);
214 }
215
216 let needed = pos + dataspace_size;
218 if buf.len() < needed {
219 return Err(FormatError::BufferTooShort {
220 needed,
221 available: buf.len(),
222 });
223 }
224 let (dataspace, _) = DataspaceMessage::decode(&buf[pos..pos + dataspace_size], ctx)?;
225 pos += dataspace_size;
226 if align > 1 {
227 pos = (pos + align - 1) & !(align - 1);
228 }
229
230 let num_elements: u64 = if dataspace.dims.is_empty() {
232 1 } else {
234 dataspace
238 .dims
239 .iter()
240 .fold(1u64, |acc, &d| acc.saturating_mul(d))
241 };
242 let data_size = num_elements
243 .saturating_mul(datatype.element_size() as u64)
244 .min(usize::MAX as u64) as usize;
245 let needed = pos.saturating_add(data_size);
246 if buf.len() < needed {
247 return Err(FormatError::BufferTooShort {
248 needed,
249 available: buf.len(),
250 });
251 }
252 let data = buf[pos..pos + data_size].to_vec();
253 pos += data_size;
254
255 Ok((
256 Self {
257 name,
258 datatype,
259 dataspace,
260 data,
261 },
262 pos,
263 ))
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 fn ctx() -> FormatContext {
272 FormatContext {
273 sizeof_addr: 8,
274 sizeof_size: 8,
275 }
276 }
277
278 #[test]
279 fn scalar_string_roundtrip() {
280 let msg = AttributeMessage::scalar_string("my_attr", "hello");
281 let encoded = msg.encode(&ctx());
282 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
283 assert_eq!(consumed, encoded.len());
284 assert_eq!(decoded.name, "my_attr");
285 assert_eq!(decoded.data, b"hello\0");
286 assert_eq!(decoded, msg);
287 }
288
289 #[test]
290 fn scalar_string_empty() {
291 let msg = AttributeMessage::scalar_string("empty", "");
292 let encoded = msg.encode(&ctx());
293 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
294 assert_eq!(consumed, encoded.len());
295 assert_eq!(decoded.name, "empty");
296 assert_eq!(decoded.data, b"\0");
297 assert_eq!(decoded, msg);
298 }
299
300 #[test]
301 fn version_is_three() {
302 let msg = AttributeMessage::scalar_string("test", "val");
303 let encoded = msg.encode(&ctx());
304 assert_eq!(encoded[0], 3);
305 }
306
307 #[test]
308 fn decode_buffer_too_short() {
309 let buf = [0u8; 4];
310 let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
311 match err {
312 FormatError::BufferTooShort { .. } => {}
313 other => panic!("unexpected error: {:?}", other),
314 }
315 }
316
317 #[test]
318 fn decode_bad_version() {
319 let msg = AttributeMessage::scalar_string("x", "y");
320 let mut encoded = msg.encode(&ctx());
321 encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
323 match err {
324 FormatError::InvalidVersion(0) => {}
325 other => panic!("unexpected error: {:?}", other),
326 }
327 }
328
329 #[test]
330 fn array_numeric_1d_roundtrip() {
331 use crate::format::messages::datatype::DatatypeMessage;
332 let vals: [i32; 3] = [10, -20, 30];
334 let mut data = Vec::new();
335 for v in vals {
336 data.extend_from_slice(&v.to_le_bytes());
337 }
338 let msg = AttributeMessage::array_numeric(
339 "dim_offset",
340 DatatypeMessage::i32_type(),
341 &[3],
342 data.clone(),
343 );
344 assert_eq!(msg.dataspace.dims, vec![3]);
345 let encoded = msg.encode(&ctx());
346 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
347 assert_eq!(consumed, encoded.len());
348 assert_eq!(decoded.name, "dim_offset");
349 assert_eq!(decoded.dataspace.dims, vec![3]);
350 assert_eq!(decoded.data, data);
351 assert_eq!(decoded, msg);
352 }
353
354 #[test]
355 fn scalar_string_utf8_content() {
356 let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
357 let encoded = msg.encode(&ctx());
358 let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
359 assert_eq!(decoded.name, "desc");
360 assert_eq!(decoded.data.len(), 6);
362 assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
363 assert_eq!(decoded.data[5], 0);
364 }
365}