1use crate::format::messages::dataspace::DataspaceMessage;
19use crate::format::messages::datatype::DatatypeMessage;
20use crate::format::{FormatContext, FormatError, FormatResult, LibverBound};
21
22const ATTR_VERSION: u8 = 3;
23
24pub const ATTR_FLAG_TYPE_SHARED: u8 = 0x01;
27pub const ATTR_FLAG_SPACE_SHARED: u8 = 0x02;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct EncodedAttribute {
38 pub body: Vec<u8>,
40 pub datatype_at: usize,
42 pub dataspace_at: usize,
44}
45
46#[derive(Debug, Clone, PartialEq)]
48pub struct AttributeMessage {
49 pub name: String,
51 pub datatype: DatatypeMessage,
53 pub dataspace: DataspaceMessage,
55 pub data: Vec<u8>,
57}
58
59impl AttributeMessage {
60 pub fn scalar_string(name: &str, value: &str) -> Self {
66 let str_size = (value.len() + 1) as u32; let datatype = DatatypeMessage::fixed_string_utf8(str_size);
68 let dataspace = DataspaceMessage::scalar();
69
70 let mut data = Vec::with_capacity(str_size as usize);
72 data.extend_from_slice(value.as_bytes());
73 data.push(0); Self {
76 name: name.to_string(),
77 datatype,
78 dataspace,
79 data,
80 }
81 }
82
83 pub fn scalar_numeric(name: &str, datatype: DatatypeMessage, data: Vec<u8>) -> Self {
85 Self {
86 name: name.to_string(),
87 datatype,
88 dataspace: DataspaceMessage::scalar(),
89 data,
90 }
91 }
92
93 pub fn array_numeric(
101 name: &str,
102 datatype: DatatypeMessage,
103 dims: &[u64],
104 data: Vec<u8>,
105 ) -> Self {
106 debug_assert_eq!(
107 data.len() as u64,
108 dims.iter().product::<u64>() * datatype.element_size() as u64,
109 "array_numeric data length must equal product(dims) * element_size"
110 );
111 Self {
112 name: name.to_string(),
113 datatype,
114 dataspace: DataspaceMessage::simple(dims),
115 data,
116 }
117 }
118
119 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
125 self.encode_at(ctx, LibverBound::Earliest)
126 }
127
128 pub fn encode_at(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
131 self.encode_for(ctx, libver, crate::format::ObjectFormat::Modern)
132 }
133
134 fn encode_v1(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
143 fn pad_to_8(buf: &mut Vec<u8>) {
146 let padded = (buf.len() + 7) & !7;
147 buf.resize(padded, 0);
148 }
149
150 let encoded_dt = self.datatype.encode_at(ctx, libver);
151 let encoded_ds = self
152 .dataspace
153 .encode_for(ctx, crate::format::ObjectFormat::Legacy);
154 let name_bytes = self.name.as_bytes();
155 let name_size = name_bytes.len() + 1;
156
157 let mut buf = Vec::with_capacity(8 + name_size + encoded_dt.len() + encoded_ds.len() + 24);
158 buf.push(1); buf.push(0); buf.extend_from_slice(&(name_size as u16).to_le_bytes());
161 buf.extend_from_slice(&(encoded_dt.len() as u16).to_le_bytes());
162 buf.extend_from_slice(&(encoded_ds.len() as u16).to_le_bytes());
163 buf.extend_from_slice(name_bytes);
164 buf.push(0);
165 pad_to_8(&mut buf);
166 buf.extend_from_slice(&encoded_dt);
167 pad_to_8(&mut buf);
168 buf.extend_from_slice(&encoded_ds);
169 pad_to_8(&mut buf);
170 buf.extend_from_slice(&self.data);
171 buf
172 }
173
174 pub fn encode_for(
177 &self,
178 ctx: &FormatContext,
179 libver: LibverBound,
180 format: crate::format::ObjectFormat,
181 ) -> Vec<u8> {
182 if format.attribute_version() == 1 {
183 return self.encode_v1(ctx, libver);
184 }
185 let encoded_dt = self.datatype.encode_at(ctx, libver);
186 let encoded_ds = self.dataspace.encode_for(ctx, format);
187 self.encode_with_fields(0x00, &encoded_dt, &encoded_ds).body
188 }
189
190 pub fn encode_with_fields(
201 &self,
202 flags: u8,
203 datatype: &[u8],
204 dataspace: &[u8],
205 ) -> EncodedAttribute {
206 let name_bytes = self.name.as_bytes();
208 let name_size = name_bytes.len() + 1; let total = 9 + name_size + datatype.len() + dataspace.len() + self.data.len();
212 let mut buf = Vec::with_capacity(total);
213
214 buf.push(ATTR_VERSION);
216
217 buf.push(flags);
219
220 buf.extend_from_slice(&(name_size as u16).to_le_bytes());
222
223 buf.extend_from_slice(&(datatype.len() as u16).to_le_bytes());
225
226 buf.extend_from_slice(&(dataspace.len() as u16).to_le_bytes());
228
229 buf.push(0x01);
231
232 buf.extend_from_slice(name_bytes);
234 buf.push(0x00);
235
236 let datatype_at = buf.len();
237 buf.extend_from_slice(datatype);
238
239 let dataspace_at = buf.len();
240 buf.extend_from_slice(dataspace);
241
242 buf.extend_from_slice(&self.data);
244
245 debug_assert_eq!(buf.len(), total);
246 EncodedAttribute {
247 body: buf,
248 datatype_at,
249 dataspace_at,
250 }
251 }
252
253 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
260 let AttributeHeader {
261 name,
262 datatype_size,
263 dataspace_size,
264 align,
265 mut pos,
266 } = AttributeHeader::decode(buf)?;
267
268 let needed = pos + datatype_size;
270 if buf.len() < needed {
271 return Err(FormatError::BufferTooShort {
272 needed,
273 available: buf.len(),
274 });
275 }
276 let (datatype, _) = DatatypeMessage::decode(&buf[pos..pos + datatype_size], ctx)?;
277 pos += datatype_size;
278 if align > 1 {
279 pos = (pos + align - 1) & !(align - 1);
280 }
281
282 let needed = pos + dataspace_size;
284 if buf.len() < needed {
285 return Err(FormatError::BufferTooShort {
286 needed,
287 available: buf.len(),
288 });
289 }
290 let (dataspace, _) = DataspaceMessage::decode(&buf[pos..pos + dataspace_size], ctx)?;
291 pos += dataspace_size;
292 if align > 1 {
293 pos = (pos + align - 1) & !(align - 1);
294 }
295
296 let num_elements: u64 = if dataspace.dims.is_empty() {
298 1 } else {
300 dataspace
304 .dims
305 .iter()
306 .fold(1u64, |acc, &d| acc.saturating_mul(d))
307 };
308 let data_size = num_elements
309 .saturating_mul(datatype.element_size() as u64)
310 .min(usize::MAX as u64) as usize;
311 let needed = pos.saturating_add(data_size);
312 if buf.len() < needed {
313 return Err(FormatError::BufferTooShort {
314 needed,
315 available: buf.len(),
316 });
317 }
318 let data = buf[pos..pos + data_size].to_vec();
319 pos += data_size;
320
321 Ok((
322 Self {
323 name,
324 datatype,
325 dataspace,
326 data,
327 },
328 pos,
329 ))
330 }
331}
332
333struct AttributeHeader {
339 name: String,
340 datatype_size: usize,
341 dataspace_size: usize,
342 align: usize,
344 pos: usize,
346}
347
348impl AttributeHeader {
349 fn decode(buf: &[u8]) -> FormatResult<Self> {
350 if buf.len() < 8 {
351 return Err(FormatError::BufferTooShort {
352 needed: 8,
353 available: buf.len(),
354 });
355 }
356
357 let version = buf[0];
358 if !(1..=ATTR_VERSION).contains(&version) {
359 return Err(FormatError::InvalidVersion(version));
360 }
361
362 let flags = buf[1];
372 if flags & (ATTR_FLAG_TYPE_SHARED | ATTR_FLAG_SPACE_SHARED) != 0 {
373 let what = if flags & ATTR_FLAG_TYPE_SHARED != 0 {
374 "datatype"
375 } else {
376 "dataspace"
377 };
378 return Err(FormatError::UnsupportedFeature(format!(
379 "attribute whose {what} is a shared-message reference"
380 )));
381 }
382 let name_size = u16::from_le_bytes([buf[2], buf[3]]) as usize;
383 let datatype_size = u16::from_le_bytes([buf[4], buf[5]]) as usize;
384 let dataspace_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
385
386 let mut pos = if version >= 3 {
387 9
389 } else {
390 8
392 };
393
394 let align = if version == 1 { 8 } else { 1 };
396
397 let needed = pos + name_size;
399 if buf.len() < needed {
400 return Err(FormatError::BufferTooShort {
401 needed,
402 available: buf.len(),
403 });
404 }
405 let name_end = if name_size > 0 && buf[pos + name_size - 1] == 0 {
407 pos + name_size - 1
408 } else {
409 pos + name_size
410 };
411 let name = String::from_utf8_lossy(&buf[pos..name_end]).to_string();
412 pos += name_size;
413 if align > 1 {
415 pos = (pos + align - 1) & !(align - 1);
416 }
417
418 Ok(Self {
419 name,
420 datatype_size,
421 dataspace_size,
422 align,
423 pos,
424 })
425 }
426}
427
428#[derive(Debug, Clone, PartialEq)]
446pub struct AttributeEntry {
447 body: AttributeBody,
448 creation_index: Option<u16>,
452}
453
454#[derive(Debug, Clone, PartialEq)]
456enum AttributeBody {
457 Readable(AttributeMessage),
459 Unreadable {
463 name: String,
464 raw: Vec<u8>,
465 reason: String,
466 },
467}
468
469impl AttributeEntry {
470 pub fn parse(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
480 let body = match AttributeMessage::decode(buf, ctx) {
481 Ok((attr, _)) => AttributeBody::Readable(attr),
482 Err(payload_err) => {
483 let header = AttributeHeader::decode(buf)?;
484 AttributeBody::Unreadable {
485 name: header.name,
486 raw: buf.to_vec(),
487 reason: payload_err.to_string(),
488 }
489 }
490 };
491 Ok(Self {
492 body,
493 creation_index: None,
494 })
495 }
496
497 pub fn with_creation_index(mut self, creation_index: Option<u16>) -> Self {
499 self.creation_index = creation_index;
500 self
501 }
502
503 pub fn set_creation_index(&mut self, creation_index: Option<u16>) {
505 self.creation_index = creation_index;
506 }
507
508 pub fn creation_index(&self) -> Option<u16> {
511 self.creation_index
512 }
513
514 pub fn name(&self) -> &str {
516 match &self.body {
517 AttributeBody::Readable(attr) => &attr.name,
518 AttributeBody::Unreadable { name, .. } => name,
519 }
520 }
521
522 pub fn decoded(&self) -> Result<&AttributeMessage, &str> {
526 match &self.body {
527 AttributeBody::Readable(attr) => Ok(attr),
528 AttributeBody::Unreadable { reason, .. } => Err(reason),
529 }
530 }
531
532 pub fn readable(&self) -> Option<&AttributeMessage> {
534 self.decoded().ok()
535 }
536
537 pub fn unreadable_reason(&self) -> Option<&str> {
539 self.decoded().err()
540 }
541
542 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
548 self.encode_at(ctx, LibverBound::Earliest)
549 }
550
551 pub fn encode_at(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
555 self.encode_for(ctx, libver, crate::format::ObjectFormat::Modern)
556 }
557
558 pub fn encode_for(
560 &self,
561 ctx: &FormatContext,
562 libver: LibverBound,
563 format: crate::format::ObjectFormat,
564 ) -> Vec<u8> {
565 match &self.body {
566 AttributeBody::Readable(attr) => attr.encode_for(ctx, libver, format),
567 AttributeBody::Unreadable { raw, .. } => raw.clone(),
568 }
569 }
570}
571
572impl From<AttributeMessage> for AttributeEntry {
573 fn from(attr: AttributeMessage) -> Self {
574 Self {
575 body: AttributeBody::Readable(attr),
576 creation_index: None,
577 }
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 fn ctx() -> FormatContext {
586 FormatContext {
587 sizeof_addr: 8,
588 sizeof_size: 8,
589 }
590 }
591
592 #[test]
593 fn scalar_string_roundtrip() {
594 let msg = AttributeMessage::scalar_string("my_attr", "hello");
595 let encoded = msg.encode(&ctx());
596 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
597 assert_eq!(consumed, encoded.len());
598 assert_eq!(decoded.name, "my_attr");
599 assert_eq!(decoded.data, b"hello\0");
600 assert_eq!(decoded, msg);
601 }
602
603 #[test]
604 fn scalar_string_empty() {
605 let msg = AttributeMessage::scalar_string("empty", "");
606 let encoded = msg.encode(&ctx());
607 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
608 assert_eq!(consumed, encoded.len());
609 assert_eq!(decoded.name, "empty");
610 assert_eq!(decoded.data, b"\0");
611 assert_eq!(decoded, msg);
612 }
613
614 #[test]
615 fn version_is_three() {
616 let msg = AttributeMessage::scalar_string("test", "val");
617 let encoded = msg.encode(&ctx());
618 assert_eq!(encoded[0], 3);
619 }
620
621 #[test]
622 fn decode_buffer_too_short() {
623 let buf = [0u8; 4];
624 let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
625 match err {
626 FormatError::BufferTooShort { .. } => {}
627 other => panic!("unexpected error: {:?}", other),
628 }
629 }
630
631 #[test]
632 fn decode_bad_version() {
633 let msg = AttributeMessage::scalar_string("x", "y");
634 let mut encoded = msg.encode(&ctx());
635 encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
637 match err {
638 FormatError::InvalidVersion(0) => {}
639 other => panic!("unexpected error: {:?}", other),
640 }
641 }
642
643 #[test]
644 fn array_numeric_1d_roundtrip() {
645 use crate::format::messages::datatype::DatatypeMessage;
646 let vals: [i32; 3] = [10, -20, 30];
648 let mut data = Vec::new();
649 for v in vals {
650 data.extend_from_slice(&v.to_le_bytes());
651 }
652 let msg = AttributeMessage::array_numeric(
653 "dim_offset",
654 DatatypeMessage::i32_type(),
655 &[3],
656 data.clone(),
657 );
658 assert_eq!(msg.dataspace.dims, vec![3]);
659 let encoded = msg.encode(&ctx());
660 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
661 assert_eq!(consumed, encoded.len());
662 assert_eq!(decoded.name, "dim_offset");
663 assert_eq!(decoded.dataspace.dims, vec![3]);
664 assert_eq!(decoded.data, data);
665 assert_eq!(decoded.dataspace.max_dims, Some(vec![3]));
669 assert_eq!(decoded.datatype, msg.datatype);
670 assert_eq!(decoded.dataspace.class, msg.dataspace.class);
671 }
672
673 #[test]
674 fn scalar_string_utf8_content() {
675 let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
676 let encoded = msg.encode(&ctx());
677 let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
678 assert_eq!(decoded.name, "desc");
679 assert_eq!(decoded.data.len(), 6);
681 assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
682 assert_eq!(decoded.data[5], 0);
683 }
684
685 #[test]
691 fn a_legacy_attribute_matches_the_bytes_libhdf5_wrote() {
692 let ctx = FormatContext::default_v3();
693 let attr = AttributeMessage::scalar_numeric(
694 "ra",
695 DatatypeMessage::i64_type(),
696 42i64.to_le_bytes().to_vec(),
697 );
698 let buf = attr.encode_for(
699 &ctx,
700 LibverBound::Earliest,
701 crate::format::ObjectFormat::Legacy,
702 );
703 assert_eq!(
704 buf,
705 vec![
706 0x01, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
707 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
708 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00,
709 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
710 ]
711 );
712 let (back, consumed) = AttributeMessage::decode(&buf, &ctx).unwrap();
713 assert_eq!(consumed, buf.len());
714 assert_eq!(back.name, "ra");
715 assert_eq!(back.data, 42i64.to_le_bytes().to_vec());
716 }
717
718 #[test]
721 fn a_legacy_attribute_round_trips_at_every_field_padding() {
722 let ctx = FormatContext::default_v3();
723 for name in ["a", "ab", "abcdefg", "abcdefgh", "abcdefghi"] {
724 let attr = AttributeMessage::array_numeric(
725 name,
726 DatatypeMessage::i32_type(),
727 &[3],
728 vec![1u8, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0],
729 );
730 let buf = attr.encode_for(
731 &ctx,
732 LibverBound::Earliest,
733 crate::format::ObjectFormat::Legacy,
734 );
735 assert_eq!(buf[0], 1, "{name}");
736 let (back, consumed) = AttributeMessage::decode(&buf, &ctx).unwrap();
737 assert_eq!(consumed, buf.len(), "{name}");
738 assert_eq!(back.name, name);
739 assert_eq!(back.data, attr.data, "{name}");
740 assert_eq!(back.dataspace.dims, vec![3], "{name}");
741 }
742 }
743}