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 encode(&self, ctx: &FormatContext) -> Vec<u8> {
74 let encoded_dt = self.datatype.encode(ctx);
75 let encoded_ds = self.dataspace.encode(ctx);
76
77 let name_bytes = self.name.as_bytes();
79 let name_size = name_bytes.len() + 1; let total = 9 + name_size + encoded_dt.len() + encoded_ds.len() + self.data.len();
83 let mut buf = Vec::with_capacity(total);
84
85 buf.push(ATTR_VERSION);
87
88 buf.push(0x00);
90
91 buf.extend_from_slice(&(name_size as u16).to_le_bytes());
93
94 buf.extend_from_slice(&(encoded_dt.len() as u16).to_le_bytes());
96
97 buf.extend_from_slice(&(encoded_ds.len() as u16).to_le_bytes());
99
100 buf.push(0x01);
102
103 buf.extend_from_slice(name_bytes);
105 buf.push(0x00);
106
107 buf.extend_from_slice(&encoded_dt);
109
110 buf.extend_from_slice(&encoded_ds);
112
113 buf.extend_from_slice(&self.data);
115
116 debug_assert_eq!(buf.len(), total);
117 buf
118 }
119
120 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
127 if buf.len() < 8 {
128 return Err(FormatError::BufferTooShort {
129 needed: 8,
130 available: buf.len(),
131 });
132 }
133
134 let version = buf[0];
135 if !(1..=ATTR_VERSION).contains(&version) {
136 return Err(FormatError::InvalidVersion(version));
137 }
138
139 let name_size = u16::from_le_bytes([buf[2], buf[3]]) as usize;
141 let datatype_size = u16::from_le_bytes([buf[4], buf[5]]) as usize;
142 let dataspace_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
143
144 let mut pos = if version >= 3 {
145 9
147 } else {
148 8
150 };
151
152 let align = if version == 1 { 8 } else { 1 };
154
155 let needed = pos + name_size;
157 if buf.len() < needed {
158 return Err(FormatError::BufferTooShort {
159 needed,
160 available: buf.len(),
161 });
162 }
163 let name_end = if name_size > 0 && buf[pos + name_size - 1] == 0 {
165 pos + name_size - 1
166 } else {
167 pos + name_size
168 };
169 let name = String::from_utf8_lossy(&buf[pos..name_end]).to_string();
170 pos += name_size;
171 if align > 1 {
173 pos = (pos + align - 1) & !(align - 1);
174 }
175
176 let needed = pos + datatype_size;
178 if buf.len() < needed {
179 return Err(FormatError::BufferTooShort {
180 needed,
181 available: buf.len(),
182 });
183 }
184 let (datatype, _) = DatatypeMessage::decode(&buf[pos..pos + datatype_size], ctx)?;
185 pos += datatype_size;
186 if align > 1 {
187 pos = (pos + align - 1) & !(align - 1);
188 }
189
190 let needed = pos + dataspace_size;
192 if buf.len() < needed {
193 return Err(FormatError::BufferTooShort {
194 needed,
195 available: buf.len(),
196 });
197 }
198 let (dataspace, _) = DataspaceMessage::decode(&buf[pos..pos + dataspace_size], ctx)?;
199 pos += dataspace_size;
200 if align > 1 {
201 pos = (pos + align - 1) & !(align - 1);
202 }
203
204 let num_elements: u64 = if dataspace.dims.is_empty() {
206 1 } else {
208 dataspace
212 .dims
213 .iter()
214 .fold(1u64, |acc, &d| acc.saturating_mul(d))
215 };
216 let data_size = num_elements
217 .saturating_mul(datatype.element_size() as u64)
218 .min(usize::MAX as u64) as usize;
219 let needed = pos.saturating_add(data_size);
220 if buf.len() < needed {
221 return Err(FormatError::BufferTooShort {
222 needed,
223 available: buf.len(),
224 });
225 }
226 let data = buf[pos..pos + data_size].to_vec();
227 pos += data_size;
228
229 Ok((
230 Self {
231 name,
232 datatype,
233 dataspace,
234 data,
235 },
236 pos,
237 ))
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 fn ctx() -> FormatContext {
246 FormatContext {
247 sizeof_addr: 8,
248 sizeof_size: 8,
249 }
250 }
251
252 #[test]
253 fn scalar_string_roundtrip() {
254 let msg = AttributeMessage::scalar_string("my_attr", "hello");
255 let encoded = msg.encode(&ctx());
256 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
257 assert_eq!(consumed, encoded.len());
258 assert_eq!(decoded.name, "my_attr");
259 assert_eq!(decoded.data, b"hello\0");
260 assert_eq!(decoded, msg);
261 }
262
263 #[test]
264 fn scalar_string_empty() {
265 let msg = AttributeMessage::scalar_string("empty", "");
266 let encoded = msg.encode(&ctx());
267 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
268 assert_eq!(consumed, encoded.len());
269 assert_eq!(decoded.name, "empty");
270 assert_eq!(decoded.data, b"\0");
271 assert_eq!(decoded, msg);
272 }
273
274 #[test]
275 fn version_is_three() {
276 let msg = AttributeMessage::scalar_string("test", "val");
277 let encoded = msg.encode(&ctx());
278 assert_eq!(encoded[0], 3);
279 }
280
281 #[test]
282 fn decode_buffer_too_short() {
283 let buf = [0u8; 4];
284 let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
285 match err {
286 FormatError::BufferTooShort { .. } => {}
287 other => panic!("unexpected error: {:?}", other),
288 }
289 }
290
291 #[test]
292 fn decode_bad_version() {
293 let msg = AttributeMessage::scalar_string("x", "y");
294 let mut encoded = msg.encode(&ctx());
295 encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
297 match err {
298 FormatError::InvalidVersion(0) => {}
299 other => panic!("unexpected error: {:?}", other),
300 }
301 }
302
303 #[test]
304 fn scalar_string_utf8_content() {
305 let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
306 let encoded = msg.encode(&ctx());
307 let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
308 assert_eq!(decoded.name, "desc");
309 assert_eq!(decoded.data.len(), 6);
311 assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
312 assert_eq!(decoded.data[5], 0);
313 }
314}