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.dims.iter().product()
209 };
210 let data_size = (num_elements * datatype.element_size() as u64) as usize;
211 let needed = pos + data_size;
212 if buf.len() < needed {
213 return Err(FormatError::BufferTooShort {
214 needed,
215 available: buf.len(),
216 });
217 }
218 let data = buf[pos..pos + data_size].to_vec();
219 pos += data_size;
220
221 Ok((
222 Self {
223 name,
224 datatype,
225 dataspace,
226 data,
227 },
228 pos,
229 ))
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn ctx() -> FormatContext {
238 FormatContext {
239 sizeof_addr: 8,
240 sizeof_size: 8,
241 }
242 }
243
244 #[test]
245 fn scalar_string_roundtrip() {
246 let msg = AttributeMessage::scalar_string("my_attr", "hello");
247 let encoded = msg.encode(&ctx());
248 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
249 assert_eq!(consumed, encoded.len());
250 assert_eq!(decoded.name, "my_attr");
251 assert_eq!(decoded.data, b"hello\0");
252 assert_eq!(decoded, msg);
253 }
254
255 #[test]
256 fn scalar_string_empty() {
257 let msg = AttributeMessage::scalar_string("empty", "");
258 let encoded = msg.encode(&ctx());
259 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
260 assert_eq!(consumed, encoded.len());
261 assert_eq!(decoded.name, "empty");
262 assert_eq!(decoded.data, b"\0");
263 assert_eq!(decoded, msg);
264 }
265
266 #[test]
267 fn version_is_three() {
268 let msg = AttributeMessage::scalar_string("test", "val");
269 let encoded = msg.encode(&ctx());
270 assert_eq!(encoded[0], 3);
271 }
272
273 #[test]
274 fn decode_buffer_too_short() {
275 let buf = [0u8; 4];
276 let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
277 match err {
278 FormatError::BufferTooShort { .. } => {}
279 other => panic!("unexpected error: {:?}", other),
280 }
281 }
282
283 #[test]
284 fn decode_bad_version() {
285 let msg = AttributeMessage::scalar_string("x", "y");
286 let mut encoded = msg.encode(&ctx());
287 encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
289 match err {
290 FormatError::InvalidVersion(0) => {}
291 other => panic!("unexpected error: {:?}", other),
292 }
293 }
294
295 #[test]
296 fn scalar_string_utf8_content() {
297 let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
298 let encoded = msg.encode(&ctx());
299 let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
300 assert_eq!(decoded.name, "desc");
301 assert_eq!(decoded.data.len(), 6);
303 assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
304 assert_eq!(decoded.data[5], 0);
305 }
306}