1use flate2::{write::ZlibEncoder, Compression};
30use std::io::{Seek, SeekFrom, Write};
31
32pub use hdf5_core::{
33 fletcher32, jenkins_lookup3, ByteOrder, ChunkIndexing, CompoundField, DataLayout,
34 DataspaceMessage, DataspaceType, Datatype, EnumMember, FillTime, FillValueMessage,
35 FilterDescription, FilterPipelineMessage, ReferenceType, StringEncoding, StringPadding,
36 StringSize, VarLenKind, FILTER_DEFLATE, FILTER_FLETCHER32, FILTER_LZ4, FILTER_NBIT,
37 FILTER_SCALEOFFSET, FILTER_SHUFFLE, FILTER_SZIP, UNLIMITED,
38};
39
40const HDF5_MAGIC: [u8; 8] = [0x89, b'H', b'D', b'F', 0x0d, 0x0a, 0x1a, 0x0a];
41const OFFSET_SIZE: u8 = 8;
42const LENGTH_SIZE: u8 = 8;
43const UNDEFINED_ADDRESS: u64 = u64::MAX;
44
45const MSG_DATASPACE: u8 = 0x01;
46const MSG_LINK_INFO: u8 = 0x02;
47const MSG_DATATYPE: u8 = 0x03;
48const MSG_FILL_VALUE: u8 = 0x05;
49const MSG_LINK: u8 = 0x06;
50const MSG_DATA_LAYOUT: u8 = 0x08;
51const MSG_GROUP_INFO: u8 = 0x0a;
52const MSG_FILTER_PIPELINE: u8 = 0x0b;
53const MSG_ATTRIBUTE: u8 = 0x0c;
54
55const SUPERBLOCK_V2_SIZE: usize = 48;
56
57#[derive(Debug, thiserror::Error)]
59pub enum Error {
60 #[error("I/O error: {0}")]
61 Io(#[from] std::io::Error),
62
63 #[error("HDF5 core error: {0}")]
64 Core(#[from] hdf5_core::Error),
65
66 #[error("invalid definition: {0}")]
67 InvalidDefinition(String),
68
69 #[error("{context}: expected {expected} element(s), got {actual}")]
72 DataLengthMismatch {
73 context: String,
74 expected: usize,
75 actual: usize,
76 },
77
78 #[error("unsupported write feature: {0}")]
79 UnsupportedFeature(String),
80}
81
82pub type Result<T> = std::result::Result<T, Error>;
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum Hdf5Variant {
87 Modern,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct WriteOptions {
94 pub byte_order: ByteOrder,
95 pub variant: Hdf5Variant,
96}
97
98impl Default for WriteOptions {
99 fn default() -> Self {
100 Self {
101 byte_order: ByteOrder::LittleEndian,
102 variant: Hdf5Variant::Modern,
103 }
104 }
105}
106
107pub trait H5WriteType {
109 fn hdf5_type() -> Datatype;
110}
111
112pub trait H5WriteElement: H5WriteType + Copy {
114 fn hdf5_type_with_order(byte_order: ByteOrder) -> Datatype;
115 fn write_one(self, byte_order: ByteOrder, dst: &mut Vec<u8>);
116}
117
118macro_rules! impl_int_type {
119 ($ty:ty, $size:literal, $signed:literal) => {
120 impl H5WriteType for $ty {
121 fn hdf5_type() -> Datatype {
122 <$ty as H5WriteElement>::hdf5_type_with_order(native_order())
123 }
124 }
125
126 impl H5WriteElement for $ty {
127 fn hdf5_type_with_order(byte_order: ByteOrder) -> Datatype {
128 Datatype::FixedPoint {
129 size: $size,
130 signed: $signed,
131 byte_order,
132 }
133 }
134
135 fn write_one(self, byte_order: ByteOrder, dst: &mut Vec<u8>) {
136 match byte_order {
137 ByteOrder::LittleEndian => dst.extend_from_slice(&self.to_le_bytes()),
138 ByteOrder::BigEndian => dst.extend_from_slice(&self.to_be_bytes()),
139 }
140 }
141 }
142 };
143}
144
145impl_int_type!(i16, 2, true);
146impl_int_type!(u16, 2, false);
147impl_int_type!(i32, 4, true);
148impl_int_type!(u32, 4, false);
149impl_int_type!(i64, 8, true);
150impl_int_type!(u64, 8, false);
151
152macro_rules! impl_byte_type {
153 ($ty:ty, $signed:literal) => {
154 impl H5WriteType for $ty {
155 fn hdf5_type() -> Datatype {
156 <$ty as H5WriteElement>::hdf5_type_with_order(native_order())
157 }
158 }
159
160 impl H5WriteElement for $ty {
161 fn hdf5_type_with_order(byte_order: ByteOrder) -> Datatype {
162 Datatype::FixedPoint {
163 size: 1,
164 signed: $signed,
165 byte_order,
166 }
167 }
168
169 fn write_one(self, _byte_order: ByteOrder, dst: &mut Vec<u8>) {
170 dst.push(self as u8);
171 }
172 }
173 };
174}
175
176impl_byte_type!(i8, true);
177impl_byte_type!(u8, false);
178
179impl H5WriteType for f32 {
180 fn hdf5_type() -> Datatype {
181 <f32 as H5WriteElement>::hdf5_type_with_order(native_order())
182 }
183}
184
185impl H5WriteElement for f32 {
186 fn hdf5_type_with_order(byte_order: ByteOrder) -> Datatype {
187 Datatype::FloatingPoint {
188 size: 4,
189 byte_order,
190 }
191 }
192
193 fn write_one(self, byte_order: ByteOrder, dst: &mut Vec<u8>) {
194 let bits = self.to_bits();
195 match byte_order {
196 ByteOrder::LittleEndian => dst.extend_from_slice(&bits.to_le_bytes()),
197 ByteOrder::BigEndian => dst.extend_from_slice(&bits.to_be_bytes()),
198 }
199 }
200}
201
202impl H5WriteType for f64 {
203 fn hdf5_type() -> Datatype {
204 <f64 as H5WriteElement>::hdf5_type_with_order(native_order())
205 }
206}
207
208impl H5WriteElement for f64 {
209 fn hdf5_type_with_order(byte_order: ByteOrder) -> Datatype {
210 Datatype::FloatingPoint {
211 size: 8,
212 byte_order,
213 }
214 }
215
216 fn write_one(self, byte_order: ByteOrder, dst: &mut Vec<u8>) {
217 let bits = self.to_bits();
218 match byte_order {
219 ByteOrder::LittleEndian => dst.extend_from_slice(&bits.to_le_bytes()),
220 ByteOrder::BigEndian => dst.extend_from_slice(&bits.to_be_bytes()),
221 }
222 }
223}
224
225fn native_order() -> ByteOrder {
226 if cfg!(target_endian = "little") {
227 ByteOrder::LittleEndian
228 } else {
229 ByteOrder::BigEndian
230 }
231}
232
233#[derive(Debug, Clone)]
238pub struct DatasetBuilder {
239 name: String,
240 datatype: Datatype,
241 shape: Vec<u64>,
242 max_shape: Option<Vec<u64>>,
243 layout: PlannedLayout,
244 filters: Vec<FilterDescription>,
245 fill_value: Option<Vec<u8>>,
246 raw_data: Option<Vec<u8>>,
247 vlen_string_values: Option<Vec<String>>,
248 vlen_sequence_values: Option<Vec<Vec<u8>>>,
249 attributes: Vec<AttributeBuilder>,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum PlannedLayout {
254 Compact,
255 Contiguous,
256 Chunked { chunk_shape: Vec<u64> },
257}
258
259impl DatasetBuilder {
260 pub fn new(name: impl Into<String>, datatype: Datatype, shape: impl Into<Vec<u64>>) -> Self {
261 Self {
262 name: name.into(),
263 datatype,
264 shape: shape.into(),
265 max_shape: None,
266 layout: PlannedLayout::Contiguous,
267 filters: Vec::new(),
268 fill_value: None,
269 raw_data: None,
270 vlen_string_values: None,
271 vlen_sequence_values: None,
272 attributes: Vec::new(),
273 }
274 }
275
276 pub fn typed<T: H5WriteType>(name: impl Into<String>, shape: impl Into<Vec<u64>>) -> Self {
277 Self::new(name, T::hdf5_type(), shape)
278 }
279
280 pub fn typed_with_order<T: H5WriteElement>(
281 name: impl Into<String>,
282 shape: impl Into<Vec<u64>>,
283 byte_order: ByteOrder,
284 ) -> Self {
285 Self::new(name, T::hdf5_type_with_order(byte_order), shape)
286 }
287
288 pub fn typed_data<T: H5WriteElement>(
289 name: impl Into<String>,
290 shape: impl Into<Vec<u64>>,
291 values: &[T],
292 ) -> Result<Self> {
293 Self::typed::<T>(name, shape).data(values)
294 }
295
296 pub fn fixed_string_data<S: AsRef<str>>(
297 name: impl Into<String>,
298 shape: impl Into<Vec<u64>>,
299 values: &[S],
300 ) -> Result<Self> {
301 let name = name.into();
302 let shape = shape.into();
303 let expected = expected_element_count(&shape)?;
304 if values.len() != expected {
305 return Err(Error::DataLengthMismatch {
306 context: format!("dataset '{name}' string data"),
307 expected,
308 actual: values.len(),
309 });
310 }
311 let (datatype, raw_data) = encode_fixed_string_values(values)?;
312 Ok(Self::new(name, datatype, shape).raw_data(raw_data))
313 }
314
315 pub fn vlen_string_data<S: AsRef<str>>(
316 name: impl Into<String>,
317 shape: impl Into<Vec<u64>>,
318 values: &[S],
319 ) -> Result<Self> {
320 let name = name.into();
321 let shape = shape.into();
322 let expected = expected_element_count(&shape)?;
323 if values.len() != expected {
324 return Err(Error::DataLengthMismatch {
325 context: format!("dataset '{name}' string data"),
326 expected,
327 actual: values.len(),
328 });
329 }
330
331 let mut strings = Vec::with_capacity(values.len());
332 let mut encoding = StringEncoding::Ascii;
333 for value in values {
334 let value = value.as_ref();
335 if value.as_bytes().contains(&0) {
336 return Err(Error::InvalidDefinition(
337 "variable-length string values cannot contain NUL bytes".into(),
338 ));
339 }
340 if !value.is_ascii() {
341 encoding = StringEncoding::Utf8;
342 }
343 strings.push(value.to_string());
344 }
345
346 Ok(Self {
347 name,
348 datatype: Datatype::String {
349 size: StringSize::Variable,
350 encoding,
351 padding: StringPadding::NullTerminate,
352 },
353 shape,
354 max_shape: None,
355 layout: PlannedLayout::Contiguous,
356 filters: Vec::new(),
357 fill_value: None,
358 raw_data: None,
359 vlen_string_values: Some(strings),
360 vlen_sequence_values: None,
361 attributes: Vec::new(),
362 })
363 }
364
365 pub fn vlen_sequence_data(
366 name: impl Into<String>,
367 base: Datatype,
368 shape: impl Into<Vec<u64>>,
369 values: Vec<Vec<u8>>,
370 ) -> Result<Self> {
371 let name = name.into();
372 let shape = shape.into();
373 let expected = expected_element_count(&shape)?;
374 if values.len() != expected {
375 return Err(Error::DataLengthMismatch {
376 context: format!("dataset '{name}' vlen sequence data"),
377 expected,
378 actual: values.len(),
379 });
380 }
381 validate_vlen_sequence_base(&base)?;
382 let base_size = datatype_element_size(&base)?;
383 for value in &values {
384 if value.len() % base_size != 0 {
385 return Err(Error::InvalidDefinition(format!(
386 "dataset '{}' vlen sequence byte length {} is not a multiple of base element size {base_size}",
387 name,
388 value.len()
389 )));
390 }
391 }
392
393 Ok(Self {
394 name,
395 datatype: Datatype::VarLen {
396 base: Box::new(base),
397 kind: VarLenKind::Sequence,
398 encoding: StringEncoding::Ascii,
399 padding: StringPadding::NullTerminate,
400 },
401 shape,
402 max_shape: None,
403 layout: PlannedLayout::Contiguous,
404 filters: Vec::new(),
405 fill_value: None,
406 raw_data: None,
407 vlen_string_values: None,
408 vlen_sequence_values: Some(values),
409 attributes: Vec::new(),
410 })
411 }
412
413 pub fn max_shape(mut self, max_shape: impl Into<Vec<u64>>) -> Self {
414 self.max_shape = Some(max_shape.into());
415 self
416 }
417
418 pub fn compact(mut self) -> Self {
419 self.layout = PlannedLayout::Compact;
420 self
421 }
422
423 pub fn chunked(mut self, chunk_shape: impl Into<Vec<u64>>) -> Self {
424 self.layout = PlannedLayout::Chunked {
425 chunk_shape: chunk_shape.into(),
426 };
427 self
428 }
429
430 pub fn filter(mut self, filter: FilterDescription) -> Self {
431 self.filters.push(filter);
432 self
433 }
434
435 pub fn fill_value(mut self, bytes: impl Into<Vec<u8>>) -> Self {
436 self.fill_value = Some(bytes.into());
437 self
438 }
439
440 pub fn raw_data(mut self, bytes: impl Into<Vec<u8>>) -> Self {
441 self.raw_data = Some(bytes.into());
442 self.vlen_string_values = None;
443 self.vlen_sequence_values = None;
444 self
445 }
446
447 pub fn data<T: H5WriteElement>(mut self, values: &[T]) -> Result<Self> {
448 let byte_order = numeric_datatype_order(&self.datatype)?;
449 ensure_datatype_matches_element::<T>(&self.datatype)?;
450 let expected = expected_data_len(&self.shape, datatype_element_size(&self.datatype)?)?;
451 let actual = std::mem::size_of_val(values);
452 if actual != expected {
453 return Err(Error::DataLengthMismatch {
454 context: format!("dataset '{}' data (bytes)", self.name),
455 expected,
456 actual,
457 });
458 }
459
460 let mut bytes = Vec::with_capacity(actual);
461 for &value in values {
462 value.write_one(byte_order, &mut bytes);
463 }
464 self.raw_data = Some(bytes);
465 self.vlen_string_values = None;
466 self.vlen_sequence_values = None;
467 Ok(self)
468 }
469
470 pub fn attribute(mut self, attribute: AttributeBuilder) -> Self {
471 self.attributes.push(attribute);
472 self
473 }
474
475 pub fn datatype(&self) -> &Datatype {
476 &self.datatype
477 }
478
479 pub fn shape(&self) -> &[u64] {
480 &self.shape
481 }
482
483 pub fn raw_data_bytes(&self) -> Option<&[u8]> {
484 self.raw_data.as_deref()
485 }
486
487 pub fn validate(&self) -> Result<()> {
488 validate_name(&self.name)?;
489 if self.shape.len() > u8::MAX as usize {
490 return Err(Error::InvalidDefinition(
491 "dataset rank exceeds HDF5 rank field capacity".into(),
492 ));
493 }
494 datatype_element_size(&self.datatype)?;
495 if let Some(max_shape) = &self.max_shape {
496 if max_shape.len() != self.shape.len() {
497 return Err(Error::InvalidDefinition(
498 "dataset max_shape rank must match shape rank".into(),
499 ));
500 }
501 if !matches!(self.layout, PlannedLayout::Chunked { .. }) {
502 return Err(Error::InvalidDefinition(
503 "resizable HDF5 datasets must use chunked layout".into(),
504 ));
505 }
506 for (&dim, &max_dim) in self.shape.iter().zip(max_shape) {
507 if max_dim != UNLIMITED && max_dim < dim {
508 return Err(Error::InvalidDefinition(
509 "dataset max_shape cannot be smaller than current shape".into(),
510 ));
511 }
512 }
513 }
514 if !self.filters.is_empty() && !matches!(self.layout, PlannedLayout::Chunked { .. }) {
515 return Err(Error::InvalidDefinition(
516 "filtered HDF5 datasets must use chunked layout".into(),
517 ));
518 }
519 if let PlannedLayout::Chunked { chunk_shape } = &self.layout {
520 if chunk_shape.len() != self.shape.len() {
521 return Err(Error::InvalidDefinition(
522 "chunk shape rank must match dataset rank".into(),
523 ));
524 }
525 if chunk_shape.contains(&0) {
526 return Err(Error::InvalidDefinition(
527 "chunk dimensions must be non-zero".into(),
528 ));
529 }
530 }
531 if let Some(fill_value) = &self.fill_value {
532 let element_size = datatype_element_size(&self.datatype)?;
533 if fill_value.len() != element_size {
534 return Err(Error::InvalidDefinition(format!(
535 "dataset '{}' fill value byte length must match datatype element size: expected {element_size}, got {}",
536 self.name,
537 fill_value.len()
538 )));
539 }
540 }
541 if let Some(values) = &self.vlen_sequence_values {
542 let Datatype::VarLen {
543 base,
544 kind: VarLenKind::Sequence,
545 ..
546 } = &self.datatype
547 else {
548 return Err(Error::InvalidDefinition(format!(
549 "dataset '{}' vlen sequence values require a sequence datatype",
550 self.name
551 )));
552 };
553 let expected = expected_element_count(&self.shape)?;
554 if values.len() != expected {
555 return Err(Error::InvalidDefinition(format!(
556 "dataset '{}' vlen sequence shape must match value count",
557 self.name
558 )));
559 }
560 validate_vlen_sequence_base(base)?;
561 let base_size = datatype_element_size(base)?;
562 for value in values {
563 if value.len() % base_size != 0 {
564 return Err(Error::InvalidDefinition(format!(
565 "dataset '{}' vlen sequence byte length {} is not a multiple of base element size {base_size}",
566 self.name,
567 value.len()
568 )));
569 }
570 }
571 }
572 validate_attributes(&self.attributes)?;
573 Ok(())
574 }
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
582pub struct AttributeBuilder {
583 name: String,
584 datatype: Datatype,
585 shape: Vec<u64>,
586 raw_data: Vec<u8>,
587 vlen_object_reference_targets: Option<Vec<Vec<String>>>,
588 vlen_string_values: Option<Vec<String>>,
589 vlen_sequence_values: Option<Vec<Vec<u8>>>,
590 object_reference_list: Option<Vec<(String, u32)>>,
594}
595
596impl AttributeBuilder {
597 pub fn new(
598 name: impl Into<String>,
599 datatype: Datatype,
600 shape: impl Into<Vec<u64>>,
601 raw_data: impl Into<Vec<u8>>,
602 ) -> Self {
603 Self {
604 name: name.into(),
605 datatype,
606 shape: shape.into(),
607 raw_data: raw_data.into(),
608 vlen_object_reference_targets: None,
609 vlen_string_values: None,
610 vlen_sequence_values: None,
611 object_reference_list: None,
612 }
613 }
614
615 pub fn scalar<T: H5WriteElement>(name: impl Into<String>, value: T) -> Result<Self> {
616 let datatype = T::hdf5_type();
617 let byte_order = numeric_datatype_order(&datatype)?;
618 let mut raw_data = Vec::with_capacity(datatype_element_size(&datatype)?);
619 value.write_one(byte_order, &mut raw_data);
620 Ok(Self::new(name, datatype, Vec::new(), raw_data))
621 }
622
623 pub fn vector<T: H5WriteElement>(name: impl Into<String>, values: &[T]) -> Result<Self> {
624 let datatype = T::hdf5_type();
625 let byte_order = numeric_datatype_order(&datatype)?;
626 let element_size = datatype_element_size(&datatype)?;
627 let mut raw_data = Vec::with_capacity(values.len() * element_size);
628 for &value in values {
629 value.write_one(byte_order, &mut raw_data);
630 }
631 Ok(Self::new(
632 name,
633 datatype,
634 vec![values.len() as u64],
635 raw_data,
636 ))
637 }
638
639 pub fn fixed_string(name: impl Into<String>, value: impl AsRef<str>) -> Self {
640 let value = value.as_ref();
641 let mut raw_data = Vec::with_capacity(value.len() + 1);
642 raw_data.extend_from_slice(value.as_bytes());
643 raw_data.push(0);
644 Self::new(
645 name,
646 Datatype::String {
647 size: StringSize::Fixed(raw_data.len() as u32),
648 encoding: if value.is_ascii() {
649 StringEncoding::Ascii
650 } else {
651 StringEncoding::Utf8
652 },
653 padding: StringPadding::NullTerminate,
654 },
655 Vec::new(),
656 raw_data,
657 )
658 }
659
660 pub fn fixed_string_vector<S: AsRef<str>>(
661 name: impl Into<String>,
662 values: &[S],
663 ) -> Result<Self> {
664 let element_count = u64::try_from(values.len()).map_err(|_| {
665 Error::InvalidDefinition("string attribute element count exceeds u64 capacity".into())
666 })?;
667 let (datatype, raw_data) = encode_fixed_string_values(values)?;
668 Ok(Self::new(name, datatype, vec![element_count], raw_data))
669 }
670
671 pub fn vlen_object_references(
672 name: impl Into<String>,
673 target_sequences: Vec<Vec<String>>,
674 ) -> Self {
675 Self {
676 name: name.into(),
677 datatype: Datatype::VarLen {
678 base: Box::new(Datatype::Reference {
679 ref_type: ReferenceType::Object,
680 size: OFFSET_SIZE,
681 }),
682 kind: VarLenKind::Sequence,
683 encoding: StringEncoding::Ascii,
684 padding: StringPadding::NullTerminate,
685 },
686 shape: vec![target_sequences.len() as u64],
687 raw_data: Vec::new(),
688 vlen_object_reference_targets: Some(target_sequences),
689 vlen_string_values: None,
690 vlen_sequence_values: None,
691 object_reference_list: None,
692 }
693 }
694
695 pub fn object_reference_list(name: impl Into<String>, entries: Vec<(String, u32)>) -> Self {
700 Self {
701 name: name.into(),
702 datatype: reference_list_datatype(),
703 shape: vec![entries.len() as u64],
704 raw_data: Vec::new(),
705 vlen_object_reference_targets: None,
706 vlen_string_values: None,
707 vlen_sequence_values: None,
708 object_reference_list: Some(entries),
709 }
710 }
711
712 pub fn vlen_strings<S: AsRef<str>>(name: impl Into<String>, values: &[S]) -> Result<Self> {
713 let element_count = u64::try_from(values.len()).map_err(|_| {
714 Error::InvalidDefinition("string attribute element count exceeds u64 capacity".into())
715 })?;
716 let mut strings = Vec::with_capacity(values.len());
717 let mut encoding = StringEncoding::Ascii;
718 for value in values {
719 let value = value.as_ref();
720 if value.as_bytes().contains(&0) {
721 return Err(Error::InvalidDefinition(
722 "variable-length string values cannot contain NUL bytes".into(),
723 ));
724 }
725 if !value.is_ascii() {
726 encoding = StringEncoding::Utf8;
727 }
728 strings.push(value.to_string());
729 }
730
731 Ok(Self {
732 name: name.into(),
733 datatype: Datatype::VarLen {
734 base: Box::new(Datatype::FixedPoint {
735 size: 1,
736 signed: false,
737 byte_order: ByteOrder::LittleEndian,
738 }),
739 kind: VarLenKind::String,
740 encoding,
741 padding: StringPadding::NullTerminate,
742 },
743 shape: vec![element_count],
744 raw_data: Vec::new(),
745 vlen_object_reference_targets: None,
746 vlen_string_values: Some(strings),
747 vlen_sequence_values: None,
748 object_reference_list: None,
749 })
750 }
751
752 pub fn vlen_sequences(
753 name: impl Into<String>,
754 base: Datatype,
755 values: Vec<Vec<u8>>,
756 ) -> Result<Self> {
757 let name = name.into();
758 validate_vlen_sequence_base(&base)?;
759 let base_size = datatype_element_size(&base)?;
760 for value in &values {
761 if value.len() % base_size != 0 {
762 return Err(Error::InvalidDefinition(format!(
763 "attribute '{}' vlen sequence byte length {} is not a multiple of base element size {base_size}",
764 name,
765 value.len()
766 )));
767 }
768 }
769 let element_count = u64::try_from(values.len()).map_err(|_| {
770 Error::InvalidDefinition(
771 "vlen sequence attribute element count exceeds u64 capacity".into(),
772 )
773 })?;
774
775 Ok(Self {
776 name,
777 datatype: Datatype::VarLen {
778 base: Box::new(base),
779 kind: VarLenKind::Sequence,
780 encoding: StringEncoding::Ascii,
781 padding: StringPadding::NullTerminate,
782 },
783 shape: vec![element_count],
784 raw_data: Vec::new(),
785 vlen_object_reference_targets: None,
786 vlen_string_values: None,
787 vlen_sequence_values: Some(values),
788 object_reference_list: None,
789 })
790 }
791
792 pub fn name(&self) -> &str {
793 &self.name
794 }
795
796 pub fn datatype(&self) -> &Datatype {
797 &self.datatype
798 }
799
800 pub fn shape(&self) -> &[u64] {
801 &self.shape
802 }
803
804 pub fn raw_data(&self) -> &[u8] {
805 &self.raw_data
806 }
807
808 pub fn validate(&self) -> Result<()> {
809 validate_name(&self.name)?;
810 if let Some(values) = &self.vlen_string_values {
811 if self.shape != [values.len() as u64] {
812 return Err(Error::InvalidDefinition(format!(
813 "attribute '{}' vlen string shape must match value count",
814 self.name
815 )));
816 }
817 for value in values {
818 if value.as_bytes().contains(&0) {
819 return Err(Error::InvalidDefinition(format!(
820 "attribute '{}' vlen string values cannot contain NUL bytes",
821 self.name
822 )));
823 }
824 }
825 return Ok(());
826 }
827 if let Some(values) = &self.vlen_sequence_values {
828 let Datatype::VarLen {
829 base,
830 kind: VarLenKind::Sequence,
831 ..
832 } = &self.datatype
833 else {
834 return Err(Error::InvalidDefinition(format!(
835 "attribute '{}' vlen sequence values require a sequence datatype",
836 self.name
837 )));
838 };
839 if self.shape != [values.len() as u64] {
840 return Err(Error::InvalidDefinition(format!(
841 "attribute '{}' vlen sequence shape must match value count",
842 self.name
843 )));
844 }
845 validate_vlen_sequence_base(base)?;
846 let base_size = datatype_element_size(base)?;
847 for value in values {
848 if value.len() % base_size != 0 {
849 return Err(Error::InvalidDefinition(format!(
850 "attribute '{}' vlen sequence byte length {} is not a multiple of base element size {base_size}",
851 self.name,
852 value.len()
853 )));
854 }
855 }
856 return Ok(());
857 }
858 if let Some(target_sequences) = &self.vlen_object_reference_targets {
859 if self.shape != [target_sequences.len() as u64] {
860 return Err(Error::InvalidDefinition(format!(
861 "attribute '{}' vlen reference shape must match sequence count",
862 self.name
863 )));
864 }
865 for sequence in target_sequences {
866 for target in sequence {
867 validate_name(target)?;
868 }
869 }
870 return Ok(());
871 }
872 if let Some(entries) = &self.object_reference_list {
873 if self.shape != [entries.len() as u64] {
874 return Err(Error::InvalidDefinition(format!(
875 "attribute '{}' reference-list shape must match entry count",
876 self.name
877 )));
878 }
879 for (target, _) in entries {
880 validate_name(target)?;
881 }
882 return Ok(());
883 }
884 let expected = expected_data_len(&self.shape, datatype_element_size(&self.datatype)?)?;
885 if self.raw_data.len() != expected {
886 return Err(Error::DataLengthMismatch {
887 context: format!("attribute '{}' data (bytes)", self.name),
888 expected,
889 actual: self.raw_data.len(),
890 });
891 }
892 Ok(())
893 }
894}
895
896const REFERENCE_LIST_ENTRY_SIZE: u32 = 16;
899const REFERENCE_LIST_DIMENSION_OFFSET: usize = 8;
900
901fn reference_list_datatype() -> Datatype {
904 Datatype::Compound {
905 size: REFERENCE_LIST_ENTRY_SIZE,
906 fields: vec![
907 CompoundField {
908 name: "dataset".to_string(),
909 byte_offset: 0,
910 datatype: Datatype::Reference {
911 ref_type: ReferenceType::Object,
912 size: OFFSET_SIZE,
913 },
914 },
915 CompoundField {
916 name: "dimension".to_string(),
917 byte_offset: REFERENCE_LIST_DIMENSION_OFFSET as u32,
918 datatype: Datatype::FixedPoint {
919 size: 4,
920 signed: false,
921 byte_order: ByteOrder::LittleEndian,
922 },
923 },
924 ],
925 }
926}
927
928#[derive(Debug, Clone, PartialEq, Eq)]
930pub struct GroupAttributeBuilder {
931 path: String,
932 attribute: AttributeBuilder,
933}
934
935impl GroupAttributeBuilder {
936 pub fn new(group_path: impl Into<String>, attribute: AttributeBuilder) -> Self {
937 Self {
938 path: group_path.into(),
939 attribute,
940 }
941 }
942
943 pub fn path(&self) -> &str {
944 &self.path
945 }
946
947 pub fn attribute(&self) -> &AttributeBuilder {
948 &self.attribute
949 }
950
951 pub fn validate(&self) -> Result<()> {
952 validate_name(&self.path)?;
953 self.attribute.validate()
954 }
955}
956
957#[derive(Debug, Clone, Default)]
961pub struct Hdf5Builder {
962 datasets: Vec<DatasetBuilder>,
963 attributes: Vec<AttributeBuilder>,
964 group_attributes: Vec<GroupAttributeBuilder>,
965}
966
967impl Hdf5Builder {
968 pub fn new() -> Self {
969 Self::default()
970 }
971
972 pub fn dataset(mut self, dataset: DatasetBuilder) -> Self {
973 self.datasets.push(dataset);
974 self
975 }
976
977 pub fn attribute(mut self, attribute: AttributeBuilder) -> Self {
978 self.attributes.push(attribute);
979 self
980 }
981
982 pub fn group_attribute(
983 mut self,
984 group_path: impl Into<String>,
985 attribute: AttributeBuilder,
986 ) -> Self {
987 self.group_attributes
988 .push(GroupAttributeBuilder::new(group_path, attribute));
989 self
990 }
991
992 pub fn validate(&self) -> Result<()> {
993 let mut names = std::collections::BTreeSet::new();
994 let mut group_paths = std::collections::BTreeSet::new();
995 for dataset in &self.datasets {
996 dataset.validate()?;
997 if !names.insert(dataset.name.as_str()) {
998 return Err(Error::InvalidDefinition(format!(
999 "duplicate root dataset '{}'",
1000 dataset.name
1001 )));
1002 }
1003 let mut parent = parent_path(&dataset.name);
1004 while let Some(path) = parent {
1005 group_paths.insert(path.to_string());
1006 parent = parent_path(path);
1007 }
1008 }
1009 for group_attribute in &self.group_attributes {
1010 group_attribute.validate()?;
1011 let mut path = Some(group_attribute.path.as_str());
1012 while let Some(group_path) = path {
1013 group_paths.insert(group_path.to_string());
1014 path = parent_path(group_path);
1015 }
1016 }
1017 for dataset in &self.datasets {
1018 if group_paths.contains(dataset.name.as_str()) {
1019 return Err(Error::InvalidDefinition(format!(
1020 "dataset '{}' conflicts with an implicit HDF5 group at the same path",
1021 dataset.name
1022 )));
1023 }
1024 }
1025 validate_attributes(&self.attributes)?;
1026 validate_group_attributes(&self.group_attributes)?;
1027 Ok(())
1028 }
1029
1030 pub fn into_plan(self) -> Result<Hdf5WritePlan> {
1031 self.validate()?;
1032 Ok(Hdf5WritePlan {
1033 datasets: self.datasets,
1034 attributes: self.attributes,
1035 group_attributes: self.group_attributes,
1036 })
1037 }
1038}
1039
1040#[derive(Debug, Clone)]
1044pub struct Hdf5WritePlan {
1045 datasets: Vec<DatasetBuilder>,
1046 attributes: Vec<AttributeBuilder>,
1047 group_attributes: Vec<GroupAttributeBuilder>,
1048}
1049
1050impl Hdf5WritePlan {
1051 pub fn datasets(&self) -> &[DatasetBuilder] {
1052 &self.datasets
1053 }
1054
1055 pub fn attributes(&self) -> &[AttributeBuilder] {
1056 &self.attributes
1057 }
1058
1059 pub fn group_attributes(&self) -> &[GroupAttributeBuilder] {
1060 &self.group_attributes
1061 }
1062
1063 pub fn validate(&self) -> Result<()> {
1064 Hdf5Builder {
1065 datasets: self.datasets.clone(),
1066 attributes: self.attributes.clone(),
1067 group_attributes: self.group_attributes.clone(),
1068 }
1069 .validate()
1070 }
1071
1072 pub fn encode(&self, options: WriteOptions) -> Result<Vec<u8>> {
1076 encode_hdf5_file(self, options)
1077 }
1078}
1079
1080pub struct Hdf5Writer<W: Write + Seek> {
1083 sink: W,
1084 options: WriteOptions,
1085}
1086
1087impl<W: Write + Seek> Hdf5Writer<W> {
1088 pub fn new(sink: W, options: WriteOptions) -> Self {
1089 Self { sink, options }
1090 }
1091
1092 pub fn finish(mut self, plan: Hdf5WritePlan) -> Result<W> {
1095 let bytes = encode_hdf5_file(&plan, self.options)?;
1096 self.sink.seek(SeekFrom::Start(0))?;
1097 self.sink.write_all(&bytes)?;
1098 Ok(self.sink)
1099 }
1100
1101 pub fn into_inner(self) -> W {
1102 self.sink
1103 }
1104}
1105
1106fn validate_name(name: &str) -> Result<()> {
1107 if name.is_empty() {
1108 return Err(Error::InvalidDefinition("name must not be empty".into()));
1109 }
1110 if name == "/" || name.split('/').any(str::is_empty) {
1111 return Err(Error::InvalidDefinition(format!(
1112 "invalid HDF5 relative path '{name}'"
1113 )));
1114 }
1115 Ok(())
1116}
1117
1118fn validate_attributes(attributes: &[AttributeBuilder]) -> Result<()> {
1119 let mut names = std::collections::BTreeSet::new();
1120 for attribute in attributes {
1121 attribute.validate()?;
1122 if !names.insert(attribute.name.as_str()) {
1123 return Err(Error::InvalidDefinition(format!(
1124 "duplicate attribute '{}'",
1125 attribute.name
1126 )));
1127 }
1128 }
1129 Ok(())
1130}
1131
1132fn validate_group_attributes(group_attributes: &[GroupAttributeBuilder]) -> Result<()> {
1133 let mut names_by_group =
1134 std::collections::BTreeMap::<&str, std::collections::BTreeSet<&str>>::new();
1135 for group_attribute in group_attributes {
1136 group_attribute.validate()?;
1137 let group_names = names_by_group
1138 .entry(group_attribute.path.as_str())
1139 .or_default();
1140 if !group_names.insert(group_attribute.attribute.name.as_str()) {
1141 return Err(Error::InvalidDefinition(format!(
1142 "duplicate attribute '{}' on HDF5 group '{}'",
1143 group_attribute.attribute.name, group_attribute.path
1144 )));
1145 }
1146 }
1147 Ok(())
1148}
1149
1150#[derive(Debug)]
1151struct DatasetEmission<'a> {
1152 dataset: &'a DatasetBuilder,
1153 raw_data: Vec<u8>,
1154 header_address: u64,
1155 data_address: u64,
1156 header: Vec<u8>,
1157 chunk_index: Option<FixedArrayChunkIndexEmission>,
1158}
1159
1160#[derive(Debug)]
1161struct GroupEmission {
1162 header_address: u64,
1163 header: Vec<u8>,
1164}
1165
1166#[derive(Debug)]
1167struct FixedArrayChunkIndexEmission {
1168 header_address: u64,
1169 data_block_address: u64,
1170 header: Vec<u8>,
1171 data_block: Vec<u8>,
1172}
1173
1174#[derive(Debug)]
1175struct HeaderMessage {
1176 type_id: u8,
1177 payload: Vec<u8>,
1178}
1179
1180#[derive(Debug, Clone)]
1181struct PlannedAttribute {
1182 name: String,
1183 datatype: Datatype,
1184 shape: Vec<u64>,
1185 raw_data: PlannedAttributeRaw,
1186}
1187
1188#[derive(Debug, Clone)]
1189enum PlannedAttributeRaw {
1190 Inline(Vec<u8>),
1191 GlobalHeapVLenReferences(Vec<VLenHeapReference>),
1192}
1193
1194#[derive(Debug, Clone)]
1195struct VLenHeapReference {
1196 sequence_len: u32,
1197 heap_index: u16,
1198}
1199
1200#[derive(Debug, Clone)]
1201struct GlobalHeapObjectPlan {
1202 index: u16,
1203 reference_count: u16,
1204 data: Vec<u8>,
1205}
1206
1207#[derive(Debug, Clone)]
1208struct PlannedAttributes {
1209 root: Vec<PlannedAttribute>,
1210 groups: Vec<Vec<PlannedAttribute>>,
1211 datasets: Vec<Vec<PlannedAttribute>>,
1212 dataset_vlen_refs: Vec<Option<Vec<VLenHeapReference>>>,
1213 heap_objects: Vec<GlobalHeapObjectPlan>,
1214}
1215
1216#[derive(Debug, Clone)]
1217struct GroupHierarchy {
1218 paths: Vec<String>,
1219 root_links: Vec<PlannedLink>,
1220 group_links: Vec<Vec<PlannedLink>>,
1221}
1222
1223#[derive(Debug, Clone, PartialEq, Eq)]
1224struct PlannedLink {
1225 name: String,
1226 target: PlannedLinkTarget,
1227}
1228
1229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1230enum PlannedLinkTarget {
1231 Group(usize),
1232 Dataset(usize),
1233}
1234
1235impl PlannedAttribute {
1236 fn raw_data(&self, heap_address: u64) -> Vec<u8> {
1237 match &self.raw_data {
1238 PlannedAttributeRaw::Inline(raw_data) => raw_data.clone(),
1239 PlannedAttributeRaw::GlobalHeapVLenReferences(refs) => {
1240 encode_vlen_heap_references(refs, heap_address)
1241 }
1242 }
1243 }
1244}
1245
1246fn encode_vlen_heap_references(refs: &[VLenHeapReference], heap_address: u64) -> Vec<u8> {
1247 let mut raw_data = Vec::with_capacity(refs.len() * (4 + usize::from(OFFSET_SIZE) + 4));
1248 for reference in refs {
1249 if reference.heap_index == 0 {
1250 raw_data.resize(raw_data.len() + 4 + usize::from(OFFSET_SIZE) + 4, 0);
1253 continue;
1254 }
1255 raw_data.extend_from_slice(&reference.sequence_len.to_le_bytes());
1256 raw_data.extend_from_slice(&heap_address.to_le_bytes());
1257 raw_data.extend_from_slice(&(u32::from(reference.heap_index)).to_le_bytes());
1258 }
1259 raw_data
1260}
1261
1262fn encode_hdf5_file(plan: &Hdf5WritePlan, options: WriteOptions) -> Result<Vec<u8>> {
1263 if options.variant != Hdf5Variant::Modern {
1264 return Err(Error::UnsupportedFeature(format!(
1265 "unsupported HDF5 variant {:?}",
1266 options.variant
1267 )));
1268 }
1269
1270 let mut prepared = prepare_datasets(plan, options)?;
1271 let groups = plan_group_hierarchy(&prepared, &plan.group_attributes)?;
1272 let placeholder_group_addresses = vec![0; groups.paths.len()];
1273 let placeholder_dataset_addresses = vec![0; prepared.len()];
1274 let placeholder_targets =
1275 placeholder_target_addresses(&prepared, &groups, &placeholder_group_addresses);
1276 let placeholder_attributes = plan_attributes(plan, &groups, &placeholder_targets)?;
1277 let root_header_size = encode_group_header_from_plan(
1278 &groups.root_links,
1279 &placeholder_group_addresses,
1280 &placeholder_dataset_addresses,
1281 &placeholder_attributes.root,
1282 0,
1283 )?
1284 .len();
1285 let mut next_address = align_u64(SUPERBLOCK_V2_SIZE as u64, 8);
1286 let root_address = next_address;
1287 next_address = align_u64(
1288 checked_add_u64(
1289 next_address,
1290 root_header_size as u64,
1291 "root object header end",
1292 )?,
1293 8,
1294 );
1295
1296 let mut group_addresses = Vec::with_capacity(groups.paths.len());
1297 for (group_links, attributes) in groups
1298 .group_links
1299 .iter()
1300 .zip(&placeholder_attributes.groups)
1301 {
1302 group_addresses.push(next_address);
1303 let placeholder = encode_group_header_from_plan(
1304 group_links,
1305 &placeholder_group_addresses,
1306 &placeholder_dataset_addresses,
1307 attributes,
1308 0,
1309 )?;
1310 next_address = align_u64(
1311 checked_add_u64(
1312 next_address,
1313 placeholder.len() as u64,
1314 "group object header end",
1315 )?,
1316 8,
1317 );
1318 }
1319
1320 let mut header_addresses = Vec::with_capacity(prepared.len());
1321 for (prepared_dataset, attributes) in prepared.iter().zip(&placeholder_attributes.datasets) {
1322 header_addresses.push(next_address);
1323 let placeholder = encode_dataset_header(prepared_dataset, attributes, 0, 0)?;
1324 next_address = align_u64(
1325 checked_add_u64(
1326 next_address,
1327 placeholder.len() as u64,
1328 "dataset object header end",
1329 )?,
1330 8,
1331 );
1332 }
1333
1334 let mut chunk_index_addresses = Vec::with_capacity(prepared.len());
1335 for prepared_dataset in &prepared {
1336 if let Some(chunk_index) = &prepared_dataset.chunk_index {
1337 let (header_len, data_block_len) = chunk_index_block_sizes(chunk_index)?;
1340 let header_address = next_address;
1341 next_address = align_u64(
1342 checked_add_u64(next_address, header_len as u64, "chunk index header end")?,
1343 8,
1344 );
1345 let data_block_address = next_address;
1346 next_address = align_u64(
1347 checked_add_u64(
1348 next_address,
1349 data_block_len as u64,
1350 "chunk index secondary block end",
1351 )?,
1352 8,
1353 );
1354 chunk_index_addresses.push(Some(FixedArrayChunkIndexAddresses {
1355 header: header_address,
1356 data_block: data_block_address,
1357 }));
1358 } else {
1359 chunk_index_addresses.push(None);
1360 }
1361 }
1362
1363 let data_start_address = next_address;
1364
1365 let target_addresses =
1366 target_addresses(&prepared, &header_addresses, &groups, &group_addresses);
1367 let planned_attributes = plan_attributes(plan, &groups, &target_addresses)?;
1368
1369 let heap = if planned_attributes.heap_objects.is_empty() {
1370 Vec::new()
1371 } else {
1372 encode_global_heap_collection(&planned_attributes.heap_objects)?
1373 };
1374 let DataHeapLayout {
1375 data_addresses,
1376 heap_address,
1377 eof_address,
1378 } = stabilize_vlen_dataset_storage(
1379 &mut prepared,
1380 &planned_attributes,
1381 data_start_address,
1382 &heap,
1383 )?;
1384
1385 let root_header = encode_group_header_from_plan(
1386 &groups.root_links,
1387 &group_addresses,
1388 &header_addresses,
1389 &planned_attributes.root,
1390 heap_address,
1391 )?;
1392
1393 let mut group_emissions = Vec::with_capacity(groups.paths.len());
1394 for ((group_links, attributes), header_address) in groups
1395 .group_links
1396 .iter()
1397 .zip(&planned_attributes.groups)
1398 .zip(group_addresses.iter().copied())
1399 {
1400 let header = encode_group_header_from_plan(
1401 group_links,
1402 &group_addresses,
1403 &header_addresses,
1404 attributes,
1405 heap_address,
1406 )?;
1407 group_emissions.push(GroupEmission {
1408 header_address,
1409 header,
1410 });
1411 }
1412
1413 let mut emissions = Vec::with_capacity(prepared.len());
1414 for ((((prepared_dataset, attributes), header_address), data_address), chunk_index_address) in
1417 prepared
1418 .into_iter()
1419 .zip(&planned_attributes.datasets)
1420 .zip(header_addresses.iter().copied())
1421 .zip(data_addresses.iter().copied())
1422 .zip(chunk_index_addresses.iter().copied())
1423 {
1424 let layout_address = chunk_index_address.map_or(data_address, |address| address.header);
1425 let header =
1426 encode_dataset_header(&prepared_dataset, attributes, layout_address, heap_address)?;
1427 let chunk_index = if let (Some(chunk_index), Some(addresses)) =
1428 (&prepared_dataset.chunk_index, chunk_index_address)
1429 {
1430 let (header, data_block) = encode_chunk_index_blocks(
1431 chunk_index,
1432 addresses.header,
1433 addresses.data_block,
1434 data_address,
1435 )?;
1436 Some(FixedArrayChunkIndexEmission {
1437 header_address: addresses.header,
1438 data_block_address: addresses.data_block,
1439 header,
1440 data_block,
1441 })
1442 } else {
1443 None
1444 };
1445 emissions.push(DatasetEmission {
1446 dataset: prepared_dataset.dataset,
1447 raw_data: prepared_dataset.raw_data,
1448 header_address,
1449 data_address,
1450 header,
1451 chunk_index,
1452 });
1453 }
1454
1455 let mut file = Vec::with_capacity(checked_usize(eof_address, "HDF5 file size")?);
1456 file.extend_from_slice(&encode_superblock_v2(root_address, eof_address));
1457 pad_to_address(&mut file, root_address)?;
1458 file.extend_from_slice(&root_header);
1459
1460 for emission in &group_emissions {
1461 pad_to_address(&mut file, emission.header_address)?;
1462 file.extend_from_slice(&emission.header);
1463 }
1464
1465 for emission in &emissions {
1466 pad_to_address(&mut file, emission.header_address)?;
1467 file.extend_from_slice(&emission.header);
1468 }
1469
1470 for emission in &emissions {
1471 if let Some(chunk_index) = &emission.chunk_index {
1472 pad_to_address(&mut file, chunk_index.header_address)?;
1473 file.extend_from_slice(&chunk_index.header);
1474 pad_to_address(&mut file, chunk_index.data_block_address)?;
1475 file.extend_from_slice(&chunk_index.data_block);
1476 }
1477 }
1478
1479 for emission in &emissions {
1480 if !matches!(emission.dataset.layout, PlannedLayout::Compact) {
1481 pad_to_address(&mut file, emission.data_address)?;
1482 file.extend_from_slice(&emission.raw_data);
1483 }
1484 }
1485
1486 if !heap.is_empty() {
1487 pad_to_address(&mut file, heap_address)?;
1488 file.extend_from_slice(&heap);
1489 }
1490
1491 pad_to_address(&mut file, eof_address)?;
1492 Ok(file)
1493}
1494
1495#[derive(Debug)]
1496struct PreparedDataset<'a> {
1497 dataset: &'a DatasetBuilder,
1498 filters: Vec<FilterDescription>,
1499 raw_data: Vec<u8>,
1500 data_size: u64,
1501 chunk_index: Option<PreparedChunkIndex>,
1502}
1503
1504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1510enum ChunkIndexKind {
1511 FixedArray,
1512 BtreeV2,
1513}
1514
1515#[derive(Debug)]
1516struct PreparedChunkIndex {
1517 kind: ChunkIndexKind,
1518 chunks: Vec<PreparedChunkIndexEntry>,
1519 chunk_size_len: u8,
1521}
1522
1523#[derive(Debug)]
1524struct PreparedChunkIndexEntry {
1525 relative_address: u64,
1526 size: u64,
1527 filter_mask: u32,
1528 scaled_offsets: Vec<u64>,
1531}
1532
1533#[derive(Debug, Clone)]
1534struct DataHeapLayout {
1535 data_addresses: Vec<u64>,
1536 heap_address: u64,
1537 eof_address: u64,
1538}
1539
1540#[derive(Debug, Clone, Copy)]
1541struct FixedArrayChunkIndexAddresses {
1542 header: u64,
1543 data_block: u64,
1544}
1545
1546fn prepare_datasets(
1547 plan: &Hdf5WritePlan,
1548 options: WriteOptions,
1549) -> Result<Vec<PreparedDataset<'_>>> {
1550 plan.validate()?;
1551 let mut prepared = Vec::with_capacity(plan.datasets.len());
1552 for dataset in &plan.datasets {
1553 let element_size = datatype_element_size(&dataset.datatype)?;
1554 let filters = validate_writer_filters(dataset, element_size)?;
1555 if let Some(datatype_order) = datatype_numeric_order(&dataset.datatype) {
1556 if datatype_order != options.byte_order {
1557 return Err(Error::InvalidDefinition(format!(
1558 "dataset '{}' datatype byte order {:?} does not match writer byte order {:?}",
1559 dataset.name, datatype_order, options.byte_order
1560 )));
1561 }
1562 }
1563 let expected = expected_data_len(&dataset.shape, element_size)?;
1564 let (storage_data, chunk_index) = if let Some(values) = &dataset.vlen_string_values {
1565 if dataset.fill_value.is_some() {
1566 return Err(Error::InvalidDefinition(format!(
1567 "dataset '{}' cannot combine variable-length strings with a fill value",
1568 dataset.name
1569 )));
1570 }
1571 if matches!(dataset.layout, PlannedLayout::Compact) {
1572 return Err(Error::UnsupportedFeature(format!(
1573 "compact variable-length string HDF5 datasets are not emitted yet: '{}'",
1574 dataset.name
1575 )));
1576 }
1577 let refs = vlen_string_heap_references(&dataset.name, values, None)?;
1578 let raw_data = encode_vlen_heap_references(&refs, 0);
1579 if raw_data.len() != expected {
1580 return Err(Error::InvalidDefinition(format!(
1581 "dataset '{}' expects {expected} vlen reference bytes, got {}",
1582 dataset.name,
1583 raw_data.len()
1584 )));
1585 }
1586 prepare_raw_dataset_storage(
1587 &raw_data,
1588 &dataset.shape,
1589 &dataset.layout,
1590 &filters,
1591 element_size,
1592 dataset.max_shape.as_deref(),
1593 )?
1594 } else if let Some(values) = &dataset.vlen_sequence_values {
1595 if dataset.fill_value.is_some() {
1596 return Err(Error::InvalidDefinition(format!(
1597 "dataset '{}' cannot combine variable-length sequences with a fill value",
1598 dataset.name
1599 )));
1600 }
1601 if matches!(dataset.layout, PlannedLayout::Compact) {
1602 return Err(Error::UnsupportedFeature(format!(
1603 "compact variable-length sequence HDF5 datasets are not emitted yet: '{}'",
1604 dataset.name
1605 )));
1606 }
1607 let Datatype::VarLen {
1608 base,
1609 kind: VarLenKind::Sequence,
1610 ..
1611 } = &dataset.datatype
1612 else {
1613 return Err(Error::InvalidDefinition(format!(
1614 "dataset '{}' vlen sequence values require a sequence datatype",
1615 dataset.name
1616 )));
1617 };
1618 validate_vlen_sequence_base(base)?;
1619 let base_size = datatype_element_size(base)?;
1620 let refs = vlen_sequence_heap_references(&dataset.name, values, base_size, None)?;
1621 let raw_data = encode_vlen_heap_references(&refs, 0);
1622 if raw_data.len() != expected {
1623 return Err(Error::InvalidDefinition(format!(
1624 "dataset '{}' expects {expected} vlen reference bytes, got {}",
1625 dataset.name,
1626 raw_data.len()
1627 )));
1628 }
1629 prepare_raw_dataset_storage(
1630 &raw_data,
1631 &dataset.shape,
1632 &dataset.layout,
1633 &filters,
1634 element_size,
1635 dataset.max_shape.as_deref(),
1636 )?
1637 } else if let Some(raw_data) = dataset.raw_data.as_deref() {
1638 if raw_data.len() != expected {
1639 return Err(Error::InvalidDefinition(format!(
1640 "dataset '{}' expects {expected} data bytes, got {}",
1641 dataset.name,
1642 raw_data.len()
1643 )));
1644 }
1645 prepare_raw_dataset_storage(
1646 raw_data,
1647 &dataset.shape,
1648 &dataset.layout,
1649 &filters,
1650 element_size,
1651 dataset.max_shape.as_deref(),
1652 )?
1653 } else if dataset.fill_value.is_some() {
1654 if matches!(dataset.layout, PlannedLayout::Compact) {
1655 return Err(Error::UnsupportedFeature(format!(
1656 "compact HDF5 datasets are not emitted yet: '{}'",
1657 dataset.name
1658 )));
1659 }
1660 (Vec::new(), None)
1661 } else {
1662 return Err(Error::InvalidDefinition(format!(
1663 "dataset '{}' has no raw data for binary emission",
1664 dataset.name
1665 )));
1666 };
1667 let data_size = u64::try_from(storage_data.len()).map_err(|_| {
1668 Error::InvalidDefinition(format!(
1669 "dataset '{}' storage byte length exceeds u64 capacity",
1670 dataset.name
1671 ))
1672 })?;
1673
1674 prepared.push(PreparedDataset {
1675 dataset,
1676 filters,
1677 raw_data: storage_data,
1678 data_size,
1679 chunk_index,
1680 });
1681 }
1682 Ok(prepared)
1683}
1684
1685fn prepare_raw_dataset_storage(
1686 raw_data: &[u8],
1687 shape: &[u64],
1688 layout: &PlannedLayout,
1689 filters: &[FilterDescription],
1690 element_size: usize,
1691 max_shape: Option<&[u64]>,
1692) -> Result<(Vec<u8>, Option<PreparedChunkIndex>)> {
1693 let unlimited = has_unlimited_max_shape(max_shape);
1694 match layout {
1695 PlannedLayout::Contiguous | PlannedLayout::Compact => Ok((raw_data.to_vec(), None)),
1696 PlannedLayout::Chunked { chunk_shape } => {
1697 if filters.is_empty() {
1698 let storage = chunked_storage_data(raw_data, shape, chunk_shape, element_size)?;
1699 let index = if unlimited {
1702 unfiltered_btree_chunk_index(shape, chunk_shape, element_size)?
1703 } else {
1704 None
1705 };
1706 Ok((storage, index))
1707 } else if chunk_count(shape, chunk_shape)? <= 1 && !unlimited {
1708 let storage_data =
1709 chunked_storage_data(raw_data, shape, chunk_shape, element_size)?;
1710 if storage_data.is_empty() {
1711 Ok((storage_data, None))
1712 } else {
1713 Ok((
1714 apply_write_filters(&storage_data, filters, element_size)?,
1715 None,
1716 ))
1717 }
1718 } else {
1719 let kind = if unlimited {
1720 ChunkIndexKind::BtreeV2
1721 } else {
1722 ChunkIndexKind::FixedArray
1723 };
1724 filtered_chunk_storage_data(
1725 raw_data,
1726 shape,
1727 chunk_shape,
1728 element_size,
1729 filters,
1730 kind,
1731 )
1732 }
1733 }
1734 }
1735}
1736
1737fn stabilize_vlen_dataset_storage(
1738 prepared: &mut [PreparedDataset<'_>],
1739 planned_attributes: &PlannedAttributes,
1740 data_start_address: u64,
1741 heap: &[u8],
1742) -> Result<DataHeapLayout> {
1743 let mut layout = compute_data_heap_layout(prepared, data_start_address, heap)?;
1744 for _ in 0..8 {
1745 let mut address_sensitive_size_changed = false;
1746 for (prepared_dataset, refs) in prepared
1747 .iter_mut()
1748 .zip(&planned_attributes.dataset_vlen_refs)
1749 {
1750 let Some(refs) = refs else {
1751 continue;
1752 };
1753 let old_data_size = prepared_dataset.data_size;
1754 let old_chunk_count = prepared_dataset
1755 .chunk_index
1756 .as_ref()
1757 .map(|chunk_index| chunk_index.chunks.len());
1758 let (raw_data, chunk_index) = dataset_vlen_storage_data(
1759 prepared_dataset.dataset,
1760 refs,
1761 layout.heap_address,
1762 &prepared_dataset.filters,
1763 )?;
1764 let data_size = u64::try_from(raw_data.len()).map_err(|_| {
1765 Error::InvalidDefinition(format!(
1766 "dataset '{}' storage byte length exceeds u64 capacity",
1767 prepared_dataset.dataset.name
1768 ))
1769 })?;
1770 let new_chunk_count = chunk_index
1771 .as_ref()
1772 .map(|chunk_index| chunk_index.chunks.len());
1773 if old_chunk_count != new_chunk_count {
1774 return Err(Error::InvalidDefinition(format!(
1775 "dataset '{}' changed filtered chunk index cardinality during VLen address stabilization",
1776 prepared_dataset.dataset.name
1777 )));
1778 }
1779 prepared_dataset.raw_data = raw_data;
1780 prepared_dataset.data_size = data_size;
1781 prepared_dataset.chunk_index = chunk_index;
1782 if data_size != old_data_size {
1783 address_sensitive_size_changed = true;
1784 }
1785 }
1786 if !address_sensitive_size_changed {
1787 return Ok(layout);
1788 }
1789 layout = compute_data_heap_layout(prepared, data_start_address, heap)?;
1790 }
1791
1792 Err(Error::InvalidDefinition(
1793 "HDF5 VLen dataset storage did not stabilize after repeated address planning".into(),
1794 ))
1795}
1796
1797fn compute_data_heap_layout(
1798 prepared: &[PreparedDataset<'_>],
1799 data_start_address: u64,
1800 heap: &[u8],
1801) -> Result<DataHeapLayout> {
1802 let mut next_address = data_start_address;
1803 let mut data_addresses = Vec::with_capacity(prepared.len());
1804 for prepared_dataset in prepared {
1805 data_addresses.push(next_address);
1806 if !matches!(prepared_dataset.dataset.layout, PlannedLayout::Compact) {
1807 next_address = align_u64(
1808 checked_add_u64(
1809 next_address,
1810 prepared_dataset.raw_data.len() as u64,
1811 "dataset raw data end",
1812 )?,
1813 8,
1814 );
1815 }
1816 }
1817
1818 let heap_address = if heap.is_empty() { 0 } else { next_address };
1819 let eof_address = align_u64(
1820 checked_add_u64(next_address, heap.len() as u64, "global heap end")?,
1821 8,
1822 );
1823 Ok(DataHeapLayout {
1824 data_addresses,
1825 heap_address,
1826 eof_address,
1827 })
1828}
1829
1830fn plan_group_hierarchy(
1831 prepared: &[PreparedDataset<'_>],
1832 group_attributes: &[GroupAttributeBuilder],
1833) -> Result<GroupHierarchy> {
1834 let mut group_paths = std::collections::BTreeSet::new();
1835 for prepared_dataset in prepared {
1836 let mut parent = parent_path(&prepared_dataset.dataset.name);
1837 while let Some(path) = parent {
1838 group_paths.insert(path.to_string());
1839 parent = parent_path(path);
1840 }
1841 }
1842 for group_attribute in group_attributes {
1843 let mut path = Some(group_attribute.path.as_str());
1844 while let Some(group_path) = path {
1845 group_paths.insert(group_path.to_string());
1846 path = parent_path(group_path);
1847 }
1848 }
1849
1850 for prepared_dataset in prepared {
1851 if group_paths.contains(&prepared_dataset.dataset.name) {
1852 return Err(Error::InvalidDefinition(format!(
1853 "dataset '{}' conflicts with an implicit HDF5 group at the same path",
1854 prepared_dataset.dataset.name
1855 )));
1856 }
1857 }
1858
1859 let paths: Vec<_> = group_paths.into_iter().collect();
1860 let group_indices: std::collections::BTreeMap<_, _> = paths
1861 .iter()
1862 .enumerate()
1863 .map(|(index, path)| (path.as_str(), index))
1864 .collect();
1865 let mut root_names = std::collections::BTreeSet::new();
1866 let mut group_names = vec![std::collections::BTreeSet::new(); paths.len()];
1867 let mut root_links = Vec::new();
1868 let mut group_links = vec![Vec::new(); paths.len()];
1869
1870 for (group_index, path) in paths.iter().enumerate() {
1871 let link = PlannedLink {
1872 name: path_basename(path).to_string(),
1873 target: PlannedLinkTarget::Group(group_index),
1874 };
1875 if let Some(parent) = parent_path(path) {
1876 let parent_index = group_indices[parent];
1877 push_group_link(
1878 &mut group_links[parent_index],
1879 &mut group_names[parent_index],
1880 link,
1881 )?;
1882 } else {
1883 push_group_link(&mut root_links, &mut root_names, link)?;
1884 }
1885 }
1886
1887 for (dataset_index, prepared_dataset) in prepared.iter().enumerate() {
1888 let path = prepared_dataset.dataset.name.as_str();
1889 let link = PlannedLink {
1890 name: path_basename(path).to_string(),
1891 target: PlannedLinkTarget::Dataset(dataset_index),
1892 };
1893 if let Some(parent) = parent_path(path) {
1894 let parent_index = group_indices[parent];
1895 push_group_link(
1896 &mut group_links[parent_index],
1897 &mut group_names[parent_index],
1898 link,
1899 )?;
1900 } else {
1901 push_group_link(&mut root_links, &mut root_names, link)?;
1902 }
1903 }
1904
1905 Ok(GroupHierarchy {
1906 paths,
1907 root_links,
1908 group_links,
1909 })
1910}
1911
1912fn push_group_link(
1913 links: &mut Vec<PlannedLink>,
1914 names: &mut std::collections::BTreeSet<String>,
1915 link: PlannedLink,
1916) -> Result<()> {
1917 if !names.insert(link.name.clone()) {
1918 return Err(Error::InvalidDefinition(format!(
1919 "duplicate HDF5 link name '{}'",
1920 link.name
1921 )));
1922 }
1923 links.push(link);
1924 Ok(())
1925}
1926
1927fn parent_path(path: &str) -> Option<&str> {
1928 path.rsplit_once('/').map(|(parent, _)| parent)
1929}
1930
1931fn path_basename(path: &str) -> &str {
1932 path.rsplit_once('/').map_or(path, |(_, name)| name)
1933}
1934
1935fn placeholder_target_addresses(
1936 prepared: &[PreparedDataset<'_>],
1937 groups: &GroupHierarchy,
1938 group_addresses: &[u64],
1939) -> std::collections::BTreeMap<String, u64> {
1940 object_target_addresses(prepared, &vec![0; prepared.len()], groups, group_addresses)
1941}
1942
1943fn target_addresses(
1944 prepared: &[PreparedDataset<'_>],
1945 header_addresses: &[u64],
1946 groups: &GroupHierarchy,
1947 group_addresses: &[u64],
1948) -> std::collections::BTreeMap<String, u64> {
1949 object_target_addresses(prepared, header_addresses, groups, group_addresses)
1950}
1951
1952fn object_target_addresses(
1953 prepared: &[PreparedDataset<'_>],
1954 header_addresses: &[u64],
1955 groups: &GroupHierarchy,
1956 group_addresses: &[u64],
1957) -> std::collections::BTreeMap<String, u64> {
1958 let mut target_addresses: std::collections::BTreeMap<String, u64> = groups
1959 .paths
1960 .iter()
1961 .zip(group_addresses.iter().copied())
1962 .map(|(path, address)| (path.clone(), address))
1963 .collect();
1964 target_addresses.extend(
1965 prepared
1966 .iter()
1967 .zip(header_addresses.iter().copied())
1968 .map(|(prepared_dataset, address)| (prepared_dataset.dataset.name.clone(), address)),
1969 );
1970 target_addresses
1971}
1972
1973fn resolve_planned_links(
1974 links: &[PlannedLink],
1975 group_addresses: &[u64],
1976 dataset_addresses: &[u64],
1977) -> Result<Vec<(String, u64)>> {
1978 links
1979 .iter()
1980 .map(|link| {
1981 let address = match link.target {
1982 PlannedLinkTarget::Group(index) => {
1983 *group_addresses.get(index).ok_or_else(|| {
1984 Error::InvalidDefinition(format!(
1985 "missing planned address for HDF5 group '{}'",
1986 link.name
1987 ))
1988 })?
1989 }
1990 PlannedLinkTarget::Dataset(index) => {
1991 *dataset_addresses.get(index).ok_or_else(|| {
1992 Error::InvalidDefinition(format!(
1993 "missing planned address for HDF5 dataset '{}'",
1994 link.name
1995 ))
1996 })?
1997 }
1998 };
1999 Ok((link.name.clone(), address))
2000 })
2001 .collect()
2002}
2003
2004#[derive(Debug)]
2005struct FixedArrayChunkEntry {
2006 address: u64,
2007 size: u64,
2008 filter_mask: u32,
2009 scaled_offsets: Vec<u64>,
2010}
2011
2012fn fixed_array_entries(
2013 chunk_index: &PreparedChunkIndex,
2014 data_address: u64,
2015) -> Result<Vec<FixedArrayChunkEntry>> {
2016 chunk_index
2017 .chunks
2018 .iter()
2019 .map(|chunk| {
2020 Ok(FixedArrayChunkEntry {
2021 address: checked_add_u64(
2022 data_address,
2023 chunk.relative_address,
2024 "fixed array chunk address",
2025 )?,
2026 size: chunk.size,
2027 filter_mask: chunk.filter_mask,
2028 scaled_offsets: chunk.scaled_offsets.clone(),
2029 })
2030 })
2031 .collect()
2032}
2033
2034fn fixed_array_entry_count(chunk_index: &PreparedChunkIndex) -> Result<u64> {
2035 u64::try_from(chunk_index.chunks.len()).map_err(|_| {
2036 Error::InvalidDefinition("fixed array chunk entry count exceeds u64 capacity".into())
2037 })
2038}
2039
2040fn chunk_index_block_sizes(chunk_index: &PreparedChunkIndex) -> Result<(usize, usize)> {
2043 let (header, data_block) = encode_chunk_index_blocks(chunk_index, 0, 0, 0)?;
2044 Ok((header.len(), data_block.len()))
2045}
2046
2047fn encode_chunk_index_blocks(
2052 chunk_index: &PreparedChunkIndex,
2053 header_address: u64,
2054 data_block_address: u64,
2055 data_address: u64,
2056) -> Result<(Vec<u8>, Vec<u8>)> {
2057 let entries = fixed_array_entries(chunk_index, data_address)?;
2058 let count = fixed_array_entry_count(chunk_index)?;
2059 match chunk_index.kind {
2060 ChunkIndexKind::FixedArray => Ok((
2061 encode_fixed_array_chunk_index_header(
2062 count,
2063 chunk_index.chunk_size_len,
2064 data_block_address,
2065 )?,
2066 encode_fixed_array_chunk_index_data_block(
2067 header_address,
2068 &entries,
2069 chunk_index.chunk_size_len,
2070 )?,
2071 )),
2072 ChunkIndexKind::BtreeV2 => {
2073 let (record_type, record_size, node_size) = btree_v2_index_params(chunk_index)?;
2074 Ok((
2075 encode_btree_v2_header(
2076 record_type,
2077 record_size,
2078 node_size,
2079 data_block_address,
2080 count,
2081 )?,
2082 encode_btree_v2_leaf(
2083 record_type,
2084 record_size,
2085 node_size,
2086 &entries,
2087 chunk_index.chunk_size_len,
2088 )?,
2089 ))
2090 }
2091 }
2092}
2093
2094fn btree_v2_index_params(chunk_index: &PreparedChunkIndex) -> Result<(u8, u16, u32)> {
2097 let filtered = chunk_index.chunk_size_len > 0;
2098 let record_type = if filtered {
2099 BTREE_V2_RECORD_TYPE_FILTERED
2100 } else {
2101 BTREE_V2_RECORD_TYPE_UNFILTERED
2102 };
2103 let ndims = chunk_index
2104 .chunks
2105 .first()
2106 .map_or(0, |chunk| chunk.scaled_offsets.len());
2107 let record_size = btree_v2_record_size(ndims, chunk_index.chunk_size_len, filtered)?;
2108 let node_size = btree_v2_node_size(fixed_array_entry_count(chunk_index)?, record_size)?;
2109 Ok((record_type, record_size, node_size))
2110}
2111
2112fn plan_attributes(
2113 plan: &Hdf5WritePlan,
2114 groups: &GroupHierarchy,
2115 target_addresses: &std::collections::BTreeMap<String, u64>,
2116) -> Result<PlannedAttributes> {
2117 let mut heap_objects = Vec::new();
2118 let root = plan_attribute_list(plan.attributes.iter(), target_addresses, &mut heap_objects)?;
2119 let groups = groups
2120 .paths
2121 .iter()
2122 .map(|path| {
2123 plan_attribute_list(
2124 plan.group_attributes.iter().filter_map(|group_attribute| {
2125 (group_attribute.path == *path).then_some(&group_attribute.attribute)
2126 }),
2127 target_addresses,
2128 &mut heap_objects,
2129 )
2130 })
2131 .collect::<Result<Vec<_>>>()?;
2132 let datasets = plan
2133 .datasets
2134 .iter()
2135 .map(|dataset| {
2136 plan_attribute_list(
2137 dataset.attributes.iter(),
2138 target_addresses,
2139 &mut heap_objects,
2140 )
2141 })
2142 .collect::<Result<Vec<_>>>()?;
2143 let dataset_vlen_refs = plan
2144 .datasets
2145 .iter()
2146 .map(|dataset| plan_dataset_vlen_values(dataset, &mut heap_objects))
2147 .collect::<Result<Vec<_>>>()?;
2148 Ok(PlannedAttributes {
2149 root,
2150 groups,
2151 datasets,
2152 dataset_vlen_refs,
2153 heap_objects,
2154 })
2155}
2156
2157fn plan_attribute_list<'a>(
2158 attributes: impl IntoIterator<Item = &'a AttributeBuilder>,
2159 target_addresses: &std::collections::BTreeMap<String, u64>,
2160 heap_objects: &mut Vec<GlobalHeapObjectPlan>,
2161) -> Result<Vec<PlannedAttribute>> {
2162 attributes
2163 .into_iter()
2164 .map(|attribute| plan_attribute(attribute, target_addresses, heap_objects))
2165 .collect()
2166}
2167
2168fn plan_attribute(
2169 attribute: &AttributeBuilder,
2170 target_addresses: &std::collections::BTreeMap<String, u64>,
2171 heap_objects: &mut Vec<GlobalHeapObjectPlan>,
2172) -> Result<PlannedAttribute> {
2173 attribute.validate()?;
2174 if let Some(values) = &attribute.vlen_string_values {
2175 let refs = vlen_string_heap_references(&attribute.name, values, Some(heap_objects))?;
2176 Ok(PlannedAttribute {
2177 name: attribute.name.clone(),
2178 datatype: attribute.datatype.clone(),
2179 shape: attribute.shape.clone(),
2180 raw_data: PlannedAttributeRaw::GlobalHeapVLenReferences(refs),
2181 })
2182 } else if let Some(values) = &attribute.vlen_sequence_values {
2183 let Datatype::VarLen {
2184 base,
2185 kind: VarLenKind::Sequence,
2186 ..
2187 } = &attribute.datatype
2188 else {
2189 return Err(Error::InvalidDefinition(format!(
2190 "attribute '{}' vlen sequence values require a sequence datatype",
2191 attribute.name
2192 )));
2193 };
2194 validate_vlen_sequence_base(base)?;
2195 let base_size = datatype_element_size(base)?;
2196 let refs =
2197 vlen_sequence_heap_references(&attribute.name, values, base_size, Some(heap_objects))?;
2198 Ok(PlannedAttribute {
2199 name: attribute.name.clone(),
2200 datatype: attribute.datatype.clone(),
2201 shape: attribute.shape.clone(),
2202 raw_data: PlannedAttributeRaw::GlobalHeapVLenReferences(refs),
2203 })
2204 } else if let Some(target_sequences) = &attribute.vlen_object_reference_targets {
2205 let mut refs = Vec::with_capacity(target_sequences.len());
2206 for sequence in target_sequences {
2207 if sequence.is_empty() {
2208 refs.push(VLenHeapReference {
2209 sequence_len: 0,
2210 heap_index: 0,
2211 });
2212 continue;
2213 }
2214 let index = checked_heap_index(heap_objects.len() + 1)?;
2215 let mut data = Vec::with_capacity(sequence.len() * usize::from(OFFSET_SIZE));
2216 for target in sequence {
2217 let address = target_addresses.get(target).ok_or_else(|| {
2218 Error::InvalidDefinition(format!(
2219 "attribute '{}' references unknown HDF5 object '{}'",
2220 attribute.name, target
2221 ))
2222 })?;
2223 data.extend_from_slice(&address.to_le_bytes());
2224 }
2225 let sequence_len = u32::try_from(sequence.len()).map_err(|_| {
2226 Error::UnsupportedFeature(format!(
2227 "attribute '{}' vlen object-reference sequence exceeds u32 capacity",
2228 attribute.name
2229 ))
2230 })?;
2231 heap_objects.push(GlobalHeapObjectPlan {
2232 index,
2233 reference_count: 1,
2234 data,
2235 });
2236 refs.push(VLenHeapReference {
2237 sequence_len,
2238 heap_index: index,
2239 });
2240 }
2241 Ok(PlannedAttribute {
2242 name: attribute.name.clone(),
2243 datatype: attribute.datatype.clone(),
2244 shape: attribute.shape.clone(),
2245 raw_data: PlannedAttributeRaw::GlobalHeapVLenReferences(refs),
2246 })
2247 } else if let Some(entries) = &attribute.object_reference_list {
2248 let mut data = Vec::with_capacity(entries.len() * REFERENCE_LIST_ENTRY_SIZE as usize);
2251 for (target, dimension) in entries {
2252 let address = target_addresses.get(target).ok_or_else(|| {
2253 Error::InvalidDefinition(format!(
2254 "attribute '{}' references unknown HDF5 object '{}'",
2255 attribute.name, target
2256 ))
2257 })?;
2258 let start = data.len();
2259 data.extend_from_slice(&address.to_le_bytes());
2260 data.extend_from_slice(&dimension.to_le_bytes());
2261 data.resize(start + REFERENCE_LIST_ENTRY_SIZE as usize, 0);
2262 }
2263 Ok(PlannedAttribute {
2264 name: attribute.name.clone(),
2265 datatype: attribute.datatype.clone(),
2266 shape: attribute.shape.clone(),
2267 raw_data: PlannedAttributeRaw::Inline(data),
2268 })
2269 } else {
2270 Ok(PlannedAttribute {
2271 name: attribute.name.clone(),
2272 datatype: attribute.datatype.clone(),
2273 shape: attribute.shape.clone(),
2274 raw_data: PlannedAttributeRaw::Inline(attribute.raw_data.clone()),
2275 })
2276 }
2277}
2278
2279fn plan_dataset_vlen_values(
2280 dataset: &DatasetBuilder,
2281 heap_objects: &mut Vec<GlobalHeapObjectPlan>,
2282) -> Result<Option<Vec<VLenHeapReference>>> {
2283 if let Some(values) = dataset.vlen_string_values.as_ref() {
2284 return vlen_string_heap_references(&dataset.name, values, Some(heap_objects)).map(Some);
2285 }
2286 if let Some(values) = dataset.vlen_sequence_values.as_ref() {
2287 let Datatype::VarLen {
2288 base,
2289 kind: VarLenKind::Sequence,
2290 ..
2291 } = &dataset.datatype
2292 else {
2293 return Err(Error::InvalidDefinition(format!(
2294 "dataset '{}' vlen sequence values require a sequence datatype",
2295 dataset.name
2296 )));
2297 };
2298 validate_vlen_sequence_base(base)?;
2299 let base_size = datatype_element_size(base)?;
2300 return vlen_sequence_heap_references(&dataset.name, values, base_size, Some(heap_objects))
2301 .map(Some);
2302 }
2303 Ok(None)
2304}
2305
2306fn vlen_string_heap_references(
2312 context: &str,
2313 values: &[String],
2314 mut heap_objects: Option<&mut Vec<GlobalHeapObjectPlan>>,
2315) -> Result<Vec<VLenHeapReference>> {
2316 let mut refs = Vec::with_capacity(values.len());
2317 for value in values {
2318 let sequence_len = value
2320 .len()
2321 .checked_add(1)
2322 .and_then(|len| u32::try_from(len).ok())
2323 .ok_or_else(|| {
2324 Error::UnsupportedFeature(format!(
2325 "'{context}' variable-length string exceeds u32 capacity"
2326 ))
2327 })?;
2328 let heap_index = match heap_objects.as_deref_mut() {
2329 Some(heap) => {
2330 let index = checked_heap_index(heap.len() + 1)?;
2331 let mut data = Vec::with_capacity(value.len() + 1);
2332 data.extend_from_slice(value.as_bytes());
2333 data.push(0);
2334 heap.push(GlobalHeapObjectPlan {
2335 index,
2336 reference_count: 1,
2337 data,
2338 });
2339 index
2340 }
2341 None => 0,
2342 };
2343 refs.push(VLenHeapReference {
2344 sequence_len,
2345 heap_index,
2346 });
2347 }
2348 Ok(refs)
2349}
2350
2351fn vlen_sequence_heap_references(
2355 context: &str,
2356 values: &[Vec<u8>],
2357 base_size: usize,
2358 mut heap_objects: Option<&mut Vec<GlobalHeapObjectPlan>>,
2359) -> Result<Vec<VLenHeapReference>> {
2360 let mut refs = Vec::with_capacity(values.len());
2361 for value in values {
2362 if value.len() % base_size != 0 {
2363 return Err(Error::InvalidDefinition(format!(
2364 "'{context}' variable-length sequence byte length {} is not a multiple of base element size {base_size}",
2365 value.len()
2366 )));
2367 }
2368 let sequence_len = u32::try_from(value.len() / base_size).map_err(|_| {
2369 Error::UnsupportedFeature(format!(
2370 "'{context}' variable-length sequence exceeds u32 element capacity"
2371 ))
2372 })?;
2373 let heap_index = match heap_objects.as_deref_mut() {
2374 Some(heap) if sequence_len != 0 => {
2375 let index = checked_heap_index(heap.len() + 1)?;
2376 heap.push(GlobalHeapObjectPlan {
2377 index,
2378 reference_count: 1,
2379 data: value.clone(),
2380 });
2381 index
2382 }
2383 _ => 0,
2384 };
2385 refs.push(VLenHeapReference {
2386 sequence_len,
2387 heap_index,
2388 });
2389 }
2390 Ok(refs)
2391}
2392
2393fn dataset_vlen_storage_data(
2394 dataset: &DatasetBuilder,
2395 refs: &[VLenHeapReference],
2396 heap_address: u64,
2397 filters: &[FilterDescription],
2398) -> Result<(Vec<u8>, Option<PreparedChunkIndex>)> {
2399 let element_size = datatype_element_size(&dataset.datatype)?;
2400 let raw_data = encode_vlen_heap_references(refs, heap_address);
2401 if matches!(&dataset.layout, PlannedLayout::Compact) {
2402 return Err(Error::UnsupportedFeature(format!(
2403 "compact variable-length HDF5 datasets are not emitted yet: '{}'",
2404 dataset.name
2405 )));
2406 }
2407 prepare_raw_dataset_storage(
2408 &raw_data,
2409 &dataset.shape,
2410 &dataset.layout,
2411 filters,
2412 element_size,
2413 dataset.max_shape.as_deref(),
2414 )
2415}
2416
2417fn checked_heap_index(index: usize) -> Result<u16> {
2418 u16::try_from(index).map_err(|_| {
2419 Error::UnsupportedFeature("global heap object count exceeds u16 capacity".into())
2420 })
2421}
2422
2423const GLOBAL_HEAP_MIN_COLLECTION_SIZE: u64 = 4096;
2425
2426fn encode_global_heap_collection(objects: &[GlobalHeapObjectPlan]) -> Result<Vec<u8>> {
2427 let mut body = Vec::new();
2428 for object in objects {
2429 body.extend_from_slice(&object.index.to_le_bytes());
2430 body.extend_from_slice(&object.reference_count.to_le_bytes());
2431 body.extend_from_slice(&[0; 4]);
2432 body.extend_from_slice(&(object.data.len() as u64).to_le_bytes());
2433 body.extend_from_slice(&object.data);
2434 let padded_len = align_u64(object.data.len() as u64, 8) as usize;
2435 body.resize(body.len() + (padded_len - object.data.len()), 0);
2436 }
2437
2438 let used = checked_add_u64(16, body.len() as u64, "global heap size")?;
2442 let collection_size = align_u64(checked_add_u64(used, 16, "global heap free object end")?, 8)
2443 .max(GLOBAL_HEAP_MIN_COLLECTION_SIZE);
2444 let free_space = collection_size - used;
2445 body.extend_from_slice(&0u16.to_le_bytes());
2446 body.extend_from_slice(&0u16.to_le_bytes());
2447 body.extend_from_slice(&[0; 4]);
2448 body.extend_from_slice(&free_space.to_le_bytes());
2449
2450 let mut bytes = Vec::with_capacity(checked_usize(collection_size, "global heap size")?);
2451 bytes.extend_from_slice(b"GCOL");
2452 bytes.push(1);
2453 bytes.extend_from_slice(&[0, 0, 0]);
2454 bytes.extend_from_slice(&collection_size.to_le_bytes());
2455 bytes.extend_from_slice(&body);
2456 bytes.resize(checked_usize(collection_size, "global heap size")?, 0);
2457 Ok(bytes)
2458}
2459
2460fn encode_superblock_v2(root_address: u64, eof_address: u64) -> Vec<u8> {
2461 let mut bytes = Vec::with_capacity(SUPERBLOCK_V2_SIZE);
2462 bytes.extend_from_slice(&HDF5_MAGIC);
2463 bytes.push(2);
2464 bytes.push(OFFSET_SIZE);
2465 bytes.push(LENGTH_SIZE);
2466 bytes.push(0);
2467 bytes.extend_from_slice(&0u64.to_le_bytes());
2468 bytes.extend_from_slice(&UNDEFINED_ADDRESS.to_le_bytes());
2469 bytes.extend_from_slice(&eof_address.to_le_bytes());
2470 bytes.extend_from_slice(&root_address.to_le_bytes());
2471 let checksum = jenkins_lookup3(&bytes);
2472 bytes.extend_from_slice(&checksum.to_le_bytes());
2473 bytes
2474}
2475
2476fn encode_group_header_from_plan(
2477 links: &[PlannedLink],
2478 group_addresses: &[u64],
2479 dataset_addresses: &[u64],
2480 attributes: &[PlannedAttribute],
2481 heap_address: u64,
2482) -> Result<Vec<u8>> {
2483 let resolved_links = resolve_planned_links(links, group_addresses, dataset_addresses)?;
2484 encode_group_header_from_links(&resolved_links, attributes, heap_address)
2485}
2486
2487fn encode_group_header_from_links(
2488 links: &[(String, u64)],
2489 attributes: &[PlannedAttribute],
2490 heap_address: u64,
2491) -> Result<Vec<u8>> {
2492 let mut messages = vec![
2495 HeaderMessage {
2496 type_id: MSG_LINK_INFO,
2497 payload: encode_link_info_message(),
2498 },
2499 HeaderMessage {
2500 type_id: MSG_GROUP_INFO,
2501 payload: encode_group_info_message(),
2502 },
2503 ];
2504 messages.extend(encode_attribute_header_messages(attributes, heap_address)?);
2505 let link_messages: Result<Vec<_>> = links
2506 .iter()
2507 .map(|(name, address)| {
2508 Ok(HeaderMessage {
2509 type_id: MSG_LINK,
2510 payload: encode_hard_link_message(name.as_str(), *address)?,
2511 })
2512 })
2513 .collect();
2514 messages.extend(link_messages?);
2515 encode_object_header_v2(&messages)
2516}
2517
2518fn encode_link_info_message() -> Vec<u8> {
2522 let mut bytes = Vec::with_capacity(18);
2523 bytes.push(0);
2524 bytes.push(0);
2525 bytes.extend_from_slice(&UNDEFINED_ADDRESS.to_le_bytes());
2526 bytes.extend_from_slice(&UNDEFINED_ADDRESS.to_le_bytes());
2527 bytes
2528}
2529
2530fn encode_group_info_message() -> Vec<u8> {
2532 vec![0, 0]
2533}
2534
2535fn encode_dataset_header(
2536 dataset: &PreparedDataset<'_>,
2537 attributes: &[PlannedAttribute],
2538 data_address: u64,
2539 heap_address: u64,
2540) -> Result<Vec<u8>> {
2541 let mut messages = Vec::new();
2542 messages.push(HeaderMessage {
2543 type_id: MSG_DATASPACE,
2544 payload: encode_dataspace_message(
2545 &dataset.dataset.shape,
2546 dataset.dataset.max_shape.as_deref(),
2547 )?,
2548 });
2549 messages.push(HeaderMessage {
2550 type_id: MSG_DATATYPE,
2551 payload: encode_datatype_message(&dataset.dataset.datatype)?,
2552 });
2553 let alloc_time = space_allocation_time(dataset);
2554 messages.push(HeaderMessage {
2555 type_id: MSG_FILL_VALUE,
2556 payload: match &dataset.dataset.fill_value {
2557 Some(fill_value) => encode_fill_value_message(fill_value, alloc_time)?,
2558 None => encode_default_fill_value_message(alloc_time),
2559 },
2560 });
2561 if !dataset.filters.is_empty() {
2562 messages.push(HeaderMessage {
2563 type_id: MSG_FILTER_PIPELINE,
2564 payload: encode_filter_pipeline_message(&dataset.filters)?,
2565 });
2566 }
2567 messages.push(HeaderMessage {
2568 type_id: MSG_DATA_LAYOUT,
2569 payload: encode_data_layout_message(dataset, data_address)?,
2570 });
2571 messages.extend(encode_attribute_header_messages(attributes, heap_address)?);
2572 encode_object_header_v2(&messages)
2573}
2574
2575fn encode_data_layout_message(dataset: &PreparedDataset<'_>, data_address: u64) -> Result<Vec<u8>> {
2576 let storage_address = if dataset.data_size == 0 {
2577 UNDEFINED_ADDRESS
2578 } else {
2579 data_address
2580 };
2581 match &dataset.dataset.layout {
2582 PlannedLayout::Contiguous => Ok(encode_contiguous_layout_message(
2583 storage_address,
2584 dataset.data_size,
2585 )),
2586 PlannedLayout::Chunked { chunk_shape } => {
2587 let element_size = chunk_element_size(&dataset.dataset.datatype)?;
2588 if let Some(chunk_index) = &dataset.chunk_index {
2589 match chunk_index.kind {
2590 ChunkIndexKind::FixedArray => {
2591 let page_bits =
2592 fixed_array_page_bits(fixed_array_entry_count(chunk_index)?)?;
2593 encode_fixed_array_chunked_layout_message(
2594 storage_address,
2595 chunk_shape,
2596 element_size,
2597 page_bits,
2598 )
2599 }
2600 ChunkIndexKind::BtreeV2 => {
2601 let (_, _, node_size) = btree_v2_index_params(chunk_index)?;
2602 encode_btree_v2_chunked_layout_message(
2603 storage_address,
2604 chunk_shape,
2605 element_size,
2606 node_size,
2607 )
2608 }
2609 }
2610 } else if dataset.filters.is_empty() {
2611 encode_implicit_chunked_layout_message(storage_address, chunk_shape, element_size)
2613 } else {
2614 encode_single_chunk_layout_message(
2616 storage_address,
2617 chunk_shape,
2618 dataset.data_size,
2619 element_size,
2620 )
2621 }
2622 }
2623 PlannedLayout::Compact => encode_compact_layout_message(&dataset.raw_data),
2624 }
2625}
2626
2627fn chunk_element_size(datatype: &Datatype) -> Result<u32> {
2630 let size = datatype_element_size(datatype)?;
2631 u32::try_from(size).map_err(|_| {
2632 Error::UnsupportedFeature("chunked element size exceeds layout message capacity".into())
2633 })
2634}
2635
2636fn fixed_array_page_bits(num_entries: u64) -> Result<u8> {
2640 let mut bits = 10u8;
2641 while (1u64 << bits) < num_entries {
2642 bits += 1;
2643 if bits > 25 {
2644 return Err(Error::UnsupportedFeature(
2645 "fixed array chunk count exceeds the single-page data block limit".into(),
2646 ));
2647 }
2648 }
2649 Ok(bits)
2650}
2651
2652fn encode_attribute_header_messages(
2653 attributes: &[PlannedAttribute],
2654 heap_address: u64,
2655) -> Result<Vec<HeaderMessage>> {
2656 attributes
2657 .iter()
2658 .map(|attribute| {
2659 Ok(HeaderMessage {
2660 type_id: MSG_ATTRIBUTE,
2661 payload: encode_attribute_message(attribute, heap_address)?,
2662 })
2663 })
2664 .collect()
2665}
2666
2667fn encode_object_header_v2(messages: &[HeaderMessage]) -> Result<Vec<u8>> {
2668 let mut message_bytes = Vec::new();
2669 for message in messages {
2670 if message.payload.len() > u16::MAX as usize {
2671 return Err(Error::UnsupportedFeature(
2672 "object header continuation messages are not emitted yet".into(),
2673 ));
2674 }
2675 message_bytes.push(message.type_id);
2676 message_bytes.extend_from_slice(&(message.payload.len() as u16).to_le_bytes());
2677 message_bytes.push(0);
2678 message_bytes.extend_from_slice(&message.payload);
2679 }
2680
2681 let (size_flag, size_width) = size_width_for(message_bytes.len() as u64)?;
2682 let mut bytes = Vec::new();
2683 bytes.extend_from_slice(b"OHDR");
2684 bytes.push(2);
2685 bytes.push(size_flag);
2686 write_uvar(message_bytes.len() as u64, size_width, &mut bytes);
2687 bytes.extend_from_slice(&message_bytes);
2688 let checksum = jenkins_lookup3(&bytes);
2689 bytes.extend_from_slice(&checksum.to_le_bytes());
2690 Ok(bytes)
2691}
2692
2693fn encode_hard_link_message(name: &str, address: u64) -> Result<Vec<u8>> {
2694 let name_bytes = name.as_bytes();
2695 let (name_len_flag, name_len_width) = size_width_for(name_bytes.len() as u64)?;
2696 let utf8_flag = if name.is_ascii() { 0 } else { 0x10 };
2697 let mut bytes = Vec::new();
2698 bytes.push(1);
2699 bytes.push(name_len_flag | utf8_flag);
2700 if utf8_flag != 0 {
2701 bytes.push(1);
2702 }
2703 write_uvar(name_bytes.len() as u64, name_len_width, &mut bytes);
2704 bytes.extend_from_slice(name_bytes);
2705 bytes.extend_from_slice(&address.to_le_bytes());
2706 Ok(bytes)
2707}
2708
2709fn encode_attribute_message(attribute: &PlannedAttribute, heap_address: u64) -> Result<Vec<u8>> {
2710 let name_bytes = attribute.name.as_bytes();
2711 let datatype = encode_datatype_message(&attribute.datatype)?;
2712 let dataspace = encode_dataspace_message(&attribute.shape, None)?;
2713 let raw_data = attribute.raw_data(heap_address);
2714 if name_bytes.len() + 1 > u16::MAX as usize
2715 || datatype.len() > u16::MAX as usize
2716 || dataspace.len() > u16::MAX as usize
2717 {
2718 return Err(Error::UnsupportedFeature(format!(
2719 "attribute '{}' is too large for compact attribute message emission",
2720 attribute.name
2721 )));
2722 }
2723
2724 let mut bytes = Vec::new();
2725 bytes.push(3);
2726 bytes.push(0);
2727 bytes.extend_from_slice(&((name_bytes.len() + 1) as u16).to_le_bytes());
2728 bytes.extend_from_slice(&(datatype.len() as u16).to_le_bytes());
2729 bytes.extend_from_slice(&(dataspace.len() as u16).to_le_bytes());
2730 bytes.push(if attribute.name.is_ascii() { 0 } else { 1 });
2731 bytes.extend_from_slice(name_bytes);
2732 bytes.push(0);
2733 bytes.extend_from_slice(&datatype);
2734 bytes.extend_from_slice(&dataspace);
2735 bytes.extend_from_slice(&raw_data);
2736 Ok(bytes)
2737}
2738
2739fn encode_dataspace_message(shape: &[u64], max_shape: Option<&[u64]>) -> Result<Vec<u8>> {
2740 if shape.len() > u8::MAX as usize {
2741 return Err(Error::InvalidDefinition(
2742 "dataset rank exceeds HDF5 rank field capacity".into(),
2743 ));
2744 }
2745 let mut bytes = Vec::new();
2746 bytes.push(2);
2747 bytes.push(shape.len() as u8);
2748 bytes.push(if max_shape.is_some() { 0x01 } else { 0x00 });
2749 bytes.push(if shape.is_empty() { 0 } else { 1 });
2750 for &dim in shape {
2751 bytes.extend_from_slice(&dim.to_le_bytes());
2752 }
2753 if let Some(max_shape) = max_shape {
2754 for &dim in max_shape {
2755 bytes.extend_from_slice(&dim.to_le_bytes());
2756 }
2757 }
2758 Ok(bytes)
2759}
2760
2761fn encode_datatype_message(datatype: &Datatype) -> Result<Vec<u8>> {
2762 match datatype {
2763 Datatype::FixedPoint {
2764 size,
2765 signed,
2766 byte_order,
2767 } => {
2768 let mut flags = byte_order_flag(*byte_order);
2769 if *signed {
2770 flags |= 0x08;
2771 }
2772 let mut bytes = Vec::new();
2773 bytes.extend_from_slice(&class_word(0, 1, flags).to_le_bytes());
2774 bytes.extend_from_slice(&u32::from(*size).to_le_bytes());
2775 bytes.extend_from_slice(&0u16.to_le_bytes());
2776 bytes.extend_from_slice(&(u16::from(*size) * 8).to_le_bytes());
2777 Ok(bytes)
2778 }
2779 Datatype::FloatingPoint { size, byte_order } => {
2780 let (exp_location, exp_size, mantissa_size, exp_bias, sign_location) = match *size {
2781 4 => (23u8, 8u8, 23u8, 127u32, 31u32),
2782 8 => (52u8, 11u8, 52u8, 1023u32, 63u32),
2783 other => {
2784 return Err(Error::UnsupportedFeature(format!(
2785 "unsupported floating-point byte width {other}"
2786 )))
2787 }
2788 };
2789 let flags = byte_order_flag(*byte_order) | 0x20 | (sign_location << 8);
2793 let mut bytes = Vec::new();
2794 bytes.extend_from_slice(&class_word(1, 1, flags).to_le_bytes());
2795 bytes.extend_from_slice(&u32::from(*size).to_le_bytes());
2796 bytes.extend_from_slice(&0u16.to_le_bytes());
2797 bytes.extend_from_slice(&(u16::from(*size) * 8).to_le_bytes());
2798 bytes.push(exp_location);
2799 bytes.push(exp_size);
2800 bytes.push(0);
2801 bytes.push(mantissa_size);
2802 bytes.extend_from_slice(&exp_bias.to_le_bytes());
2803 Ok(bytes)
2804 }
2805 Datatype::String {
2806 size,
2807 encoding,
2808 padding,
2809 } => match size {
2810 StringSize::Fixed(size) => {
2811 let flags = string_padding_bits(*padding) | (string_encoding_bits(*encoding) << 4);
2812 let mut bytes = Vec::new();
2813 bytes.extend_from_slice(&class_word(3, 1, flags).to_le_bytes());
2814 bytes.extend_from_slice(&size.to_le_bytes());
2815 Ok(bytes)
2816 }
2817 StringSize::Variable => encode_datatype_message(&Datatype::VarLen {
2821 base: Box::new(Datatype::FixedPoint {
2822 size: 1,
2823 signed: false,
2824 byte_order: ByteOrder::LittleEndian,
2825 }),
2826 kind: VarLenKind::String,
2827 encoding: *encoding,
2828 padding: *padding,
2829 }),
2830 },
2831 Datatype::Reference { ref_type, size } => {
2832 let flags = match ref_type {
2833 hdf5_core::ReferenceType::Object => 0,
2834 hdf5_core::ReferenceType::DatasetRegion => 1,
2835 };
2836 let mut bytes = Vec::new();
2837 bytes.extend_from_slice(&class_word(7, 1, flags).to_le_bytes());
2838 bytes.extend_from_slice(&u32::from(*size).to_le_bytes());
2839 Ok(bytes)
2840 }
2841 Datatype::VarLen {
2842 base,
2843 kind,
2844 encoding,
2845 padding,
2846 } => {
2847 let kind_bits = match kind {
2848 VarLenKind::Sequence => 0,
2849 VarLenKind::String => 1,
2850 VarLenKind::Unknown(value) => u32::from(*value),
2851 };
2852 let flags = kind_bits
2853 | (string_padding_bits(*padding) << 4)
2854 | (string_encoding_bits(*encoding) << 8);
2855 let mut bytes = Vec::new();
2856 bytes.extend_from_slice(&class_word(9, 1, flags).to_le_bytes());
2857 bytes.extend_from_slice(&16u32.to_le_bytes());
2858 bytes.extend_from_slice(&encode_datatype_message(base)?);
2859 Ok(bytes)
2860 }
2861 Datatype::Bitfield { size, byte_order } => {
2862 let mut bytes = Vec::new();
2863 bytes.extend_from_slice(&class_word(4, 1, byte_order_flag(*byte_order)).to_le_bytes());
2864 bytes.extend_from_slice(&u32::from(*size).to_le_bytes());
2865 bytes.extend_from_slice(&0u16.to_le_bytes());
2866 bytes.extend_from_slice(&(u16::from(*size) * 8).to_le_bytes());
2867 Ok(bytes)
2868 }
2869 Datatype::Opaque { size, tag } => {
2870 let tag_len = if tag.is_empty() {
2871 0
2872 } else {
2873 checked_add_usize(tag.len(), 1, "opaque datatype tag length")?
2874 };
2875 if tag.as_bytes().contains(&0) {
2876 return Err(Error::InvalidDefinition(
2877 "opaque datatype tag cannot contain NUL bytes".into(),
2878 ));
2879 }
2880 let flags = u32::try_from(tag_len).map_err(|_| {
2881 Error::UnsupportedFeature("opaque datatype tag exceeds u32 capacity".into())
2882 })?;
2883 if flags > 0xff {
2884 return Err(Error::UnsupportedFeature(
2885 "opaque datatype tag exceeds HDF5 class flag capacity".into(),
2886 ));
2887 }
2888 let mut bytes = Vec::new();
2889 bytes.extend_from_slice(&class_word(5, 1, flags).to_le_bytes());
2890 bytes.extend_from_slice(&size.to_le_bytes());
2891 if tag_len > 0 {
2892 bytes.extend_from_slice(tag.as_bytes());
2893 bytes.push(0);
2894 align_vec(&mut bytes, 8);
2895 }
2896 Ok(bytes)
2897 }
2898 Datatype::Compound { size, fields } => {
2899 let n_fields = u32::try_from(fields.len()).map_err(|_| {
2900 Error::UnsupportedFeature(
2901 "compound datatype member count exceeds u32 capacity".into(),
2902 )
2903 })?;
2904 if n_fields > 0xffff {
2905 return Err(Error::UnsupportedFeature(
2906 "compound datatype member count exceeds HDF5 class flag capacity".into(),
2907 ));
2908 }
2909 let offset_size = compound_member_offset_size(*size);
2910 let mut bytes = Vec::new();
2911 bytes.extend_from_slice(&class_word(6, 3, n_fields).to_le_bytes());
2912 bytes.extend_from_slice(&size.to_le_bytes());
2913 let mut names = std::collections::BTreeSet::new();
2914 for field in fields {
2915 encode_datatype_name(&field.name, "compound field")?;
2916 if !names.insert(field.name.as_str()) {
2917 return Err(Error::InvalidDefinition(format!(
2918 "duplicate compound field '{}'",
2919 field.name
2920 )));
2921 }
2922 let field_size =
2923 u32::try_from(datatype_element_size(&field.datatype)?).map_err(|_| {
2924 Error::UnsupportedFeature(format!(
2925 "compound field '{}' size exceeds u32 capacity",
2926 field.name
2927 ))
2928 })?;
2929 let field_end = field.byte_offset.checked_add(field_size).ok_or_else(|| {
2930 Error::InvalidDefinition(format!(
2931 "compound field '{}' offset overflows datatype size",
2932 field.name
2933 ))
2934 })?;
2935 if field_end > *size {
2936 return Err(Error::InvalidDefinition(format!(
2937 "compound field '{}' byte range {}..{} is outside datatype size {size}",
2938 field.name, field.byte_offset, field_end
2939 )));
2940 }
2941 bytes.extend_from_slice(field.name.as_bytes());
2942 bytes.push(0);
2943 write_uvar(u64::from(field.byte_offset), offset_size, &mut bytes);
2944 bytes.extend_from_slice(&encode_datatype_message(&field.datatype)?);
2945 }
2946 Ok(bytes)
2947 }
2948 Datatype::Enum { base, members } => {
2949 let n_members = u32::try_from(members.len()).map_err(|_| {
2950 Error::UnsupportedFeature("enum datatype member count exceeds u32 capacity".into())
2951 })?;
2952 if n_members > 0xffff {
2953 return Err(Error::UnsupportedFeature(
2954 "enum datatype member count exceeds HDF5 class flag capacity".into(),
2955 ));
2956 }
2957 if !matches!(base.as_ref(), Datatype::FixedPoint { .. }) {
2958 return Err(Error::InvalidDefinition(
2959 "enum datatype base must be fixed-point integer".into(),
2960 ));
2961 }
2962 let value_size = datatype_element_size(base)?;
2963 let value_size_u32 = u32::try_from(value_size).map_err(|_| {
2964 Error::UnsupportedFeature("enum datatype value size exceeds u32 capacity".into())
2965 })?;
2966 let mut bytes = Vec::new();
2967 bytes.extend_from_slice(&class_word(8, 3, n_members).to_le_bytes());
2968 bytes.extend_from_slice(&value_size_u32.to_le_bytes());
2969 bytes.extend_from_slice(&encode_datatype_message(base)?);
2970 let mut names = std::collections::BTreeSet::new();
2971 for member in members {
2972 encode_datatype_name(&member.name, "enum member")?;
2973 if !names.insert(member.name.as_str()) {
2974 return Err(Error::InvalidDefinition(format!(
2975 "duplicate enum member '{}'",
2976 member.name
2977 )));
2978 }
2979 bytes.extend_from_slice(member.name.as_bytes());
2980 bytes.push(0);
2981 }
2982 for member in members {
2983 if member.value.len() != value_size {
2984 return Err(Error::InvalidDefinition(format!(
2985 "enum member '{}' value byte length must be {value_size}, got {}",
2986 member.name,
2987 member.value.len()
2988 )));
2989 }
2990 bytes.extend_from_slice(&member.value);
2991 }
2992 Ok(bytes)
2993 }
2994 Datatype::Array { base, dims } => {
2995 let rank = u8::try_from(dims.len()).map_err(|_| {
2996 Error::UnsupportedFeature("array datatype rank exceeds HDF5 capacity".into())
2997 })?;
2998 let element_size = datatype_element_size(datatype)?;
2999 let element_size_u32 = u32::try_from(element_size).map_err(|_| {
3000 Error::UnsupportedFeature("array datatype element size exceeds u32 capacity".into())
3001 })?;
3002 let mut bytes = Vec::new();
3003 bytes.extend_from_slice(&class_word(10, 3, 0).to_le_bytes());
3004 bytes.extend_from_slice(&element_size_u32.to_le_bytes());
3005 bytes.push(rank);
3006 for &dim in dims {
3007 let dim = u32::try_from(dim).map_err(|_| {
3008 Error::UnsupportedFeature(
3009 "array datatype dimension exceeds HDF5 u32 capacity".into(),
3010 )
3011 })?;
3012 bytes.extend_from_slice(&dim.to_le_bytes());
3013 }
3014 bytes.extend_from_slice(&encode_datatype_message(base)?);
3015 Ok(bytes)
3016 }
3017 }
3018}
3019
3020fn encode_datatype_name(name: &str, context: &str) -> Result<()> {
3021 if name.is_empty() {
3022 return Err(Error::InvalidDefinition(format!(
3023 "{context} name must not be empty"
3024 )));
3025 }
3026 if name.as_bytes().contains(&0) {
3027 return Err(Error::InvalidDefinition(format!(
3028 "{context} name cannot contain NUL bytes"
3029 )));
3030 }
3031 Ok(())
3032}
3033
3034fn compound_member_offset_size(size: u32) -> usize {
3035 match size {
3036 0..=0xff => 1,
3037 0x100..=0xffff => 2,
3038 0x1_0000..=0xff_ffff => 3,
3039 _ => 4,
3040 }
3041}
3042
3043fn align_vec(bytes: &mut Vec<u8>, align: usize) {
3044 let padding = (align - (bytes.len() % align)) % align;
3045 bytes.resize(bytes.len() + padding, 0);
3046}
3047
3048fn encode_contiguous_layout_message(address: u64, size: u64) -> Vec<u8> {
3049 let mut bytes = Vec::with_capacity(18);
3050 bytes.push(3);
3051 bytes.push(1);
3052 bytes.extend_from_slice(&address.to_le_bytes());
3053 bytes.extend_from_slice(&size.to_le_bytes());
3054 bytes
3055}
3056
3057fn encode_compact_layout_message(data: &[u8]) -> Result<Vec<u8>> {
3058 let size = u16::try_from(data.len()).map_err(|_| {
3059 Error::UnsupportedFeature(
3060 "compact HDF5 dataset data exceeds u16 byte length capacity".into(),
3061 )
3062 })?;
3063 let mut bytes = Vec::with_capacity(4 + data.len());
3064 bytes.push(3);
3065 bytes.push(0);
3066 bytes.extend_from_slice(&size.to_le_bytes());
3067 bytes.extend_from_slice(data);
3068 Ok(bytes)
3069}
3070
3071const ALLOC_TIME_EARLY: u8 = 1;
3073const ALLOC_TIME_LATE: u8 = 2;
3074const ALLOC_TIME_INCREMENTAL: u8 = 3;
3075const FILL_WRITE_TIME_IFSET: u8 = 2 << 2;
3077const FILL_VALUE_DEFINED: u8 = 0x20;
3079
3080fn space_allocation_time(dataset: &PreparedDataset<'_>) -> u8 {
3084 match &dataset.dataset.layout {
3085 PlannedLayout::Compact => ALLOC_TIME_EARLY,
3086 PlannedLayout::Contiguous => ALLOC_TIME_LATE,
3087 PlannedLayout::Chunked { .. } => {
3088 if dataset.filters.is_empty() {
3089 ALLOC_TIME_EARLY
3090 } else {
3091 ALLOC_TIME_INCREMENTAL
3092 }
3093 }
3094 }
3095}
3096
3097fn encode_fill_value_message(fill_value: &[u8], alloc_time: u8) -> Result<Vec<u8>> {
3098 let size = u32::try_from(fill_value.len()).map_err(|_| {
3099 Error::UnsupportedFeature("HDF5 fill value exceeds u32 byte length capacity".into())
3100 })?;
3101 let mut bytes = Vec::with_capacity(6 + fill_value.len());
3102 bytes.push(3);
3103 bytes.push(alloc_time | FILL_WRITE_TIME_IFSET | FILL_VALUE_DEFINED);
3104 bytes.extend_from_slice(&size.to_le_bytes());
3105 bytes.extend_from_slice(fill_value);
3106 Ok(bytes)
3107}
3108
3109fn encode_default_fill_value_message(alloc_time: u8) -> Vec<u8> {
3112 vec![3, alloc_time | FILL_WRITE_TIME_IFSET]
3113}
3114
3115fn encode_filter_pipeline_message(filters: &[FilterDescription]) -> Result<Vec<u8>> {
3116 let filter_count = u8::try_from(filters.len()).map_err(|_| {
3117 Error::UnsupportedFeature("HDF5 filter pipeline exceeds u8 filter count capacity".into())
3118 })?;
3119 let mut bytes = Vec::new();
3120 bytes.push(2);
3121 bytes.push(filter_count);
3122 for filter in filters {
3123 bytes.extend_from_slice(&filter.id.to_le_bytes());
3124 if filter.id >= 256 {
3125 let name_len = filter.name.as_ref().map_or(0usize, |name| name.len() + 1);
3126 let name_len = u16::try_from(name_len).map_err(|_| {
3127 Error::UnsupportedFeature(format!(
3128 "HDF5 filter {} name exceeds u16 byte length capacity",
3129 filter.id
3130 ))
3131 })?;
3132 bytes.extend_from_slice(&name_len.to_le_bytes());
3133 }
3134 bytes.extend_from_slice(&0u16.to_le_bytes());
3135 let client_count = u16::try_from(filter.client_data.len()).map_err(|_| {
3136 Error::UnsupportedFeature(format!(
3137 "HDF5 filter {} client data exceeds u16 count capacity",
3138 filter.id
3139 ))
3140 })?;
3141 bytes.extend_from_slice(&client_count.to_le_bytes());
3142 if filter.id >= 256 {
3143 if let Some(name) = &filter.name {
3144 bytes.extend_from_slice(name.as_bytes());
3145 bytes.push(0);
3146 }
3147 }
3148 for value in &filter.client_data {
3149 bytes.extend_from_slice(&value.to_le_bytes());
3150 }
3151 }
3152 Ok(bytes)
3153}
3154
3155const V4_LAYOUT_SINGLE_INDEX_WITH_FILTER: u8 = 0x02;
3158
3159fn encode_single_chunk_layout_message(
3160 address: u64,
3161 chunk_shape: &[u64],
3162 filtered_size: u64,
3163 element_size: u32,
3164) -> Result<Vec<u8>> {
3165 let mut bytes = Vec::with_capacity(22 + chunk_shape.len() * 4 + usize::from(OFFSET_SIZE));
3166 encode_v4_chunked_layout_preamble(
3167 &mut bytes,
3168 V4_LAYOUT_SINGLE_INDEX_WITH_FILTER,
3169 chunk_shape,
3170 element_size,
3171 )?;
3172 bytes.push(1);
3173 bytes.extend_from_slice(&filtered_size.to_le_bytes());
3174 bytes.extend_from_slice(&0u32.to_le_bytes());
3175 bytes.extend_from_slice(&address.to_le_bytes());
3176 Ok(bytes)
3177}
3178
3179fn encode_v4_chunked_layout_preamble(
3186 bytes: &mut Vec<u8>,
3187 flags: u8,
3188 chunk_shape: &[u64],
3189 element_size: u32,
3190) -> Result<()> {
3191 if chunk_shape.len() + 1 > u8::MAX as usize {
3192 return Err(Error::InvalidDefinition(
3193 "chunked dataset rank exceeds HDF5 rank field capacity".into(),
3194 ));
3195 }
3196 let max_dim = chunk_shape
3197 .iter()
3198 .copied()
3199 .chain(std::iter::once(u64::from(element_size)))
3200 .max()
3201 .unwrap_or(1);
3202 let dim_bytes = minimal_uint_width(max_dim);
3203 bytes.push(4);
3204 bytes.push(2);
3205 bytes.push(flags);
3206 bytes.push((chunk_shape.len() + 1) as u8);
3207 bytes.push(dim_bytes);
3208 for &dim in chunk_shape {
3209 write_uvar(dim, usize::from(dim_bytes), bytes);
3210 }
3211 write_uvar(u64::from(element_size), usize::from(dim_bytes), bytes);
3212 Ok(())
3213}
3214
3215fn minimal_uint_width(value: u64) -> u8 {
3217 let bits = 64 - value.leading_zeros();
3218 (bits.max(1)).div_ceil(8) as u8
3219}
3220
3221fn encode_implicit_chunked_layout_message(
3222 address: u64,
3223 chunk_shape: &[u64],
3224 element_size: u32,
3225) -> Result<Vec<u8>> {
3226 let mut bytes = Vec::with_capacity(10 + chunk_shape.len() * 4 + usize::from(OFFSET_SIZE));
3227 encode_v4_chunked_layout_preamble(&mut bytes, 0, chunk_shape, element_size)?;
3228 bytes.push(2);
3229 bytes.extend_from_slice(&address.to_le_bytes());
3230 Ok(bytes)
3231}
3232
3233fn encode_fixed_array_chunked_layout_message(
3234 address: u64,
3235 chunk_shape: &[u64],
3236 element_size: u32,
3237 page_bits: u8,
3238) -> Result<Vec<u8>> {
3239 let mut bytes = Vec::with_capacity(11 + chunk_shape.len() * 4 + usize::from(OFFSET_SIZE));
3240 encode_v4_chunked_layout_preamble(&mut bytes, 0, chunk_shape, element_size)?;
3241 bytes.push(3);
3242 bytes.push(page_bits);
3243 bytes.extend_from_slice(&address.to_le_bytes());
3244 Ok(bytes)
3245}
3246
3247const BTREE_V2_SPLIT_PERCENT: u8 = 100;
3250const BTREE_V2_MERGE_PERCENT: u8 = 40;
3251const BTREE_V2_LEAF_OVERHEAD: u64 = 4 + 1 + 1 + 4; const BTREE_V2_RECORD_TYPE_UNFILTERED: u8 = 10;
3253const BTREE_V2_RECORD_TYPE_FILTERED: u8 = 11;
3254
3255fn btree_v2_record_size(ndims: usize, chunk_size_len: u8, filtered: bool) -> Result<u16> {
3257 let offsets = ndims
3258 .checked_mul(8)
3259 .ok_or_else(|| Error::InvalidDefinition("chunk offset bytes overflow".into()))?;
3260 let fixed = usize::from(OFFSET_SIZE)
3261 + offsets
3262 + if filtered {
3263 usize::from(chunk_size_len) + 4
3264 } else {
3265 0
3266 };
3267 u16::try_from(fixed)
3268 .map_err(|_| Error::UnsupportedFeature("v2 B-tree chunk record exceeds u16".into()))
3269}
3270
3271fn btree_v2_node_size(num_records: u64, record_size: u16) -> Result<u32> {
3274 let records_bytes = num_records
3275 .checked_mul(u64::from(record_size))
3276 .ok_or_else(|| Error::InvalidDefinition("v2 B-tree leaf size overflow".into()))?;
3277 let size = align_u64(
3278 checked_add_u64(BTREE_V2_LEAF_OVERHEAD, records_bytes, "v2 B-tree leaf size")?,
3279 8,
3280 );
3281 u32::try_from(size)
3282 .map_err(|_| Error::UnsupportedFeature("v2 B-tree node size exceeds u32".into()))
3283}
3284
3285fn encode_btree_v2_chunked_layout_message(
3286 address: u64,
3287 chunk_shape: &[u64],
3288 element_size: u32,
3289 node_size: u32,
3290) -> Result<Vec<u8>> {
3291 let mut bytes = Vec::with_capacity(15 + chunk_shape.len() * 4 + usize::from(OFFSET_SIZE));
3292 encode_v4_chunked_layout_preamble(&mut bytes, 0, chunk_shape, element_size)?;
3293 bytes.push(5); bytes.extend_from_slice(&node_size.to_le_bytes());
3295 bytes.push(BTREE_V2_SPLIT_PERCENT);
3296 bytes.push(BTREE_V2_MERGE_PERCENT);
3297 bytes.extend_from_slice(&address.to_le_bytes());
3298 Ok(bytes)
3299}
3300
3301fn encode_btree_v2_header(
3302 record_type: u8,
3303 record_size: u16,
3304 node_size: u32,
3305 root_node_address: u64,
3306 num_records: u64,
3307) -> Result<Vec<u8>> {
3308 let mut bytes = Vec::with_capacity(4 + 1 + 1 + 4 + 2 + 2 + 1 + 1 + 8 + 2 + 8 + 4);
3309 bytes.extend_from_slice(b"BTHD");
3310 bytes.push(0); bytes.push(record_type);
3312 bytes.extend_from_slice(&node_size.to_le_bytes());
3313 bytes.extend_from_slice(&record_size.to_le_bytes());
3314 bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.push(BTREE_V2_SPLIT_PERCENT);
3316 bytes.push(BTREE_V2_MERGE_PERCENT);
3317 bytes.extend_from_slice(&root_node_address.to_le_bytes());
3318 let num_records_u16 = u16::try_from(num_records)
3319 .map_err(|_| Error::UnsupportedFeature("v2 B-tree root record count exceeds u16".into()))?;
3320 bytes.extend_from_slice(&num_records_u16.to_le_bytes());
3321 bytes.extend_from_slice(&num_records.to_le_bytes()); let checksum = jenkins_lookup3(&bytes);
3323 bytes.extend_from_slice(&checksum.to_le_bytes());
3324 Ok(bytes)
3325}
3326
3327fn encode_btree_v2_leaf(
3331 record_type: u8,
3332 record_size: u16,
3333 node_size: u32,
3334 entries: &[FixedArrayChunkEntry],
3335 chunk_size_len: u8,
3336) -> Result<Vec<u8>> {
3337 let filtered = record_type == BTREE_V2_RECORD_TYPE_FILTERED;
3338 let ndims = entries
3339 .first()
3340 .map_or(0, |entry| entry.scaled_offsets.len());
3341 let mut records = Vec::new();
3342 records.extend_from_slice(b"BTLF");
3343 records.push(0); records.push(record_type);
3345 let size_limit = if chunk_size_len >= 8 {
3346 u64::MAX
3347 } else {
3348 (1u64 << (8 * u32::from(chunk_size_len))) - 1
3349 };
3350 for entry in entries {
3351 if entry.scaled_offsets.len() != ndims {
3352 return Err(Error::InvalidDefinition(
3353 "v2 B-tree chunk records have inconsistent rank".into(),
3354 ));
3355 }
3356 records.extend_from_slice(&entry.address.to_le_bytes());
3357 if filtered {
3358 if entry.size > size_limit {
3359 return Err(Error::InvalidDefinition(format!(
3360 "filtered chunk byte length {} exceeds the {chunk_size_len}-byte record field",
3361 entry.size
3362 )));
3363 }
3364 write_uvar(entry.size, usize::from(chunk_size_len), &mut records);
3365 records.extend_from_slice(&entry.filter_mask.to_le_bytes());
3366 }
3367 for &scaled in &entry.scaled_offsets {
3368 records.extend_from_slice(&scaled.to_le_bytes());
3369 }
3370 }
3371 let _ = record_size;
3372 let checksum = jenkins_lookup3(&records);
3373 records.extend_from_slice(&checksum.to_le_bytes());
3374 let node_size = checked_usize(u64::from(node_size), "v2 B-tree node size")?;
3375 if records.len() > node_size {
3376 return Err(Error::InvalidDefinition(
3377 "v2 B-tree leaf records exceed node size".into(),
3378 ));
3379 }
3380 records.resize(node_size, 0);
3381 Ok(records)
3382}
3383
3384fn encode_fixed_array_chunk_index_header(
3385 num_entries: u64,
3386 chunk_size_len: u8,
3387 data_block_address: u64,
3388) -> Result<Vec<u8>> {
3389 let entry_size = u8::try_from(usize::from(OFFSET_SIZE) + usize::from(chunk_size_len) + 4)
3390 .map_err(|_| Error::UnsupportedFeature("fixed array entry size exceeds u8".into()))?;
3391 let mut bytes = Vec::with_capacity(4 + 1 + 1 + 1 + 1 + 8 + 8 + 4);
3392 bytes.extend_from_slice(b"FAHD");
3393 bytes.push(0);
3394 bytes.push(1);
3395 bytes.push(entry_size);
3396 bytes.push(fixed_array_page_bits(num_entries)?);
3397 bytes.extend_from_slice(&num_entries.to_le_bytes());
3398 bytes.extend_from_slice(&data_block_address.to_le_bytes());
3399 let checksum = jenkins_lookup3(&bytes);
3400 bytes.extend_from_slice(&checksum.to_le_bytes());
3401 Ok(bytes)
3402}
3403
3404fn encode_fixed_array_chunk_index_data_block(
3405 header_address: u64,
3406 entries: &[FixedArrayChunkEntry],
3407 chunk_size_len: u8,
3408) -> Result<Vec<u8>> {
3409 let entry_size = usize::from(OFFSET_SIZE) + usize::from(chunk_size_len) + 4;
3410 let entries_len = checked_mul_usize(entries.len(), entry_size, "fixed array entries size")?;
3411 let capacity = checked_add_usize(
3412 4 + 1 + 1 + usize::from(OFFSET_SIZE),
3413 checked_add_usize(entries_len, 4, "fixed array data block size")?,
3414 "fixed array data block size",
3415 )?;
3416 let size_limit = if chunk_size_len >= 8 {
3417 u64::MAX
3418 } else {
3419 (1u64 << (8 * u32::from(chunk_size_len))) - 1
3420 };
3421 let mut bytes = Vec::with_capacity(capacity);
3422 bytes.extend_from_slice(b"FADB");
3423 bytes.push(0);
3424 bytes.push(1);
3425 bytes.extend_from_slice(&header_address.to_le_bytes());
3426 for entry in entries {
3427 if entry.size > size_limit {
3428 return Err(Error::InvalidDefinition(format!(
3429 "filtered chunk byte length {} exceeds the {chunk_size_len}-byte entry field",
3430 entry.size
3431 )));
3432 }
3433 bytes.extend_from_slice(&entry.address.to_le_bytes());
3434 write_uvar(entry.size, usize::from(chunk_size_len), &mut bytes);
3435 bytes.extend_from_slice(&entry.filter_mask.to_le_bytes());
3436 }
3437 let checksum = jenkins_lookup3(&bytes);
3438 bytes.extend_from_slice(&checksum.to_le_bytes());
3439 Ok(bytes)
3440}
3441
3442fn class_word(class: u8, version: u8, flags: u32) -> u32 {
3443 u32::from(class) | (u32::from(version) << 4) | (flags << 8)
3444}
3445
3446fn byte_order_flag(byte_order: ByteOrder) -> u32 {
3447 match byte_order {
3448 ByteOrder::LittleEndian => 0,
3449 ByteOrder::BigEndian => 1,
3450 }
3451}
3452
3453fn string_padding_bits(padding: StringPadding) -> u32 {
3454 match padding {
3455 StringPadding::NullTerminate => 0,
3456 StringPadding::NullPad => 1,
3457 StringPadding::SpacePad => 2,
3458 }
3459}
3460
3461fn string_encoding_bits(encoding: StringEncoding) -> u32 {
3462 match encoding {
3463 StringEncoding::Ascii => 0,
3464 StringEncoding::Utf8 => 1,
3465 }
3466}
3467
3468fn datatype_element_size(datatype: &Datatype) -> Result<usize> {
3469 let size = match datatype {
3470 Datatype::FixedPoint { size, .. }
3471 | Datatype::FloatingPoint { size, .. }
3472 | Datatype::Bitfield { size, .. }
3473 | Datatype::Reference { size, .. } => Ok(*size as usize),
3474 Datatype::String {
3475 size: StringSize::Fixed(size),
3476 ..
3477 } => Ok(*size as usize),
3478 Datatype::Compound { size, .. } | Datatype::Opaque { size, .. } => Ok(*size as usize),
3479 Datatype::Array { base, dims } => {
3480 if dims.is_empty() {
3481 return Err(Error::InvalidDefinition(
3482 "array datatype must have at least one dimension".into(),
3483 ));
3484 }
3485 if dims.contains(&0) {
3486 return Err(Error::InvalidDefinition(
3487 "array datatype dimensions must be non-zero".into(),
3488 ));
3489 }
3490 let base_size = datatype_element_size(base)?;
3491 let count = dims.iter().try_fold(1usize, |acc, &dim| {
3492 let dim = checked_usize(dim, "array datatype dimension")?;
3493 checked_mul_usize(acc, dim, "array datatype element count")
3494 })?;
3495 checked_mul_usize(base_size, count, "array datatype byte size")
3496 }
3497 Datatype::Enum { base, .. } => datatype_element_size(base),
3498 Datatype::String {
3499 size: StringSize::Variable,
3500 ..
3501 } => Ok(16),
3502 Datatype::VarLen { .. } => Ok(16),
3503 }?;
3504 if size == 0 {
3505 return Err(Error::InvalidDefinition(
3506 "datatype byte size must be non-zero".into(),
3507 ));
3508 }
3509 Ok(size)
3510}
3511
3512fn validate_vlen_sequence_base(datatype: &Datatype) -> Result<()> {
3513 if datatype_element_size(datatype)? == 0 {
3514 return Err(Error::InvalidDefinition(
3515 "variable-length sequence base datatype must have non-zero byte size".into(),
3516 ));
3517 }
3518 match datatype {
3519 Datatype::String {
3520 size: StringSize::Variable,
3521 ..
3522 }
3523 | Datatype::VarLen { .. } => Err(Error::UnsupportedFeature(
3524 "nested heap-backed variable-length sequence bases are not emitted yet".into(),
3525 )),
3526 Datatype::Compound { fields, .. } => {
3527 for field in fields {
3528 validate_vlen_sequence_base(&field.datatype)?;
3529 }
3530 Ok(())
3531 }
3532 Datatype::Array { base, .. } | Datatype::Enum { base, .. } => {
3533 validate_vlen_sequence_base(base)
3534 }
3535 Datatype::FixedPoint { .. }
3536 | Datatype::FloatingPoint { .. }
3537 | Datatype::Bitfield { .. }
3538 | Datatype::Reference { .. }
3539 | Datatype::String {
3540 size: StringSize::Fixed(_),
3541 ..
3542 }
3543 | Datatype::Opaque { .. } => Ok(()),
3544 }
3545}
3546
3547fn validate_writer_filters(
3548 dataset: &DatasetBuilder,
3549 element_size: usize,
3550) -> Result<Vec<FilterDescription>> {
3551 if dataset.filters.is_empty() {
3552 return Ok(Vec::new());
3553 }
3554 let PlannedLayout::Chunked { chunk_shape } = &dataset.layout else {
3555 return Err(Error::InvalidDefinition(
3556 "filtered HDF5 datasets must use chunked layout".into(),
3557 ));
3558 };
3559 let _ = chunk_count(&dataset.shape, chunk_shape)?;
3560 let mut filters = Vec::with_capacity(dataset.filters.len());
3561 for filter in &dataset.filters {
3562 match filter.id {
3563 FILTER_DEFLATE => {
3564 if filter.client_data.len() != 1 || filter.client_data[0] > 9 {
3565 return Err(Error::InvalidDefinition(
3566 "deflate filter requires one compression level in 0..=9".into(),
3567 ));
3568 }
3569 if filter.name.is_some() {
3570 return Err(Error::InvalidDefinition(
3571 "well-known HDF5 deflate filter must not carry a name".into(),
3572 ));
3573 }
3574 filters.push(filter.clone());
3575 }
3576 FILTER_SHUFFLE => {
3577 if filter.name.is_some() {
3578 return Err(Error::InvalidDefinition(
3579 "well-known HDF5 shuffle filter must not carry a name".into(),
3580 ));
3581 }
3582 let element_size_client_data = u32::try_from(element_size).map_err(|_| {
3583 Error::UnsupportedFeature(
3584 "shuffle filter element size exceeds u32 capacity".into(),
3585 )
3586 })?;
3587 match filter.client_data.as_slice() {
3588 [] => filters.push(FilterDescription {
3589 id: FILTER_SHUFFLE,
3590 name: None,
3591 client_data: vec![element_size_client_data],
3592 }),
3593 [size] if *size == element_size_client_data => filters.push(filter.clone()),
3594 [_] => {
3595 return Err(Error::InvalidDefinition(format!(
3596 "shuffle filter element size must match datatype element size {element_size}"
3597 )));
3598 }
3599 _ => {
3600 return Err(Error::InvalidDefinition(
3601 "shuffle filter requires zero or one element-size client value".into(),
3602 ));
3603 }
3604 }
3605 }
3606 FILTER_FLETCHER32 => {
3607 if filter.name.is_some() {
3608 return Err(Error::InvalidDefinition(
3609 "well-known HDF5 Fletcher32 filter must not carry a name".into(),
3610 ));
3611 }
3612 if !filter.client_data.is_empty() {
3613 return Err(Error::InvalidDefinition(
3614 "Fletcher32 filter must not carry client data".into(),
3615 ));
3616 }
3617 filters.push(filter.clone());
3618 }
3619 other => {
3620 return Err(Error::UnsupportedFeature(format!(
3621 "HDF5 filter id {other} is not implemented for writing"
3622 )));
3623 }
3624 }
3625 }
3626 Ok(filters)
3627}
3628
3629fn apply_write_filters(
3630 data: &[u8],
3631 filters: &[FilterDescription],
3632 element_size: usize,
3633) -> Result<Vec<u8>> {
3634 let mut current = data.to_vec();
3635 for filter in filters {
3636 current = match filter.id {
3637 FILTER_SHUFFLE => shuffle_filter(¤t, element_size),
3638 FILTER_DEFLATE => deflate_filter(¤t, filter)?,
3639 FILTER_FLETCHER32 => fletcher32_filter(¤t),
3640 other => {
3641 return Err(Error::UnsupportedFeature(format!(
3642 "HDF5 filter id {other} is not implemented for writing"
3643 )));
3644 }
3645 };
3646 }
3647 Ok(current)
3648}
3649
3650fn shuffle_filter(data: &[u8], element_size: usize) -> Vec<u8> {
3651 if element_size <= 1 || data.is_empty() {
3652 return data.to_vec();
3653 }
3654
3655 let n_elements = data.len() / element_size;
3656 if n_elements == 0 {
3657 return data.to_vec();
3658 }
3659
3660 let mut output = vec![0u8; data.len()];
3661 for byte_idx in 0..element_size {
3662 let dst_start = byte_idx * n_elements;
3663 for elem in 0..n_elements {
3664 output[dst_start + elem] = data[elem * element_size + byte_idx];
3665 }
3666 }
3667
3668 let complete = n_elements * element_size;
3669 if complete < data.len() {
3670 output[complete..].copy_from_slice(&data[complete..]);
3671 }
3672 output
3673}
3674
3675fn fletcher32_filter(data: &[u8]) -> Vec<u8> {
3676 let mut output = Vec::with_capacity(data.len() + 4);
3677 output.extend_from_slice(data);
3678 output.extend_from_slice(&fletcher32(data).to_le_bytes());
3679 output
3680}
3681
3682fn deflate_filter(data: &[u8], filter: &FilterDescription) -> Result<Vec<u8>> {
3683 let level = filter.client_data[0];
3684 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::new(level));
3685 encoder.write_all(data)?;
3686 Ok(encoder.finish()?)
3687}
3688
3689fn ensure_datatype_matches_element<T: H5WriteElement>(datatype: &Datatype) -> Result<()> {
3690 let expected = T::hdf5_type_with_order(numeric_datatype_order(datatype)?);
3691 if &expected != datatype {
3692 return Err(Error::InvalidDefinition(format!(
3693 "typed data does not match dataset datatype: expected {datatype:?}, got {expected:?}"
3694 )));
3695 }
3696 Ok(())
3697}
3698
3699fn chunk_count(shape: &[u64], chunk_shape: &[u64]) -> Result<u64> {
3700 if shape.contains(&0) {
3701 return Ok(0);
3702 }
3703 shape
3704 .iter()
3705 .zip(chunk_shape)
3706 .try_fold(1u64, |acc, (&dim, &chunk_dim)| {
3707 checked_mul_u64(acc, dim.div_ceil(chunk_dim), "chunk count")
3708 })
3709}
3710
3711fn chunked_storage_data(
3712 raw_data: &[u8],
3713 shape: &[u64],
3714 chunk_shape: &[u64],
3715 element_size: usize,
3716) -> Result<Vec<u8>> {
3717 if shape.is_empty() {
3718 return Err(Error::InvalidDefinition(
3719 "chunked scalar HDF5 datasets are not supported".into(),
3720 ));
3721 }
3722 if chunk_shape.len() != shape.len() {
3723 return Err(Error::InvalidDefinition(
3724 "chunk shape rank must match dataset rank".into(),
3725 ));
3726 }
3727
3728 let mut chunks_per_dim = Vec::with_capacity(shape.len());
3729 for (&dim, &chunk_dim) in shape.iter().zip(chunk_shape) {
3730 if chunk_dim == 0 {
3731 return Err(Error::InvalidDefinition(
3732 "chunk dimensions must be non-zero".into(),
3733 ));
3734 }
3735 u32::try_from(chunk_dim).map_err(|_| {
3736 Error::InvalidDefinition("chunk dimension exceeds HDF5 v4 layout capacity".into())
3737 })?;
3738 chunks_per_dim.push(dim.div_ceil(chunk_dim));
3739 }
3740 if shape.contains(&0) {
3741 return Ok(Vec::new());
3742 }
3743
3744 let chunk_elements = chunk_shape.iter().try_fold(1u64, |acc, &dim| {
3745 checked_mul_u64(acc, dim, "chunk element count")
3746 })?;
3747 let element_size_u64 = u64::try_from(element_size).map_err(|_| {
3748 Error::InvalidDefinition("datatype element size exceeds u64 capacity".into())
3749 })?;
3750 let chunk_bytes_u64 = checked_mul_u64(chunk_elements, element_size_u64, "chunk byte length")?;
3751 let total_chunks = chunks_per_dim.iter().try_fold(1u64, |acc, &count| {
3752 checked_mul_u64(acc, count, "chunk count")
3753 })?;
3754 let storage_bytes_u64 = checked_mul_u64(total_chunks, chunk_bytes_u64, "chunked data length")?;
3755 let storage_bytes = checked_usize(storage_bytes_u64, "chunked data length")?;
3756 let mut storage = vec![0u8; storage_bytes];
3757
3758 let dataset_strides = row_major_strides(shape, "dataset stride")?;
3759 let chunk_strides = row_major_strides(chunk_shape, "chunk stride")?;
3760 let mut chunk_indices = vec![0u64; shape.len()];
3761
3762 for chunk_linear in 0..total_chunks {
3763 let chunk_base = checked_usize(
3764 checked_mul_u64(chunk_linear, chunk_bytes_u64, "chunk byte offset")?,
3765 "chunk byte offset",
3766 )?;
3767 let mut starts = Vec::with_capacity(shape.len());
3768 let mut counts = Vec::with_capacity(shape.len());
3769 for ((&chunk_index, &chunk_dim), &dim) in chunk_indices.iter().zip(chunk_shape).zip(shape) {
3770 let start = checked_mul_u64(chunk_index, chunk_dim, "chunk start")?;
3771 starts.push(start);
3772 counts.push((dim - start).min(chunk_dim));
3773 }
3774 let src_start =
3775 starts
3776 .iter()
3777 .zip(&dataset_strides)
3778 .try_fold(0u64, |acc, (&start, &stride)| {
3779 checked_add_u64(
3780 acc,
3781 checked_mul_u64(start, stride, "chunk source element offset")?,
3782 "chunk source element offset",
3783 )
3784 })?;
3785 copy_chunk_rows(
3786 raw_data,
3787 &mut storage,
3788 element_size,
3789 &dataset_strides,
3790 &chunk_strides,
3791 &counts,
3792 0,
3793 src_start,
3794 0,
3795 chunk_base,
3796 )?;
3797 increment_row_major_index(&mut chunk_indices, &chunks_per_dim);
3798 }
3799
3800 Ok(storage)
3801}
3802
3803fn filtered_chunk_storage_data(
3804 raw_data: &[u8],
3805 shape: &[u64],
3806 chunk_shape: &[u64],
3807 element_size: usize,
3808 filters: &[FilterDescription],
3809 kind: ChunkIndexKind,
3810) -> Result<(Vec<u8>, Option<PreparedChunkIndex>)> {
3811 if shape.is_empty() {
3812 return Err(Error::InvalidDefinition(
3813 "chunked scalar HDF5 datasets are not supported".into(),
3814 ));
3815 }
3816 if chunk_shape.len() != shape.len() {
3817 return Err(Error::InvalidDefinition(
3818 "chunk shape rank must match dataset rank".into(),
3819 ));
3820 }
3821
3822 let mut chunks_per_dim = Vec::with_capacity(shape.len());
3823 for (&dim, &chunk_dim) in shape.iter().zip(chunk_shape) {
3824 if chunk_dim == 0 {
3825 return Err(Error::InvalidDefinition(
3826 "chunk dimensions must be non-zero".into(),
3827 ));
3828 }
3829 u32::try_from(chunk_dim).map_err(|_| {
3830 Error::InvalidDefinition("chunk dimension exceeds HDF5 v4 layout capacity".into())
3831 })?;
3832 chunks_per_dim.push(dim.div_ceil(chunk_dim));
3833 }
3834 if shape.contains(&0) {
3835 return Ok((Vec::new(), None));
3836 }
3837
3838 let chunk_elements = chunk_shape.iter().try_fold(1u64, |acc, &dim| {
3839 checked_mul_u64(acc, dim, "chunk element count")
3840 })?;
3841 let element_size_u64 = u64::try_from(element_size).map_err(|_| {
3842 Error::InvalidDefinition("datatype element size exceeds u64 capacity".into())
3843 })?;
3844 let chunk_bytes_u64 = checked_mul_u64(chunk_elements, element_size_u64, "chunk byte length")?;
3845 let chunk_bytes = checked_usize(chunk_bytes_u64, "chunk byte length")?;
3846 let total_chunks = chunks_per_dim.iter().try_fold(1u64, |acc, &count| {
3847 checked_mul_u64(acc, count, "chunk count")
3848 })?;
3849
3850 let dataset_strides = row_major_strides(shape, "dataset stride")?;
3851 let chunk_strides = row_major_strides(chunk_shape, "chunk stride")?;
3852 let mut chunk_indices = vec![0u64; shape.len()];
3853 let mut storage = Vec::new();
3854 let mut entries = Vec::with_capacity(checked_usize(total_chunks, "chunk count")?);
3855
3856 for _ in 0..total_chunks {
3857 let mut starts = Vec::with_capacity(shape.len());
3858 let mut counts = Vec::with_capacity(shape.len());
3859 for ((&chunk_index, &chunk_dim), &dim) in chunk_indices.iter().zip(chunk_shape).zip(shape) {
3860 let start = checked_mul_u64(chunk_index, chunk_dim, "chunk start")?;
3861 starts.push(start);
3862 counts.push((dim - start).min(chunk_dim));
3863 }
3864 let src_start =
3865 starts
3866 .iter()
3867 .zip(&dataset_strides)
3868 .try_fold(0u64, |acc, (&start, &stride)| {
3869 checked_add_u64(
3870 acc,
3871 checked_mul_u64(start, stride, "chunk source element offset")?,
3872 "chunk source element offset",
3873 )
3874 })?;
3875
3876 let mut chunk = vec![0u8; chunk_bytes];
3877 copy_chunk_rows(
3878 raw_data,
3879 &mut chunk,
3880 element_size,
3881 &dataset_strides,
3882 &chunk_strides,
3883 &counts,
3884 0,
3885 src_start,
3886 0,
3887 0,
3888 )?;
3889
3890 let encoded = apply_write_filters(&chunk, filters, element_size)?;
3891 let relative_address = u64::try_from(storage.len()).map_err(|_| {
3892 Error::InvalidDefinition("filtered chunk storage exceeds u64 capacity".into())
3893 })?;
3894 let size = u64::try_from(encoded.len()).map_err(|_| {
3895 Error::InvalidDefinition("filtered chunk byte length exceeds u64 capacity".into())
3896 })?;
3897 storage.extend_from_slice(&encoded);
3898 entries.push(PreparedChunkIndexEntry {
3899 relative_address,
3900 size,
3901 filter_mask: 0,
3902 scaled_offsets: chunk_indices.clone(),
3903 });
3904
3905 increment_row_major_index(&mut chunk_indices, &chunks_per_dim);
3906 }
3907
3908 Ok((
3909 storage,
3910 Some(PreparedChunkIndex {
3911 kind,
3912 chunks: entries,
3913 chunk_size_len: fixed_array_chunk_size_len(chunk_bytes_u64),
3914 }),
3915 ))
3916}
3917
3918fn unfiltered_btree_chunk_index(
3923 shape: &[u64],
3924 chunk_shape: &[u64],
3925 element_size: usize,
3926) -> Result<Option<PreparedChunkIndex>> {
3927 if shape.is_empty() || shape.contains(&0) {
3928 return Ok(None);
3929 }
3930 let mut chunks_per_dim = Vec::with_capacity(shape.len());
3931 for (&dim, &chunk_dim) in shape.iter().zip(chunk_shape) {
3932 if chunk_dim == 0 {
3933 return Err(Error::InvalidDefinition(
3934 "chunk dimensions must be non-zero".into(),
3935 ));
3936 }
3937 chunks_per_dim.push(dim.div_ceil(chunk_dim));
3938 }
3939 let chunk_elements = chunk_shape.iter().try_fold(1u64, |acc, &dim| {
3940 checked_mul_u64(acc, dim, "chunk element count")
3941 })?;
3942 let element_size_u64 = u64::try_from(element_size).map_err(|_| {
3943 Error::InvalidDefinition("datatype element size exceeds u64 capacity".into())
3944 })?;
3945 let chunk_bytes = checked_mul_u64(chunk_elements, element_size_u64, "chunk byte length")?;
3946 let total_chunks = chunks_per_dim.iter().try_fold(1u64, |acc, &count| {
3947 checked_mul_u64(acc, count, "chunk count")
3948 })?;
3949
3950 let mut chunk_indices = vec![0u64; shape.len()];
3951 let mut entries = Vec::with_capacity(checked_usize(total_chunks, "chunk count")?);
3952 let mut relative_address = 0u64;
3953 for _ in 0..total_chunks {
3954 entries.push(PreparedChunkIndexEntry {
3955 relative_address,
3956 size: chunk_bytes,
3957 filter_mask: 0,
3958 scaled_offsets: chunk_indices.clone(),
3959 });
3960 relative_address = checked_add_u64(relative_address, chunk_bytes, "chunk offset")?;
3961 increment_row_major_index(&mut chunk_indices, &chunks_per_dim);
3962 }
3963 Ok(Some(PreparedChunkIndex {
3964 kind: ChunkIndexKind::BtreeV2,
3965 chunks: entries,
3966 chunk_size_len: 0,
3968 }))
3969}
3970
3971fn has_unlimited_max_shape(max_shape: Option<&[u64]>) -> bool {
3973 max_shape.is_some_and(|dims| dims.contains(&UNLIMITED))
3974}
3975
3976fn fixed_array_chunk_size_len(chunk_bytes: u64) -> u8 {
3981 let log2 = 63u32.saturating_sub(chunk_bytes.leading_zeros());
3982 (1 + (log2 + 8) / 8).min(8) as u8
3983}
3984
3985#[allow(clippy::too_many_arguments)]
3986fn copy_chunk_rows(
3987 src: &[u8],
3988 dst: &mut [u8],
3989 element_size: usize,
3990 src_strides: &[u64],
3991 chunk_strides: &[u64],
3992 counts: &[u64],
3993 dim: usize,
3994 src_element: u64,
3995 dst_element: u64,
3996 dst_chunk_base: usize,
3997) -> Result<()> {
3998 let last_dim = counts.len() - 1;
3999 if dim == last_dim {
4000 let count = checked_usize(counts[dim], "chunk row element count")?;
4001 let byte_count = checked_mul_usize(count, element_size, "chunk row byte length")?;
4002 let src_start = element_byte_offset(src_element, element_size, "chunk source offset")?;
4003 let dst_relative =
4004 element_byte_offset(dst_element, element_size, "chunk destination offset")?;
4005 let dst_start = dst_chunk_base.checked_add(dst_relative).ok_or_else(|| {
4006 Error::InvalidDefinition("chunk destination offset overflows usize".into())
4007 })?;
4008 let src_end = src_start
4009 .checked_add(byte_count)
4010 .ok_or_else(|| Error::InvalidDefinition("chunk source end overflows usize".into()))?;
4011 let dst_end = dst_start.checked_add(byte_count).ok_or_else(|| {
4012 Error::InvalidDefinition("chunk destination end overflows usize".into())
4013 })?;
4014 if src_end > src.len() || dst_end > dst.len() {
4015 return Err(Error::InvalidDefinition(
4016 "chunk packing range exceeds dataset storage".into(),
4017 ));
4018 }
4019 dst[dst_start..dst_end].copy_from_slice(&src[src_start..src_end]);
4020 return Ok(());
4021 }
4022
4023 for index in 0..counts[dim] {
4024 copy_chunk_rows(
4025 src,
4026 dst,
4027 element_size,
4028 src_strides,
4029 chunk_strides,
4030 counts,
4031 dim + 1,
4032 checked_add_u64(
4033 src_element,
4034 checked_mul_u64(index, src_strides[dim], "chunk source stride")?,
4035 "chunk source stride",
4036 )?,
4037 checked_add_u64(
4038 dst_element,
4039 checked_mul_u64(index, chunk_strides[dim], "chunk destination stride")?,
4040 "chunk destination stride",
4041 )?,
4042 dst_chunk_base,
4043 )?;
4044 }
4045 Ok(())
4046}
4047
4048fn row_major_strides(dims: &[u64], context: &str) -> Result<Vec<u64>> {
4049 let mut strides = vec![1u64; dims.len()];
4050 let mut stride = 1u64;
4051 for index in (0..dims.len()).rev() {
4052 strides[index] = stride;
4053 stride = checked_mul_u64(stride, dims[index], context)?;
4054 }
4055 Ok(strides)
4056}
4057
4058fn increment_row_major_index(indices: &mut [u64], limits: &[u64]) {
4059 for dim in (0..indices.len()).rev() {
4060 indices[dim] += 1;
4061 if indices[dim] < limits[dim] {
4062 return;
4063 }
4064 indices[dim] = 0;
4065 }
4066}
4067
4068fn element_byte_offset(element_offset: u64, element_size: usize, context: &str) -> Result<usize> {
4069 let element_size = u64::try_from(element_size).map_err(|_| {
4070 Error::InvalidDefinition(format!("{context} element size exceeds u64 capacity"))
4071 })?;
4072 checked_usize(
4073 checked_mul_u64(element_offset, element_size, context)?,
4074 context,
4075 )
4076}
4077
4078fn numeric_datatype_order(datatype: &Datatype) -> Result<ByteOrder> {
4079 datatype_numeric_order(datatype).ok_or_else(|| {
4080 Error::UnsupportedFeature(format!(
4081 "typed data encoding is not implemented for {datatype:?}"
4082 ))
4083 })
4084}
4085
4086fn datatype_numeric_order(datatype: &Datatype) -> Option<ByteOrder> {
4087 match datatype {
4088 Datatype::FixedPoint { byte_order, .. } | Datatype::FloatingPoint { byte_order, .. } => {
4089 Some(*byte_order)
4090 }
4091 _ => None,
4092 }
4093}
4094
4095fn encode_fixed_string_values<S: AsRef<str>>(values: &[S]) -> Result<(Datatype, Vec<u8>)> {
4096 let mut width = 1usize;
4097 let mut encoding = StringEncoding::Ascii;
4098 for value in values {
4099 let value = value.as_ref();
4100 if value.as_bytes().contains(&0) {
4101 return Err(Error::InvalidDefinition(
4102 "fixed string values cannot contain NUL bytes".into(),
4103 ));
4104 }
4105 width = width.max(value.len());
4106 if !value.is_ascii() {
4107 encoding = StringEncoding::Utf8;
4108 }
4109 }
4110
4111 let width_u32 = u32::try_from(width).map_err(|_| {
4112 Error::InvalidDefinition("fixed string element width exceeds HDF5 capacity".into())
4113 })?;
4114 let capacity = checked_mul_usize(values.len(), width, "fixed string data byte length")?;
4115 let mut raw_data = Vec::with_capacity(capacity);
4116 for value in values {
4117 let element_end = raw_data.len().checked_add(width).ok_or_else(|| {
4118 Error::InvalidDefinition("fixed string data byte length exceeds usize capacity".into())
4119 })?;
4120 raw_data.extend_from_slice(value.as_ref().as_bytes());
4121 raw_data.resize(element_end, 0);
4122 }
4123
4124 Ok((
4125 Datatype::String {
4126 size: StringSize::Fixed(width_u32),
4127 encoding,
4128 padding: StringPadding::NullPad,
4129 },
4130 raw_data,
4131 ))
4132}
4133
4134fn expected_element_count(shape: &[u64]) -> Result<usize> {
4135 if shape.is_empty() {
4136 return Ok(1);
4137 }
4138 shape.iter().try_fold(1usize, |acc, &dim| {
4139 checked_mul_usize(
4140 acc,
4141 checked_usize(dim, "dataset dimension")?,
4142 "dataset element count",
4143 )
4144 })
4145}
4146
4147fn expected_data_len(shape: &[u64], element_size: usize) -> Result<usize> {
4148 let elements = expected_element_count(shape)?;
4149 checked_mul_usize(elements, element_size, "dataset byte length")
4150}
4151
4152fn size_width_for(value: u64) -> Result<(u8, usize)> {
4153 if value <= u8::MAX as u64 {
4154 Ok((0, 1))
4155 } else if value <= u16::MAX as u64 {
4156 Ok((1, 2))
4157 } else if value <= u32::MAX as u64 {
4158 Ok((2, 4))
4159 } else {
4160 Ok((3, 8))
4161 }
4162}
4163
4164fn write_uvar(value: u64, width: usize, dst: &mut Vec<u8>) {
4165 let bytes = value.to_le_bytes();
4166 dst.extend_from_slice(&bytes[..width]);
4167}
4168
4169fn pad_to_address(bytes: &mut Vec<u8>, address: u64) -> Result<()> {
4170 let address = checked_usize(address, "HDF5 address")?;
4171 if bytes.len() > address {
4172 return Err(Error::InvalidDefinition(format!(
4173 "internal HDF5 layout overlap: current offset {} exceeds target {address}",
4174 bytes.len()
4175 )));
4176 }
4177 bytes.resize(address, 0);
4178 Ok(())
4179}
4180
4181fn align_u64(value: u64, alignment: u64) -> u64 {
4182 debug_assert!(alignment.is_power_of_two());
4183 (value + alignment - 1) & !(alignment - 1)
4184}
4185
4186fn checked_usize(value: u64, context: &str) -> Result<usize> {
4187 usize::try_from(value).map_err(|_| {
4188 Error::InvalidDefinition(format!(
4189 "{context} value {value} exceeds platform usize capacity"
4190 ))
4191 })
4192}
4193
4194fn checked_mul_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
4195 lhs.checked_mul(rhs).ok_or_else(|| {
4196 Error::InvalidDefinition(format!("{context} exceeds platform usize capacity"))
4197 })
4198}
4199
4200fn checked_add_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
4201 lhs.checked_add(rhs).ok_or_else(|| {
4202 Error::InvalidDefinition(format!("{context} exceeds platform usize capacity"))
4203 })
4204}
4205
4206fn checked_mul_u64(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
4207 lhs.checked_mul(rhs)
4208 .ok_or_else(|| Error::InvalidDefinition(format!("{context} exceeds u64 capacity")))
4209}
4210
4211fn checked_add_u64(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
4212 lhs.checked_add(rhs)
4213 .ok_or_else(|| Error::InvalidDefinition(format!("{context} exceeds u64 capacity")))
4214}