1use crate::format::messages::dataspace::DataspaceMessage;
16use crate::format::messages::datatype::DatatypeMessage;
17use crate::format::{FormatContext, FormatError, FormatResult, LibverBound};
18
19const ATTR_VERSION: u8 = 3;
20
21pub const ATTR_FLAG_TYPE_SHARED: u8 = 0x01;
24pub const ATTR_FLAG_SPACE_SHARED: u8 = 0x02;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct EncodedAttribute {
35 pub body: Vec<u8>,
37 pub datatype_at: usize,
39 pub dataspace_at: usize,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct AttributeMessage {
46 pub name: String,
48 pub datatype: DatatypeMessage,
50 pub dataspace: DataspaceMessage,
52 pub data: Vec<u8>,
54}
55
56impl AttributeMessage {
57 pub fn scalar_string(name: &str, value: &str) -> Self {
63 let str_size = (value.len() + 1) as u32; let datatype = DatatypeMessage::fixed_string_utf8(str_size);
65 let dataspace = DataspaceMessage::scalar();
66
67 let mut data = Vec::with_capacity(str_size as usize);
69 data.extend_from_slice(value.as_bytes());
70 data.push(0); Self {
73 name: name.to_string(),
74 datatype,
75 dataspace,
76 data,
77 }
78 }
79
80 pub fn scalar_numeric(name: &str, datatype: DatatypeMessage, data: Vec<u8>) -> Self {
82 Self {
83 name: name.to_string(),
84 datatype,
85 dataspace: DataspaceMessage::scalar(),
86 data,
87 }
88 }
89
90 pub fn array_numeric(
98 name: &str,
99 datatype: DatatypeMessage,
100 dims: &[u64],
101 data: Vec<u8>,
102 ) -> Self {
103 debug_assert_eq!(
104 data.len() as u64,
105 dims.iter().product::<u64>() * datatype.element_size() as u64,
106 "array_numeric data length must equal product(dims) * element_size"
107 );
108 Self {
109 name: name.to_string(),
110 datatype,
111 dataspace: DataspaceMessage::simple(dims),
112 data,
113 }
114 }
115
116 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
122 self.encode_at(ctx, LibverBound::Earliest)
123 }
124
125 pub fn encode_at(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
128 self.encode_for(ctx, libver, crate::format::ObjectFormat::Modern)
129 }
130
131 fn encode_v1(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
140 fn pad_to_8(buf: &mut Vec<u8>) {
143 let padded = (buf.len() + 7) & !7;
144 buf.resize(padded, 0);
145 }
146
147 let encoded_dt = self.datatype.encode_at(ctx, libver);
148 let encoded_ds = self
149 .dataspace
150 .encode_for(ctx, crate::format::ObjectFormat::Legacy);
151 let name_bytes = self.name.as_bytes();
152 let name_size = name_bytes.len() + 1;
153
154 let mut buf = Vec::with_capacity(8 + name_size + encoded_dt.len() + encoded_ds.len() + 24);
155 buf.push(1); buf.push(0); buf.extend_from_slice(&(name_size as u16).to_le_bytes());
158 buf.extend_from_slice(&(encoded_dt.len() as u16).to_le_bytes());
159 buf.extend_from_slice(&(encoded_ds.len() as u16).to_le_bytes());
160 buf.extend_from_slice(name_bytes);
161 buf.push(0);
162 pad_to_8(&mut buf);
163 buf.extend_from_slice(&encoded_dt);
164 pad_to_8(&mut buf);
165 buf.extend_from_slice(&encoded_ds);
166 pad_to_8(&mut buf);
167 buf.extend_from_slice(&self.data);
168 buf
169 }
170
171 pub fn encode_for(
174 &self,
175 ctx: &FormatContext,
176 libver: LibverBound,
177 format: crate::format::ObjectFormat,
178 ) -> Vec<u8> {
179 if format.attribute_version() == 1 {
180 return self.encode_v1(ctx, libver);
181 }
182 let encoded_dt = self.datatype.encode_at(ctx, libver);
183 let encoded_ds = self.dataspace.encode_for(ctx, format);
184 self.encode_with_fields(0x00, &encoded_dt, &encoded_ds).body
185 }
186
187 pub fn encode_with_fields(
198 &self,
199 flags: u8,
200 datatype: &[u8],
201 dataspace: &[u8],
202 ) -> EncodedAttribute {
203 let name_bytes = self.name.as_bytes();
205 let name_size = name_bytes.len() + 1; let total = 9 + name_size + datatype.len() + dataspace.len() + self.data.len();
209 let mut buf = Vec::with_capacity(total);
210
211 buf.push(ATTR_VERSION);
213
214 buf.push(flags);
216
217 buf.extend_from_slice(&(name_size as u16).to_le_bytes());
219
220 buf.extend_from_slice(&(datatype.len() as u16).to_le_bytes());
222
223 buf.extend_from_slice(&(dataspace.len() as u16).to_le_bytes());
225
226 buf.push(0x01);
228
229 buf.extend_from_slice(name_bytes);
231 buf.push(0x00);
232
233 let datatype_at = buf.len();
234 buf.extend_from_slice(datatype);
235
236 let dataspace_at = buf.len();
237 buf.extend_from_slice(dataspace);
238
239 buf.extend_from_slice(&self.data);
241
242 debug_assert_eq!(buf.len(), total);
243 EncodedAttribute {
244 body: buf,
245 datatype_at,
246 dataspace_at,
247 }
248 }
249
250 pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
257 let AttributeHeader {
258 name,
259 datatype_size,
260 dataspace_size,
261 align,
262 mut pos,
263 } = AttributeHeader::decode(buf)?;
264
265 let needed = pos + datatype_size;
267 if buf.len() < needed {
268 return Err(FormatError::BufferTooShort {
269 needed,
270 available: buf.len(),
271 });
272 }
273 let (datatype, _) = DatatypeMessage::decode(&buf[pos..pos + datatype_size], ctx)?;
274 pos += datatype_size;
275 if align > 1 {
276 pos = (pos + align - 1) & !(align - 1);
277 }
278
279 let needed = pos + dataspace_size;
281 if buf.len() < needed {
282 return Err(FormatError::BufferTooShort {
283 needed,
284 available: buf.len(),
285 });
286 }
287 let (dataspace, _) = DataspaceMessage::decode(&buf[pos..pos + dataspace_size], ctx)?;
288 pos += dataspace_size;
289 if align > 1 {
290 pos = (pos + align - 1) & !(align - 1);
291 }
292
293 let num_elements: u64 = if dataspace.dims.is_empty() {
295 1 } else {
297 dataspace
301 .dims
302 .iter()
303 .fold(1u64, |acc, &d| acc.saturating_mul(d))
304 };
305 let data_size = num_elements
306 .saturating_mul(datatype.element_size() as u64)
307 .min(usize::MAX as u64) as usize;
308 let needed = pos.saturating_add(data_size);
309 if buf.len() < needed {
310 return Err(FormatError::BufferTooShort {
311 needed,
312 available: buf.len(),
313 });
314 }
315 let data = buf[pos..pos + data_size].to_vec();
316 pos += data_size;
317
318 Ok((
319 Self {
320 name,
321 datatype,
322 dataspace,
323 data,
324 },
325 pos,
326 ))
327 }
328}
329
330struct AttributeHeader {
336 name: String,
337 datatype_size: usize,
338 dataspace_size: usize,
339 align: usize,
341 pos: usize,
343}
344
345impl AttributeHeader {
346 fn decode(buf: &[u8]) -> FormatResult<Self> {
347 if buf.len() < 8 {
348 return Err(FormatError::BufferTooShort {
349 needed: 8,
350 available: buf.len(),
351 });
352 }
353
354 let version = buf[0];
355 if !(1..=ATTR_VERSION).contains(&version) {
356 return Err(FormatError::InvalidVersion(version));
357 }
358
359 let flags = buf[1];
369 if flags & (ATTR_FLAG_TYPE_SHARED | ATTR_FLAG_SPACE_SHARED) != 0 {
370 let what = if flags & ATTR_FLAG_TYPE_SHARED != 0 {
371 "datatype"
372 } else {
373 "dataspace"
374 };
375 return Err(FormatError::UnsupportedFeature(format!(
376 "attribute whose {what} is a shared-message reference"
377 )));
378 }
379 let name_size = u16::from_le_bytes([buf[2], buf[3]]) as usize;
380 let datatype_size = u16::from_le_bytes([buf[4], buf[5]]) as usize;
381 let dataspace_size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
382
383 let mut pos = if version >= 3 {
384 9
386 } else {
387 8
389 };
390
391 let align = if version == 1 { 8 } else { 1 };
393
394 let needed = pos + name_size;
396 if buf.len() < needed {
397 return Err(FormatError::BufferTooShort {
398 needed,
399 available: buf.len(),
400 });
401 }
402 let name_end = if name_size > 0 && buf[pos + name_size - 1] == 0 {
404 pos + name_size - 1
405 } else {
406 pos + name_size
407 };
408 let name = String::from_utf8_lossy(&buf[pos..name_end]).to_string();
409 pos += name_size;
410 if align > 1 {
412 pos = (pos + align - 1) & !(align - 1);
413 }
414
415 Ok(Self {
416 name,
417 datatype_size,
418 dataspace_size,
419 align,
420 pos,
421 })
422 }
423}
424
425#[derive(Debug, Clone, PartialEq)]
443pub struct AttributeEntry {
444 body: AttributeBody,
445 creation_index: Option<u16>,
449}
450
451#[derive(Debug, Clone, PartialEq)]
453enum AttributeBody {
454 Readable(AttributeMessage),
456 Unreadable {
460 name: String,
461 raw: Vec<u8>,
462 reason: String,
463 },
464}
465
466impl AttributeEntry {
467 pub fn parse(buf: &[u8], ctx: &FormatContext) -> FormatResult<Self> {
477 let body = match AttributeMessage::decode(buf, ctx) {
478 Ok((attr, _)) => AttributeBody::Readable(attr),
479 Err(payload_err) => {
480 let header = AttributeHeader::decode(buf)?;
481 AttributeBody::Unreadable {
482 name: header.name,
483 raw: buf.to_vec(),
484 reason: payload_err.to_string(),
485 }
486 }
487 };
488 Ok(Self {
489 body,
490 creation_index: None,
491 })
492 }
493
494 pub fn with_creation_index(mut self, creation_index: Option<u16>) -> Self {
496 self.creation_index = creation_index;
497 self
498 }
499
500 pub fn set_creation_index(&mut self, creation_index: Option<u16>) {
502 self.creation_index = creation_index;
503 }
504
505 pub fn creation_index(&self) -> Option<u16> {
508 self.creation_index
509 }
510
511 pub fn name(&self) -> &str {
513 match &self.body {
514 AttributeBody::Readable(attr) => &attr.name,
515 AttributeBody::Unreadable { name, .. } => name,
516 }
517 }
518
519 pub fn decoded(&self) -> Result<&AttributeMessage, &str> {
523 match &self.body {
524 AttributeBody::Readable(attr) => Ok(attr),
525 AttributeBody::Unreadable { reason, .. } => Err(reason),
526 }
527 }
528
529 pub fn readable(&self) -> Option<&AttributeMessage> {
531 self.decoded().ok()
532 }
533
534 pub fn unreadable_reason(&self) -> Option<&str> {
536 self.decoded().err()
537 }
538
539 pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
545 self.encode_at(ctx, LibverBound::Earliest)
546 }
547
548 pub fn encode_at(&self, ctx: &FormatContext, libver: LibverBound) -> Vec<u8> {
552 self.encode_for(ctx, libver, crate::format::ObjectFormat::Modern)
553 }
554
555 pub fn encode_for(
557 &self,
558 ctx: &FormatContext,
559 libver: LibverBound,
560 format: crate::format::ObjectFormat,
561 ) -> Vec<u8> {
562 match &self.body {
563 AttributeBody::Readable(attr) => attr.encode_for(ctx, libver, format),
564 AttributeBody::Unreadable { raw, .. } => raw.clone(),
565 }
566 }
567}
568
569impl From<AttributeMessage> for AttributeEntry {
570 fn from(attr: AttributeMessage) -> Self {
571 Self {
572 body: AttributeBody::Readable(attr),
573 creation_index: None,
574 }
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 fn ctx() -> FormatContext {
583 FormatContext {
584 sizeof_addr: 8,
585 sizeof_size: 8,
586 }
587 }
588
589 #[test]
590 fn scalar_string_roundtrip() {
591 let msg = AttributeMessage::scalar_string("my_attr", "hello");
592 let encoded = msg.encode(&ctx());
593 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
594 assert_eq!(consumed, encoded.len());
595 assert_eq!(decoded.name, "my_attr");
596 assert_eq!(decoded.data, b"hello\0");
597 assert_eq!(decoded, msg);
598 }
599
600 #[test]
601 fn scalar_string_empty() {
602 let msg = AttributeMessage::scalar_string("empty", "");
603 let encoded = msg.encode(&ctx());
604 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
605 assert_eq!(consumed, encoded.len());
606 assert_eq!(decoded.name, "empty");
607 assert_eq!(decoded.data, b"\0");
608 assert_eq!(decoded, msg);
609 }
610
611 #[test]
612 fn version_is_three() {
613 let msg = AttributeMessage::scalar_string("test", "val");
614 let encoded = msg.encode(&ctx());
615 assert_eq!(encoded[0], 3);
616 }
617
618 #[test]
619 fn decode_buffer_too_short() {
620 let buf = [0u8; 4];
621 let err = AttributeMessage::decode(&buf, &ctx()).unwrap_err();
622 match err {
623 FormatError::BufferTooShort { .. } => {}
624 other => panic!("unexpected error: {:?}", other),
625 }
626 }
627
628 #[test]
629 fn decode_bad_version() {
630 let msg = AttributeMessage::scalar_string("x", "y");
631 let mut encoded = msg.encode(&ctx());
632 encoded[0] = 0; let err = AttributeMessage::decode(&encoded, &ctx()).unwrap_err();
634 match err {
635 FormatError::InvalidVersion(0) => {}
636 other => panic!("unexpected error: {:?}", other),
637 }
638 }
639
640 #[test]
641 fn array_numeric_1d_roundtrip() {
642 use crate::format::messages::datatype::DatatypeMessage;
643 let vals: [i32; 3] = [10, -20, 30];
645 let mut data = Vec::new();
646 for v in vals {
647 data.extend_from_slice(&v.to_le_bytes());
648 }
649 let msg = AttributeMessage::array_numeric(
650 "dim_offset",
651 DatatypeMessage::i32_type(),
652 &[3],
653 data.clone(),
654 );
655 assert_eq!(msg.dataspace.dims, vec![3]);
656 let encoded = msg.encode(&ctx());
657 let (decoded, consumed) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
658 assert_eq!(consumed, encoded.len());
659 assert_eq!(decoded.name, "dim_offset");
660 assert_eq!(decoded.dataspace.dims, vec![3]);
661 assert_eq!(decoded.data, data);
662 assert_eq!(decoded.dataspace.max_dims, Some(vec![3]));
666 assert_eq!(decoded.datatype, msg.datatype);
667 assert_eq!(decoded.dataspace.class, msg.dataspace.class);
668 }
669
670 #[test]
671 fn scalar_string_utf8_content() {
672 let msg = AttributeMessage::scalar_string("desc", "caf\u{00e9}");
673 let encoded = msg.encode(&ctx());
674 let (decoded, _) = AttributeMessage::decode(&encoded, &ctx()).unwrap();
675 assert_eq!(decoded.name, "desc");
676 assert_eq!(decoded.data.len(), 6);
678 assert_eq!(&decoded.data[..5], "caf\u{00e9}".as_bytes());
679 assert_eq!(decoded.data[5], 0);
680 }
681
682 #[test]
688 fn a_legacy_attribute_matches_the_bytes_libhdf5_wrote() {
689 let ctx = FormatContext::default_v3();
690 let attr = AttributeMessage::scalar_numeric(
691 "ra",
692 DatatypeMessage::i64_type(),
693 42i64.to_le_bytes().to_vec(),
694 );
695 let buf = attr.encode_for(
696 &ctx,
697 LibverBound::Earliest,
698 crate::format::ObjectFormat::Legacy,
699 );
700 assert_eq!(
701 buf,
702 vec![
703 0x01, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
704 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
705 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00,
706 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
707 ]
708 );
709 let (back, consumed) = AttributeMessage::decode(&buf, &ctx).unwrap();
710 assert_eq!(consumed, buf.len());
711 assert_eq!(back.name, "ra");
712 assert_eq!(back.data, 42i64.to_le_bytes().to_vec());
713 }
714
715 #[test]
718 fn a_legacy_attribute_round_trips_at_every_field_padding() {
719 let ctx = FormatContext::default_v3();
720 for name in ["a", "ab", "abcdefg", "abcdefgh", "abcdefghi"] {
721 let attr = AttributeMessage::array_numeric(
722 name,
723 DatatypeMessage::i32_type(),
724 &[3],
725 vec![1u8, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0],
726 );
727 let buf = attr.encode_for(
728 &ctx,
729 LibverBound::Earliest,
730 crate::format::ObjectFormat::Legacy,
731 );
732 assert_eq!(buf[0], 1, "{name}");
733 let (back, consumed) = AttributeMessage::decode(&buf, &ctx).unwrap();
734 assert_eq!(consumed, buf.len(), "{name}");
735 assert_eq!(back.name, name);
736 assert_eq!(back.data, attr.data, "{name}");
737 assert_eq!(back.dataspace.dims, vec![3], "{name}");
738 }
739 }
740}