1use std::io::Write;
9
10#[cfg(feature = "netcdf4")]
11use hdf5_writer::{
12 AttributeBuilder as H5AttributeBuilder, ByteOrder as H5ByteOrder,
13 CompoundField as H5CompoundField, DatasetBuilder as H5DatasetBuilder, Datatype as H5Datatype,
14 EnumMember as H5EnumMember, FilterDescription as H5FilterDescription, Hdf5Builder,
15 ReferenceType as H5ReferenceType, StringEncoding as H5StringEncoding,
16 StringPadding as H5StringPadding, StringSize as H5StringSize, VarLenKind as H5VarLenKind,
17 WriteOptions as H5WriteOptions, FILTER_DEFLATE as H5_FILTER_DEFLATE,
18 FILTER_FLETCHER32 as H5_FILTER_FLETCHER32, FILTER_SHUFFLE as H5_FILTER_SHUFFLE,
19 UNLIMITED as H5_UNLIMITED,
20};
21
22pub use netcdf_core::{
23 NcAttrValue, NcAttribute, NcCompoundField, NcDimension, NcEnumMember, NcFormat, NcGroup,
24 NcIntegerValue, NcSliceInfo, NcSliceInfoElem, NcType, NcVariable, NC_FILL_BYTE, NC_FILL_CHAR,
25 NC_FILL_DOUBLE, NC_FILL_FLOAT, NC_FILL_INT, NC_FILL_INT64, NC_FILL_SHORT, NC_FILL_UBYTE,
26 NC_FILL_UINT, NC_FILL_UINT64, NC_FILL_USHORT,
27};
28
29const ABSENT: u32 = 0x0000_0000;
30const NC_DIMENSION: u32 = 0x0000_000A;
31const NC_VARIABLE: u32 = 0x0000_000B;
32const NC_ATTRIBUTE: u32 = 0x0000_000C;
33
34#[derive(Debug, thiserror::Error)]
36pub enum Error {
37 #[error("I/O error: {0}")]
38 Io(#[from] std::io::Error),
39
40 #[error("NetCDF core error: {0}")]
41 Core(#[from] netcdf_core::Error),
42
43 #[error("invalid definition: {0}")]
44 InvalidDefinition(String),
45
46 #[error("type mismatch: expected {expected}, got {actual}")]
47 TypeMismatch { expected: String, actual: String },
48
49 #[error("data length mismatch: expected {expected}, got {actual}")]
50 DataLengthMismatch { expected: usize, actual: usize },
51
52 #[error("requires the NetCDF-4 data model: {reason}")]
56 RequiresNetcdf4 { reason: String },
57
58 #[error("exceeds the capacity of the requested classic format: {reason}")]
62 FormatCapacityExceeded { reason: String },
63
64 #[error("unsupported write feature: {0}")]
65 UnsupportedFeature(String),
66}
67
68pub type Result<T> = std::result::Result<T, Error>;
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum NcWriteFormat {
73 AutoClassic,
75 Classic,
76 Offset64,
77 Cdf5,
78 Nc4,
79 Nc4Classic,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct NcWriteOptions {
85 pub format: NcWriteFormat,
86}
87
88impl Default for NcWriteOptions {
89 fn default() -> Self {
90 Self {
91 format: NcWriteFormat::AutoClassic,
92 }
93 }
94}
95
96impl NcWriteOptions {
97 pub fn classic() -> Self {
98 Self {
99 format: NcWriteFormat::Classic,
100 }
101 }
102
103 pub fn offset64() -> Self {
104 Self {
105 format: NcWriteFormat::Offset64,
106 }
107 }
108
109 pub fn cdf5() -> Self {
110 Self {
111 format: NcWriteFormat::Cdf5,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct DimensionId(usize);
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
122pub struct VariableId(usize);
123
124pub trait NcWriteType: Copy {
126 fn nc_type() -> NcType;
127 fn write_one_be(self, dst: &mut Vec<u8>);
128
129 fn write_one_le(self, dst: &mut Vec<u8>) {
130 let start = dst.len();
131 self.write_one_be(dst);
132 dst[start..].reverse();
133 }
134}
135
136pub trait NcFillValueType: NcWriteType {
143 fn fill_attr_value(self) -> NcAttrValue;
144}
145
146macro_rules! impl_write_type {
147 ($ty:ty, $nc_type:expr, $write:expr) => {
148 impl NcWriteType for $ty {
149 fn nc_type() -> NcType {
150 $nc_type
151 }
152
153 fn write_one_be(self, dst: &mut Vec<u8>) {
154 $write(self, dst)
155 }
156 }
157 };
158}
159
160macro_rules! impl_fill_value_type {
161 ($ty:ty, $attr:ident) => {
162 impl NcFillValueType for $ty {
163 fn fill_attr_value(self) -> NcAttrValue {
164 NcAttrValue::$attr(vec![self])
165 }
166 }
167 };
168}
169
170impl_write_type!(i8, NcType::Byte, |value: i8, dst: &mut Vec<u8>| dst
171 .push(value as u8));
172impl_write_type!(u8, NcType::UByte, |value: u8, dst: &mut Vec<u8>| dst
173 .push(value));
174impl_write_type!(i16, NcType::Short, |value: i16, dst: &mut Vec<u8>| dst
175 .extend_from_slice(&value.to_be_bytes()));
176impl_write_type!(u16, NcType::UShort, |value: u16, dst: &mut Vec<u8>| dst
177 .extend_from_slice(&value.to_be_bytes()));
178impl_write_type!(i32, NcType::Int, |value: i32, dst: &mut Vec<u8>| dst
179 .extend_from_slice(&value.to_be_bytes()));
180impl_write_type!(u32, NcType::UInt, |value: u32, dst: &mut Vec<u8>| dst
181 .extend_from_slice(&value.to_be_bytes()));
182impl_write_type!(i64, NcType::Int64, |value: i64, dst: &mut Vec<u8>| dst
183 .extend_from_slice(&value.to_be_bytes()));
184impl_write_type!(u64, NcType::UInt64, |value: u64, dst: &mut Vec<u8>| dst
185 .extend_from_slice(&value.to_be_bytes()));
186impl_write_type!(f32, NcType::Float, |value: f32, dst: &mut Vec<u8>| dst
187 .extend_from_slice(&value.to_be_bytes()));
188impl_write_type!(f64, NcType::Double, |value: f64, dst: &mut Vec<u8>| dst
189 .extend_from_slice(&value.to_be_bytes()));
190
191impl_fill_value_type!(i8, Bytes);
192impl_fill_value_type!(u8, UBytes);
193impl_fill_value_type!(i16, Shorts);
194impl_fill_value_type!(u16, UShorts);
195impl_fill_value_type!(i32, Ints);
196impl_fill_value_type!(u32, UInts);
197impl_fill_value_type!(i64, Int64s);
198impl_fill_value_type!(u64, UInt64s);
199impl_fill_value_type!(f32, Floats);
200impl_fill_value_type!(f64, Doubles);
201
202const FILL_VALUE_ATTR_NAME: &str = "_FillValue";
203
204#[derive(Debug, Clone)]
205struct DimensionDef {
206 name: String,
207 size: u64,
208 is_unlimited: bool,
209 current_size: u64,
210}
211
212#[derive(Debug, Clone)]
213struct GroupAttributeDef {
214 group_path: String,
215 attribute: NcAttribute,
216}
217
218#[derive(Debug, Clone)]
219struct VariableDef {
220 name: String,
221 dim_ids: Vec<DimensionId>,
222 dtype: NcType,
223 attributes: Vec<NcAttribute>,
224 data: Vec<u8>,
225 data_encoding: VariableDataEncoding,
226 string_values: Option<Vec<String>>,
227 vlen_values: Option<Vec<Vec<u8>>>,
228 storage: VariableStorageDef,
229 fill_value: Option<VariableFillValue>,
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233enum VariableDataEncoding {
234 ClassicBigEndian,
235 Hdf5Native,
236}
237
238#[derive(Debug, Clone)]
239struct VariableFillValue {
240 classic_bytes: Vec<u8>,
241 hdf5_bytes: Vec<u8>,
242}
243
244#[derive(Debug, Clone, Default)]
245struct VariableStorageDef {
246 chunk_shape: Option<Vec<u64>>,
247 shuffle: bool,
248 deflate_level: Option<u8>,
249 fletcher32: bool,
250}
251
252impl VariableStorageDef {
253 fn has_filters(&self) -> bool {
254 self.shuffle || self.deflate_level.is_some() || self.fletcher32
255 }
256
257 fn has_nc4_options(&self) -> bool {
258 self.chunk_shape.is_some() || self.has_filters()
259 }
260}
261
262#[derive(Debug, Clone, Default)]
264pub struct NcFileBuilder {
265 dimensions: Vec<DimensionDef>,
266 attributes: Vec<NcAttribute>,
267 group_attributes: Vec<GroupAttributeDef>,
268 variables: Vec<VariableDef>,
269}
270
271impl NcFileBuilder {
272 pub fn new() -> Self {
273 Self::default()
274 }
275
276 pub fn add_dimension(&mut self, name: impl Into<String>, size: u64) -> Result<DimensionId> {
277 let name = name.into();
278 validate_name(&name, "dimension")?;
279 self.add_dimension_def(name, size, false)
280 }
281
282 pub fn add_dimension_path(
283 &mut self,
284 path: impl Into<String>,
285 size: u64,
286 ) -> Result<DimensionId> {
287 let path = validate_path_name(path.into(), "dimension")?;
288 self.add_dimension_def(path, size, false)
289 }
290
291 pub fn add_unlimited_dimension(&mut self, name: impl Into<String>) -> Result<DimensionId> {
292 let name = name.into();
293 validate_name(&name, "dimension")?;
294 self.add_dimension_def(name, 0, true)
295 }
296
297 pub fn add_unlimited_dimension_path(&mut self, path: impl Into<String>) -> Result<DimensionId> {
298 let path = validate_path_name(path.into(), "dimension")?;
299 self.add_dimension_def(path, 0, true)
300 }
301
302 pub fn add_attribute(&mut self, name: impl Into<String>, value: NcAttrValue) -> Result<()> {
303 let name = name.into();
304 validate_name(&name, "attribute")?;
305 ensure_unique_attr(&self.attributes, &name)?;
306 self.attributes.push(NcAttribute { name, value });
307 Ok(())
308 }
309
310 pub fn add_group_attribute(
311 &mut self,
312 group_path: impl Into<String>,
313 name: impl Into<String>,
314 value: NcAttrValue,
315 ) -> Result<()> {
316 let group_path = validate_path_name(group_path.into(), "group")?;
317 let name = name.into();
318 validate_name(&name, "attribute")?;
319 if self
320 .group_attributes
321 .iter()
322 .any(|attr| attr.group_path == group_path && attr.attribute.name == name)
323 {
324 return Err(Error::InvalidDefinition(format!(
325 "duplicate attribute '{name}' on group '{group_path}'"
326 )));
327 }
328 self.group_attributes.push(GroupAttributeDef {
329 group_path,
330 attribute: NcAttribute { name, value },
331 });
332 Ok(())
333 }
334
335 pub fn add_variable<T: NcWriteType>(
336 &mut self,
337 name: impl Into<String>,
338 dimensions: &[DimensionId],
339 ) -> Result<VariableId> {
340 self.add_variable_with_type(name, dimensions, T::nc_type())
341 }
342
343 pub fn add_variable_path<T: NcWriteType>(
344 &mut self,
345 path: impl Into<String>,
346 dimensions: &[DimensionId],
347 ) -> Result<VariableId> {
348 self.add_variable_path_with_type(path, dimensions, T::nc_type())
349 }
350
351 pub fn add_char_variable(
352 &mut self,
353 name: impl Into<String>,
354 dimensions: &[DimensionId],
355 ) -> Result<VariableId> {
356 self.add_variable_with_type(name, dimensions, NcType::Char)
357 }
358
359 pub fn add_char_variable_path(
360 &mut self,
361 path: impl Into<String>,
362 dimensions: &[DimensionId],
363 ) -> Result<VariableId> {
364 self.add_variable_path_with_type(path, dimensions, NcType::Char)
365 }
366
367 pub fn add_string_variable(
368 &mut self,
369 name: impl Into<String>,
370 dimensions: &[DimensionId],
371 ) -> Result<VariableId> {
372 self.add_variable_with_type(name, dimensions, NcType::String)
373 }
374
375 pub fn add_string_variable_path(
376 &mut self,
377 path: impl Into<String>,
378 dimensions: &[DimensionId],
379 ) -> Result<VariableId> {
380 self.add_variable_path_with_type(path, dimensions, NcType::String)
381 }
382
383 pub fn add_user_defined_variable(
384 &mut self,
385 name: impl Into<String>,
386 dimensions: &[DimensionId],
387 dtype: NcType,
388 ) -> Result<VariableId> {
389 validate_supported_user_defined_type(&dtype)?;
390 self.add_variable_with_type(name, dimensions, dtype)
391 }
392
393 pub fn add_user_defined_variable_path(
394 &mut self,
395 path: impl Into<String>,
396 dimensions: &[DimensionId],
397 dtype: NcType,
398 ) -> Result<VariableId> {
399 validate_supported_user_defined_type(&dtype)?;
400 self.add_variable_path_with_type(path, dimensions, dtype)
401 }
402
403 pub fn add_variable_attribute(
404 &mut self,
405 variable: VariableId,
406 name: impl Into<String>,
407 value: NcAttrValue,
408 ) -> Result<()> {
409 let name = name.into();
410 validate_name(&name, "attribute")?;
411 let variable = self.variable_mut(variable)?;
412 ensure_unique_attr(&variable.attributes, &name)?;
413 variable.attributes.push(NcAttribute { name, value });
414 Ok(())
415 }
416
417 pub fn set_variable_fill_value<T: NcFillValueType>(
418 &mut self,
419 variable: VariableId,
420 value: T,
421 ) -> Result<()> {
422 let variable = self.variable_mut(variable)?;
423 let expected = T::nc_type();
424 if variable.dtype != expected {
425 return Err(Error::TypeMismatch {
426 expected: format!("{:?}", variable.dtype),
427 actual: format!("{expected:?}"),
428 });
429 }
430
431 let mut classic_bytes = Vec::with_capacity(expected.size()?);
432 value.write_one_be(&mut classic_bytes);
433 let mut hdf5_bytes = Vec::with_capacity(expected.size()?);
434 value.write_one_le(&mut hdf5_bytes);
435 set_variable_fill_value_metadata(
436 variable,
437 value.fill_attr_value(),
438 classic_bytes,
439 hdf5_bytes,
440 )
441 }
442
443 pub fn set_variable_chunking(
444 &mut self,
445 variable: VariableId,
446 chunk_shape: impl Into<Vec<u64>>,
447 ) -> Result<()> {
448 let variable = self.variable_mut(variable)?;
449 let chunk_shape = chunk_shape.into();
450 validate_chunk_shape_for_variable(variable, &chunk_shape)?;
451 variable.storage.chunk_shape = Some(chunk_shape);
452 Ok(())
453 }
454
455 pub fn set_variable_shuffle(&mut self, variable: VariableId, enabled: bool) -> Result<()> {
456 let variable = self.variable_mut(variable)?;
457 reject_scalar_filtered_variable(variable)?;
458 variable.storage.shuffle = enabled;
459 Ok(())
460 }
461
462 pub fn set_variable_deflate(
463 &mut self,
464 variable: VariableId,
465 level: Option<u8>,
466 shuffle: bool,
467 ) -> Result<()> {
468 if let Some(level) = level {
469 if level > 9 {
470 return Err(Error::InvalidDefinition(
471 "deflate level must be in 0..=9".into(),
472 ));
473 }
474 }
475 let variable = self.variable_mut(variable)?;
476 reject_scalar_filtered_variable(variable)?;
477 variable.storage.deflate_level = level;
478 variable.storage.shuffle = shuffle;
479 Ok(())
480 }
481
482 pub fn set_variable_fletcher32(&mut self, variable: VariableId, enabled: bool) -> Result<()> {
483 let variable = self.variable_mut(variable)?;
484 reject_scalar_filtered_variable(variable)?;
485 variable.storage.fletcher32 = enabled;
486 Ok(())
487 }
488
489 pub fn write_variable<T: NcWriteType>(
490 &mut self,
491 variable: VariableId,
492 values: &[T],
493 ) -> Result<()> {
494 {
495 let variable = self.variable_mut(variable)?;
496 let expected = T::nc_type();
497 if variable.dtype != expected {
498 return Err(Error::TypeMismatch {
499 expected: format!("{:?}", variable.dtype),
500 actual: format!("{:?}", expected),
501 });
502 }
503 let mut data = Vec::with_capacity(std::mem::size_of_val(values));
504 for &value in values {
505 value.write_one_be(&mut data);
506 }
507 variable.data = data;
508 variable.data_encoding = VariableDataEncoding::ClassicBigEndian;
509 variable.string_values = None;
510 variable.vlen_values = None;
511 }
512 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
513 }
514
515 pub fn write_variable_slice<T: NcWriteType>(
516 &mut self,
517 variable: VariableId,
518 selection: &NcSliceInfo,
519 values: &[T],
520 ) -> Result<()> {
521 let variable_def = self.variable(variable)?;
522 let expected = T::nc_type();
523 if variable_def.dtype != expected {
524 return Err(Error::TypeMismatch {
525 expected: format!("{:?}", variable_def.dtype),
526 actual: format!("{expected:?}"),
527 });
528 }
529 let resolved =
530 self.resolve_variable_write_selection(variable_def, selection, expected.size()?)?;
531 if values.len() != resolved.elements {
532 return Err(Error::DataLengthMismatch {
533 expected: resolved.elements,
534 actual: values.len(),
535 });
536 }
537
538 let elem_size = expected.size()?;
539 let mut encoded = Vec::with_capacity(checked_mul_usize(
540 values.len(),
541 elem_size,
542 "variable slice byte size",
543 )?);
544 for &value in values {
545 value.write_one_be(&mut encoded);
546 }
547
548 let strides = row_major_strides(&resolved.shape, "writer slice stride")?;
549 let total_elements =
550 checked_shape_elements(&resolved.shape, "writer variable element count")?;
551 {
552 let variable_def = self.variable_mut(variable)?;
553 ensure_variable_slice_buffer(
554 variable_def,
555 &resolved.old_shape,
556 &resolved.shape,
557 total_elements,
558 elem_size,
559 resolved.can_grow,
560 )?;
561 scatter_slice_bytes(
562 &mut variable_def.data,
563 elem_size,
564 &resolved.dims,
565 &strides,
566 &encoded,
567 )?;
568 }
569 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
570 }
571
572 pub fn write_char_variable(&mut self, variable: VariableId, bytes: &[u8]) -> Result<()> {
573 {
574 let variable = self.variable_mut(variable)?;
575 if variable.dtype != NcType::Char {
576 return Err(Error::TypeMismatch {
577 expected: "Char".into(),
578 actual: format!("{:?}", variable.dtype),
579 });
580 }
581 variable.data = bytes.to_vec();
582 variable.data_encoding = VariableDataEncoding::ClassicBigEndian;
583 variable.string_values = None;
584 variable.vlen_values = None;
585 }
586 self.update_unlimited_extents_from_element_count(variable, bytes.len() as u64)
587 }
588
589 pub fn write_char_variable_slice(
590 &mut self,
591 variable: VariableId,
592 selection: &NcSliceInfo,
593 bytes: &[u8],
594 ) -> Result<()> {
595 let variable_def = self.variable(variable)?;
596 if variable_def.dtype != NcType::Char {
597 return Err(Error::TypeMismatch {
598 expected: "Char".into(),
599 actual: format!("{:?}", variable_def.dtype),
600 });
601 }
602 let resolved = self.resolve_variable_write_selection(variable_def, selection, 1)?;
603 if bytes.len() != resolved.elements {
604 return Err(Error::DataLengthMismatch {
605 expected: resolved.elements,
606 actual: bytes.len(),
607 });
608 }
609
610 let strides = row_major_strides(&resolved.shape, "writer char slice stride")?;
611 let total_elements =
612 checked_shape_elements(&resolved.shape, "writer char variable element count")?;
613 {
614 let variable_def = self.variable_mut(variable)?;
615 ensure_variable_slice_buffer(
616 variable_def,
617 &resolved.old_shape,
618 &resolved.shape,
619 total_elements,
620 1,
621 resolved.can_grow,
622 )?;
623 scatter_slice_bytes(&mut variable_def.data, 1, &resolved.dims, &strides, bytes)?;
624 }
625 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
626 }
627
628 pub fn write_char_variable_strings<S: AsRef<str>>(
629 &mut self,
630 variable: VariableId,
631 values: &[S],
632 ) -> Result<()> {
633 let (name, width, inferred_unlimited) = {
634 let variable_def = self.variable(variable)?;
635 if variable_def.dtype != NcType::Char {
636 return Err(Error::TypeMismatch {
637 expected: "Char".into(),
638 actual: format!("{:?}", variable_def.dtype),
639 });
640 }
641 let inferred_unlimited =
642 self.validate_char_string_value_count(variable_def, values.len())?;
643 (
644 variable_def.name.clone(),
645 self.char_string_axis_width(variable_def)?,
646 inferred_unlimited,
647 )
648 };
649 let encoded = encode_char_string_values(&name, width, values)?;
650 self.write_char_variable(variable, &encoded)?;
651 if let Some((dimension, size)) = inferred_unlimited {
652 if size > self.dimensions[dimension.0].current_size {
653 self.dimensions[dimension.0].current_size = size;
654 }
655 }
656 Ok(())
657 }
658
659 pub fn write_char_variable_strings_slice<S: AsRef<str>>(
660 &mut self,
661 variable: VariableId,
662 selection: &NcSliceInfo,
663 values: &[S],
664 ) -> Result<()> {
665 let (name, width, resolved) = {
666 let variable_def = self.variable(variable)?;
667 if variable_def.dtype != NcType::Char {
668 return Err(Error::TypeMismatch {
669 expected: "Char".into(),
670 actual: format!("{:?}", variable_def.dtype),
671 });
672 }
673 (
674 variable_def.name.clone(),
675 self.char_string_axis_width(variable_def)?,
676 self.resolve_char_string_write_selection(variable_def, selection)?,
677 )
678 };
679 if values.len() != resolved.elements {
680 return Err(Error::DataLengthMismatch {
681 expected: resolved.elements,
682 actual: values.len(),
683 });
684 }
685
686 let encoded = encode_char_string_values(&name, width, values)?;
687 let mut selections = selection.selections.clone();
688 selections.push(NcSliceInfoElem::Slice {
689 start: 0,
690 end: u64::try_from(width).map_err(|_| {
691 Error::InvalidDefinition("char string width exceeds u64 capacity".into())
692 })?,
693 step: 1,
694 });
695 self.write_char_variable_slice(variable, &NcSliceInfo { selections }, &encoded)
696 }
697
698 pub fn write_user_defined_variable_bytes(
699 &mut self,
700 variable: VariableId,
701 bytes: &[u8],
702 ) -> Result<()> {
703 let elem_size = {
704 let variable = self.variable_mut(variable)?;
705 validate_fixed_width_user_defined_type(&variable.dtype)?;
706 let elem_size = variable.dtype.size()?;
707 variable.data = bytes.to_vec();
708 variable.data_encoding = VariableDataEncoding::Hdf5Native;
709 variable.string_values = None;
710 variable.vlen_values = None;
711 elem_size
712 };
713 if bytes.len() % elem_size != 0 {
714 return Err(Error::DataLengthMismatch {
715 expected: bytes.len() + (elem_size - bytes.len() % elem_size),
716 actual: bytes.len(),
717 });
718 }
719 self.update_unlimited_extents_from_element_count(variable, (bytes.len() / elem_size) as u64)
720 }
721
722 pub fn write_user_defined_variable_slice_bytes(
723 &mut self,
724 variable: VariableId,
725 selection: &NcSliceInfo,
726 bytes: &[u8],
727 ) -> Result<()> {
728 let elem_size = {
729 let variable = self.variable(variable)?;
730 validate_fixed_width_user_defined_type(&variable.dtype)?;
731 variable.dtype.size()?
732 };
733 self.write_native_variable_slice_bytes(variable, selection, bytes, elem_size)
734 }
735
736 pub fn write_enum_variable(
737 &mut self,
738 variable: VariableId,
739 values: &[NcIntegerValue],
740 ) -> Result<()> {
741 {
742 let variable = self.variable_mut(variable)?;
743 let NcType::Enum { base, .. } = &variable.dtype else {
744 return Err(Error::TypeMismatch {
745 expected: "Enum".into(),
746 actual: format!("{:?}", variable.dtype),
747 });
748 };
749 let mut data = Vec::with_capacity(values.len() * base.size()?);
750 for &value in values {
751 data.extend_from_slice(&nc_enum_value_to_le_bytes(base, value)?);
752 }
753 variable.data = data;
754 variable.data_encoding = VariableDataEncoding::Hdf5Native;
755 variable.string_values = None;
756 variable.vlen_values = None;
757 }
758 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
759 }
760
761 pub fn write_enum_variable_slice(
762 &mut self,
763 variable: VariableId,
764 selection: &NcSliceInfo,
765 values: &[NcIntegerValue],
766 ) -> Result<()> {
767 let base = {
768 let variable = self.variable(variable)?;
769 let NcType::Enum { base, .. } = &variable.dtype else {
770 return Err(Error::TypeMismatch {
771 expected: "Enum".into(),
772 actual: format!("{:?}", variable.dtype),
773 });
774 };
775 base.clone()
776 };
777 let elem_size = base.size()?;
778 let mut data = Vec::with_capacity(checked_mul_usize(
779 values.len(),
780 elem_size,
781 "enum slice byte size",
782 )?);
783 for &value in values {
784 data.extend_from_slice(&nc_enum_value_to_le_bytes(&base, value)?);
785 }
786 self.write_native_variable_slice_bytes(variable, selection, &data, elem_size)
787 }
788
789 pub fn write_opaque_variable<S: AsRef<[u8]>>(
790 &mut self,
791 variable: VariableId,
792 values: &[S],
793 ) -> Result<()> {
794 {
795 let variable = self.variable_mut(variable)?;
796 let NcType::Opaque { size, .. } = &variable.dtype else {
797 return Err(Error::TypeMismatch {
798 expected: "Opaque".into(),
799 actual: format!("{:?}", variable.dtype),
800 });
801 };
802 let size = usize::try_from(*size).map_err(|_| {
803 Error::InvalidDefinition("opaque element size exceeds platform usize".into())
804 })?;
805 let mut data = Vec::with_capacity(values.len() * size);
806 for value in values {
807 let value = value.as_ref();
808 if value.len() != size {
809 return Err(Error::DataLengthMismatch {
810 expected: size,
811 actual: value.len(),
812 });
813 }
814 data.extend_from_slice(value);
815 }
816 variable.data = data;
817 variable.data_encoding = VariableDataEncoding::Hdf5Native;
818 variable.string_values = None;
819 variable.vlen_values = None;
820 }
821 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
822 }
823
824 pub fn write_opaque_variable_slice<S: AsRef<[u8]>>(
825 &mut self,
826 variable: VariableId,
827 selection: &NcSliceInfo,
828 values: &[S],
829 ) -> Result<()> {
830 let size = {
831 let variable = self.variable(variable)?;
832 let NcType::Opaque { size, .. } = &variable.dtype else {
833 return Err(Error::TypeMismatch {
834 expected: "Opaque".into(),
835 actual: format!("{:?}", variable.dtype),
836 });
837 };
838 usize::try_from(*size).map_err(|_| {
839 Error::InvalidDefinition("opaque element size exceeds platform usize".into())
840 })?
841 };
842 let mut data = Vec::with_capacity(checked_mul_usize(
843 values.len(),
844 size,
845 "opaque slice byte size",
846 )?);
847 for value in values {
848 let value = value.as_ref();
849 if value.len() != size {
850 return Err(Error::DataLengthMismatch {
851 expected: size,
852 actual: value.len(),
853 });
854 }
855 data.extend_from_slice(value);
856 }
857 self.write_native_variable_slice_bytes(variable, selection, &data, size)
858 }
859
860 pub fn write_array_variable<T: NcWriteType>(
861 &mut self,
862 variable: VariableId,
863 values: &[T],
864 ) -> Result<()> {
865 let variable_elements = {
866 let variable = self.variable_mut(variable)?;
867 let NcType::Array { base, .. } = &variable.dtype else {
868 return Err(Error::TypeMismatch {
869 expected: "Array".into(),
870 actual: format!("{:?}", variable.dtype),
871 });
872 };
873 let expected = T::nc_type();
874 if base.as_ref() != &expected {
875 return Err(Error::TypeMismatch {
876 expected: format!("{:?}", base),
877 actual: format!("{expected:?}"),
878 });
879 }
880 let mut data = Vec::with_capacity(std::mem::size_of_val(values));
881 for &value in values {
882 value.write_one_le(&mut data);
883 }
884 let elem_size = variable.dtype.size()?;
885 if data.len() % elem_size != 0 {
886 return Err(Error::DataLengthMismatch {
887 expected: data.len() + (elem_size - data.len() % elem_size),
888 actual: data.len(),
889 });
890 }
891 let variable_elements = (data.len() / elem_size) as u64;
892 variable.data = data;
893 variable.data_encoding = VariableDataEncoding::Hdf5Native;
894 variable.string_values = None;
895 variable.vlen_values = None;
896 variable_elements
897 };
898 self.update_unlimited_extents_from_element_count(variable, variable_elements)
899 }
900
901 pub fn write_array_variable_slice<T: NcWriteType>(
902 &mut self,
903 variable: VariableId,
904 selection: &NcSliceInfo,
905 values: &[T],
906 ) -> Result<()> {
907 let elem_size = {
908 let variable = self.variable(variable)?;
909 let NcType::Array { base, .. } = &variable.dtype else {
910 return Err(Error::TypeMismatch {
911 expected: "Array".into(),
912 actual: format!("{:?}", variable.dtype),
913 });
914 };
915 let expected = T::nc_type();
916 if base.as_ref() != &expected {
917 return Err(Error::TypeMismatch {
918 expected: format!("{:?}", base),
919 actual: format!("{expected:?}"),
920 });
921 }
922 variable.dtype.size()?
923 };
924 let mut data = Vec::with_capacity(checked_mul_usize(
925 values.len(),
926 T::nc_type().size()?,
927 "array slice byte size",
928 )?);
929 for &value in values {
930 value.write_one_le(&mut data);
931 }
932 self.write_native_variable_slice_bytes(variable, selection, &data, elem_size)
933 }
934
935 pub fn write_vlen_variable_bytes<S: AsRef<[u8]>>(
936 &mut self,
937 variable: VariableId,
938 values: &[S],
939 ) -> Result<()> {
940 {
941 let variable = self.variable_mut(variable)?;
942 let NcType::VLen { base } = &variable.dtype else {
943 return Err(Error::TypeMismatch {
944 expected: "VLen".into(),
945 actual: format!("{:?}", variable.dtype),
946 });
947 };
948 validate_vlen_base_nc4_type(base)?;
949 let base_size = base.size()?;
950 let mut sequences = Vec::with_capacity(values.len());
951 for value in values {
952 let value = value.as_ref();
953 if value.len() % base_size != 0 {
954 return Err(Error::InvalidDefinition(format!(
955 "vlen sequence byte length {} is not a multiple of base element size {base_size}",
956 value.len()
957 )));
958 }
959 sequences.push(value.to_vec());
960 }
961 variable.data.clear();
962 variable.data_encoding = VariableDataEncoding::Hdf5Native;
963 variable.string_values = None;
964 variable.vlen_values = Some(sequences);
965 }
966 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
967 }
968
969 pub fn write_vlen_variable<T: NcWriteType>(
970 &mut self,
971 variable: VariableId,
972 values: &[Vec<T>],
973 ) -> Result<()> {
974 {
975 let variable_def = self.variable_mut(variable)?;
976 let NcType::VLen { base } = &variable_def.dtype else {
977 return Err(Error::TypeMismatch {
978 expected: "VLen".into(),
979 actual: format!("{:?}", variable_def.dtype),
980 });
981 };
982 let expected = T::nc_type();
983 if base.as_ref() != &expected {
984 return Err(Error::TypeMismatch {
985 expected: format!("{:?}", base),
986 actual: format!("{expected:?}"),
987 });
988 }
989
990 let mut sequences = Vec::with_capacity(values.len());
991 for sequence in values {
992 let mut bytes = Vec::with_capacity(std::mem::size_of_val(sequence.as_slice()));
993 for &value in sequence {
994 value.write_one_le(&mut bytes);
995 }
996 sequences.push(bytes);
997 }
998 variable_def.data.clear();
999 variable_def.data_encoding = VariableDataEncoding::Hdf5Native;
1000 variable_def.string_values = None;
1001 variable_def.vlen_values = Some(sequences);
1002 }
1003 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
1004 }
1005
1006 pub fn write_vlen_variable_slice_bytes<S: AsRef<[u8]>>(
1007 &mut self,
1008 variable: VariableId,
1009 selection: &NcSliceInfo,
1010 values: &[S],
1011 ) -> Result<()> {
1012 let (resolved, base_size) = {
1013 let variable_def = self.variable(variable)?;
1014 let NcType::VLen { base } = &variable_def.dtype else {
1015 return Err(Error::TypeMismatch {
1016 expected: "VLen".into(),
1017 actual: format!("{:?}", variable_def.dtype),
1018 });
1019 };
1020 validate_vlen_base_nc4_type(base)?;
1021 (
1022 self.resolve_variable_write_selection(variable_def, selection, 1)?,
1023 base.size()?,
1024 )
1025 };
1026 if values.len() != resolved.elements {
1027 return Err(Error::DataLengthMismatch {
1028 expected: resolved.elements,
1029 actual: values.len(),
1030 });
1031 }
1032
1033 let mut sequences = Vec::with_capacity(values.len());
1034 for value in values {
1035 let value = value.as_ref();
1036 if value.len() % base_size != 0 {
1037 return Err(Error::InvalidDefinition(format!(
1038 "vlen sequence byte length {} is not a multiple of base element size {base_size}",
1039 value.len()
1040 )));
1041 }
1042 sequences.push(value.to_vec());
1043 }
1044
1045 let strides = row_major_strides(&resolved.shape, "writer vlen slice stride")?;
1046 let total_elements =
1047 checked_shape_elements(&resolved.shape, "writer vlen variable element count")?;
1048 {
1049 let variable_def = self.variable_mut(variable)?;
1050 ensure_variable_vlen_slice_buffer(
1051 variable_def,
1052 &resolved.old_shape,
1053 &resolved.shape,
1054 total_elements,
1055 resolved.can_grow,
1056 )?;
1057 let vlen_values = variable_def.vlen_values.as_mut().ok_or_else(|| {
1058 Error::InvalidDefinition(format!(
1059 "vlen variable '{}' has no initialized sequence data",
1060 variable_def.name
1061 ))
1062 })?;
1063 scatter_slice_values(vlen_values, &resolved.dims, &strides, &sequences)?;
1064 }
1065 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
1066 }
1067
1068 pub fn write_vlen_variable_slice<T: NcWriteType>(
1069 &mut self,
1070 variable: VariableId,
1071 selection: &NcSliceInfo,
1072 values: &[Vec<T>],
1073 ) -> Result<()> {
1074 let resolved = {
1075 let variable_def = self.variable(variable)?;
1076 let NcType::VLen { base } = &variable_def.dtype else {
1077 return Err(Error::TypeMismatch {
1078 expected: "VLen".into(),
1079 actual: format!("{:?}", variable_def.dtype),
1080 });
1081 };
1082 let expected = T::nc_type();
1083 if base.as_ref() != &expected {
1084 return Err(Error::TypeMismatch {
1085 expected: format!("{:?}", base),
1086 actual: format!("{expected:?}"),
1087 });
1088 }
1089 self.resolve_variable_write_selection(variable_def, selection, 1)?
1090 };
1091 if values.len() != resolved.elements {
1092 return Err(Error::DataLengthMismatch {
1093 expected: resolved.elements,
1094 actual: values.len(),
1095 });
1096 }
1097
1098 let mut sequences = Vec::with_capacity(values.len());
1099 for sequence in values {
1100 let mut bytes = Vec::with_capacity(std::mem::size_of_val(sequence.as_slice()));
1101 for &value in sequence {
1102 value.write_one_le(&mut bytes);
1103 }
1104 sequences.push(bytes);
1105 }
1106
1107 let strides = row_major_strides(&resolved.shape, "writer vlen slice stride")?;
1108 let total_elements =
1109 checked_shape_elements(&resolved.shape, "writer vlen variable element count")?;
1110 {
1111 let variable_def = self.variable_mut(variable)?;
1112 ensure_variable_vlen_slice_buffer(
1113 variable_def,
1114 &resolved.old_shape,
1115 &resolved.shape,
1116 total_elements,
1117 resolved.can_grow,
1118 )?;
1119 let vlen_values = variable_def.vlen_values.as_mut().ok_or_else(|| {
1120 Error::InvalidDefinition(format!(
1121 "vlen variable '{}' has no initialized sequence data",
1122 variable_def.name
1123 ))
1124 })?;
1125 scatter_slice_values(vlen_values, &resolved.dims, &strides, &sequences)?;
1126 }
1127 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
1128 }
1129
1130 pub fn write_string_variable<S: AsRef<str>>(
1131 &mut self,
1132 variable: VariableId,
1133 values: &[S],
1134 ) -> Result<()> {
1135 {
1136 let variable = self.variable_mut(variable)?;
1137 if variable.dtype != NcType::String {
1138 return Err(Error::TypeMismatch {
1139 expected: "String".into(),
1140 actual: format!("{:?}", variable.dtype),
1141 });
1142 }
1143
1144 let mut strings = Vec::with_capacity(values.len());
1145 for value in values {
1146 let value = value.as_ref();
1147 if value.as_bytes().contains(&0) {
1148 return Err(Error::InvalidDefinition(
1149 "NC_STRING variable values cannot contain NUL bytes".into(),
1150 ));
1151 }
1152 strings.push(value.to_string());
1153 }
1154 variable.data.clear();
1155 variable.data_encoding = VariableDataEncoding::Hdf5Native;
1156 variable.string_values = Some(strings);
1157 variable.vlen_values = None;
1158 }
1159 self.update_unlimited_extents_from_element_count(variable, values.len() as u64)
1160 }
1161
1162 pub fn write_string_variable_slice<S: AsRef<str>>(
1163 &mut self,
1164 variable: VariableId,
1165 selection: &NcSliceInfo,
1166 values: &[S],
1167 ) -> Result<()> {
1168 let variable_def = self.variable(variable)?;
1169 if variable_def.dtype != NcType::String {
1170 return Err(Error::TypeMismatch {
1171 expected: "String".into(),
1172 actual: format!("{:?}", variable_def.dtype),
1173 });
1174 }
1175 let resolved = self.resolve_variable_write_selection(variable_def, selection, 1)?;
1176 if values.len() != resolved.elements {
1177 return Err(Error::DataLengthMismatch {
1178 expected: resolved.elements,
1179 actual: values.len(),
1180 });
1181 }
1182
1183 let mut strings = Vec::with_capacity(values.len());
1184 for value in values {
1185 let value = value.as_ref();
1186 if value.as_bytes().contains(&0) {
1187 return Err(Error::InvalidDefinition(
1188 "NC_STRING variable values cannot contain NUL bytes".into(),
1189 ));
1190 }
1191 strings.push(value.to_string());
1192 }
1193
1194 let strides = row_major_strides(&resolved.shape, "writer string slice stride")?;
1195 let total_elements =
1196 checked_shape_elements(&resolved.shape, "writer string variable element count")?;
1197 {
1198 let variable_def = self.variable_mut(variable)?;
1199 ensure_variable_string_slice_buffer(
1200 variable_def,
1201 &resolved.old_shape,
1202 &resolved.shape,
1203 total_elements,
1204 resolved.can_grow,
1205 )?;
1206 let string_values = variable_def.string_values.as_mut().ok_or_else(|| {
1207 Error::InvalidDefinition(format!(
1208 "NC_STRING variable '{}' has no initialized string data",
1209 variable_def.name
1210 ))
1211 })?;
1212 scatter_slice_values(string_values, &resolved.dims, &strides, &strings)?;
1213 }
1214 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
1215 }
1216
1217 pub fn write<W: Write>(&self, mut writer: W, options: NcWriteOptions) -> Result<NcFormat> {
1218 if options.format == NcWriteFormat::AutoClassic {
1219 return self.write_auto_classic(&mut writer);
1220 }
1221
1222 let format = self.select_format(options)?;
1223 match format {
1224 NcFormat::Classic | NcFormat::Offset64 | NcFormat::Cdf5 => {
1225 let plan = ClassicWritePlan::build(self, format)?;
1226 plan.write(&mut writer)?;
1227 Ok(format)
1228 }
1229 NcFormat::Nc4 | NcFormat::Nc4Classic => self.write_nc4(&mut writer, format),
1230 }
1231 }
1232
1233 pub fn to_vec(&self, options: NcWriteOptions) -> Result<(NcFormat, Vec<u8>)> {
1234 let mut data = Vec::new();
1235 let format = self.write(&mut data, options)?;
1236 Ok((format, data))
1237 }
1238
1239 fn write_auto_classic(&self, writer: &mut impl Write) -> Result<NcFormat> {
1240 if self.requires_cdf5() {
1241 self.validate_for_format(NcFormat::Cdf5)?;
1242 let plan = ClassicWritePlan::build(self, NcFormat::Cdf5)?;
1243 plan.write(writer)?;
1244 return Ok(NcFormat::Cdf5);
1245 }
1246
1247 match ClassicWritePlan::build(self, NcFormat::Classic) {
1248 Ok(plan) => {
1249 plan.write(writer)?;
1250 Ok(NcFormat::Classic)
1251 }
1252 Err(classic_err) => match ClassicWritePlan::build(self, NcFormat::Offset64) {
1253 Ok(plan) => {
1254 plan.write(writer)?;
1255 Ok(NcFormat::Offset64)
1256 }
1257 Err(_) => Err(classic_err),
1258 },
1259 }
1260 }
1261
1262 #[cfg(feature = "netcdf4")]
1263 fn write_nc4(&self, writer: &mut impl Write, format: NcFormat) -> Result<NcFormat> {
1264 self.validate_for_nc4_bridge(format)?;
1265 let hdf5_plan = self.build_hdf5_plan(format)?;
1266 let bytes = hdf5_plan
1269 .encode(H5WriteOptions::default())
1270 .map_err(hdf5_error_to_unsupported)?;
1271 writer.write_all(&bytes)?;
1272 Ok(format)
1273 }
1274
1275 #[cfg(not(feature = "netcdf4"))]
1276 fn write_nc4(&self, _writer: &mut impl Write, _format: NcFormat) -> Result<NcFormat> {
1277 Err(Error::UnsupportedFeature(
1278 "NetCDF-4 writing requires the netcdf4 feature".into(),
1279 ))
1280 }
1281
1282 #[cfg(feature = "netcdf4")]
1283 fn validate_for_nc4_bridge(&self, format: NcFormat) -> Result<()> {
1284 if format == NcFormat::Nc4Classic {
1285 validate_nc4_classic_model(self)?;
1286 for variable in &self.variables {
1287 validate_classic_type(&variable.dtype)?;
1288 for attr in &variable.attributes {
1289 validate_nc4_classic_attr_value(&attr.value)?;
1290 }
1291 }
1292 for attr in &self.attributes {
1293 validate_nc4_classic_attr_value(&attr.value)?;
1294 }
1295 }
1296 let dimension_sizes = self.nc4_dimension_sizes()?;
1297 for variable in &self.variables {
1298 validate_nc4_variable_data(variable, &dimension_sizes)?;
1299 }
1300 for (dim_id, dimension) in self.dimensions.iter().enumerate() {
1301 let dimension_id = DimensionId(dim_id);
1302 if self
1303 .coordinate_variable_for_dimension(dimension_id)
1304 .is_none()
1305 && self
1306 .variables
1307 .iter()
1308 .any(|variable| variable.name == dimension.name)
1309 {
1310 return Err(Error::UnsupportedFeature(format!(
1311 "NetCDF-4 dimension '{}' needs a hidden dimension-scale dataset, but a scalar variable already uses that name",
1312 dimension.name
1313 )));
1314 }
1315 }
1316 Ok(())
1317 }
1318
1319 #[cfg(feature = "netcdf4")]
1320 fn build_hdf5_plan(&self, format: NcFormat) -> Result<hdf5_writer::Hdf5WritePlan> {
1321 let mut builder = Hdf5Builder::new();
1322 let dimension_sizes = self.nc4_dimension_sizes()?;
1323 if format == NcFormat::Nc4Classic {
1324 builder = builder.attribute(
1325 H5AttributeBuilder::scalar("_nc3_strict", 1_i32)
1326 .map_err(hdf5_error_to_unsupported)?,
1327 );
1328 }
1329 for attr in &self.attributes {
1330 builder = builder.attribute(nc_attr_to_hdf5(attr)?);
1331 }
1332 for group_attr in &self.group_attributes {
1333 builder = builder.group_attribute(
1334 group_attr.group_path.as_str(),
1335 nc_attr_to_hdf5(&group_attr.attribute)?,
1336 );
1337 }
1338
1339 for (dim_id, dimension) in self.dimensions.iter().enumerate() {
1340 if self
1341 .coordinate_variable_for_dimension(DimensionId(dim_id))
1342 .is_some()
1343 {
1344 continue;
1345 }
1346 let size = dimension_sizes[dim_id];
1347 let zeros = vec![0_i32; checked_usize(size, "dimension scale size")?];
1348 let mut dataset = H5DatasetBuilder::typed_data(&dimension.name, vec![size], &zeros)
1349 .map_err(hdf5_error_to_unsupported)?
1350 .attribute(H5AttributeBuilder::fixed_string("CLASS", "DIMENSION_SCALE"))
1351 .attribute(H5AttributeBuilder::fixed_string(
1352 "NAME",
1353 format!(
1354 "This is a netCDF dimension but not a netCDF variable. {}",
1355 path_leaf_name(&dimension.name)
1356 ),
1357 ))
1358 .attribute(
1359 H5AttributeBuilder::scalar("_Netcdf4Dimid", dim_id as i32)
1360 .map_err(hdf5_error_to_unsupported)?,
1361 );
1362 if let Some(reference_list) = self.reference_list_attribute(DimensionId(dim_id))? {
1363 dataset = dataset.attribute(reference_list);
1364 }
1365 if dimension.is_unlimited {
1366 dataset = dataset
1367 .max_shape(vec![H5_UNLIMITED])
1368 .chunked(nc4_default_chunk_shape(
1369 &[size],
1370 std::mem::size_of::<i32>(),
1371 )?);
1372 }
1373 builder = builder.dataset(dataset);
1374 }
1375
1376 for variable in &self.variables {
1377 let shape = variable
1378 .dim_ids
1379 .iter()
1380 .map(|id| dimension_sizes[id.0])
1381 .collect::<Vec<_>>();
1382 let mut dataset = if variable.dtype == NcType::String {
1383 let values = variable.string_values.as_ref().ok_or_else(|| {
1384 Error::InvalidDefinition(format!(
1385 "NC_STRING variable '{}' has no string data",
1386 variable.name
1387 ))
1388 })?;
1389 H5DatasetBuilder::vlen_string_data(&variable.name, shape.clone(), values)
1390 .map_err(hdf5_error_to_unsupported)?
1391 } else if let NcType::VLen { base } = &variable.dtype {
1392 let values = variable.vlen_values.as_ref().ok_or_else(|| {
1393 Error::InvalidDefinition(format!(
1394 "NC_VLEN variable '{}' has no sequence data",
1395 variable.name
1396 ))
1397 })?;
1398 H5DatasetBuilder::vlen_sequence_data(
1399 &variable.name,
1400 nc_type_to_hdf5(base)?,
1401 shape.clone(),
1402 values.clone(),
1403 )
1404 .map_err(hdf5_error_to_unsupported)?
1405 } else {
1406 let data = match variable.data_encoding {
1407 VariableDataEncoding::ClassicBigEndian => {
1408 convert_classic_be_data_to_hdf5_le(&variable.dtype, &variable.data)?
1409 }
1410 VariableDataEncoding::Hdf5Native => variable.data.clone(),
1411 };
1412 let dataset = H5DatasetBuilder::new(
1413 &variable.name,
1414 nc_type_to_hdf5(&variable.dtype)?,
1415 shape.clone(),
1416 );
1417 if data.is_empty() && variable.fill_value.is_some() {
1418 dataset
1419 } else {
1420 dataset.raw_data(data)
1421 }
1422 };
1423 if let Some(fill_value) = &variable.fill_value {
1424 dataset = dataset.fill_value(fill_value.hdf5_bytes.clone());
1425 }
1426 let chunk_shape = nc4_variable_chunk_shape(
1427 variable,
1428 &shape,
1429 self.nc4_variable_max_shape(variable).is_some(),
1430 )?;
1431 if let Some(max_shape) = self.nc4_variable_max_shape(variable) {
1432 dataset = dataset.max_shape(max_shape);
1433 }
1434 if let Some(chunk_shape) = chunk_shape {
1435 dataset = dataset.chunked(chunk_shape);
1436 }
1437 for filter in nc4_variable_filters(variable) {
1438 dataset = dataset.filter(filter);
1439 }
1440
1441 if self.variable_is_coordinate_scale(variable)? {
1442 let dim_id = variable.dim_ids[0];
1443 dataset = dataset
1444 .attribute(H5AttributeBuilder::fixed_string("CLASS", "DIMENSION_SCALE"))
1445 .attribute(H5AttributeBuilder::fixed_string(
1446 "NAME",
1447 path_leaf_name(&self.dimensions[dim_id.0].name),
1448 ))
1449 .attribute(
1450 H5AttributeBuilder::scalar("_Netcdf4Dimid", dim_id.0 as i32)
1451 .map_err(hdf5_error_to_unsupported)?,
1452 );
1453 if let Some(reference_list) = self.reference_list_attribute(dim_id)? {
1454 dataset = dataset.attribute(reference_list);
1455 }
1456 } else if variable.dim_ids.is_empty() {
1457 dataset = dataset.attribute(empty_dimension_list_attribute());
1458 } else {
1459 dataset = dataset.attribute(self.dimension_list_attribute(variable)?);
1460 }
1461
1462 for attr in &variable.attributes {
1463 dataset = dataset.attribute(nc_attr_to_hdf5(attr)?);
1464 }
1465
1466 builder = builder.dataset(dataset);
1467 }
1468
1469 builder.into_plan().map_err(hdf5_error_to_unsupported)
1470 }
1471
1472 #[cfg(feature = "netcdf4")]
1473 fn nc4_dimension_sizes(&self) -> Result<Vec<u64>> {
1474 infer_nc4_dimension_sizes(self)
1475 }
1476
1477 #[cfg(feature = "netcdf4")]
1478 fn nc4_variable_max_shape(&self, variable: &VariableDef) -> Option<Vec<u64>> {
1479 variable
1480 .dim_ids
1481 .iter()
1482 .any(|id| self.dimensions[id.0].is_unlimited)
1483 .then(|| {
1484 variable
1485 .dim_ids
1486 .iter()
1487 .map(|id| {
1488 let dimension = &self.dimensions[id.0];
1489 if dimension.is_unlimited {
1490 H5_UNLIMITED
1491 } else {
1492 dimension.size
1493 }
1494 })
1495 .collect()
1496 })
1497 }
1498
1499 #[cfg(feature = "netcdf4")]
1500 fn variable_is_coordinate_scale(&self, variable: &VariableDef) -> Result<bool> {
1501 Ok(match variable.dim_ids.as_slice() {
1502 [dim_id] => self.dimension(*dim_id)?.name == variable.name,
1503 _ => false,
1504 })
1505 }
1506
1507 #[cfg(feature = "netcdf4")]
1508 fn coordinate_variable_for_dimension(&self, dimension: DimensionId) -> Option<&VariableDef> {
1509 let dim = self.dimensions.get(dimension.0)?;
1510 self.variables.iter().find(|variable| {
1511 variable.name == dim.name && variable.dim_ids.as_slice() == [dimension]
1512 })
1513 }
1514
1515 #[cfg(feature = "netcdf4")]
1521 fn reference_list_attribute(&self, dim_id: DimensionId) -> Result<Option<H5AttributeBuilder>> {
1522 let dim_name = &self.dimensions[dim_id.0].name;
1523 let mut entries = Vec::new();
1524 for variable in &self.variables {
1525 let is_scale_itself =
1526 variable.name == *dim_name && variable.dim_ids.as_slice() == [dim_id];
1527 if is_scale_itself {
1528 continue;
1529 }
1530 for (position, vid) in variable.dim_ids.iter().enumerate() {
1531 if *vid == dim_id {
1532 entries.push((variable.name.clone(), position as u32));
1533 }
1534 }
1535 }
1536 Ok((!entries.is_empty())
1537 .then(|| H5AttributeBuilder::object_reference_list("REFERENCE_LIST", entries)))
1538 }
1539
1540 #[cfg(feature = "netcdf4")]
1541 fn dimension_list_attribute(&self, variable: &VariableDef) -> Result<H5AttributeBuilder> {
1542 let target_sequences = variable
1543 .dim_ids
1544 .iter()
1545 .map(|dimension| Ok(vec![self.dimension(*dimension)?.name.clone()]))
1546 .collect::<Result<Vec<_>>>()?;
1547 Ok(H5AttributeBuilder::vlen_object_references(
1548 "DIMENSION_LIST",
1549 target_sequences,
1550 ))
1551 }
1552
1553 fn add_dimension_def(
1554 &mut self,
1555 name: String,
1556 size: u64,
1557 is_unlimited: bool,
1558 ) -> Result<DimensionId> {
1559 if !is_unlimited && size == 0 {
1560 return Err(Error::InvalidDefinition(
1561 "fixed dimensions must have non-zero size; use add_unlimited_dimension".into(),
1562 ));
1563 }
1564 self.ensure_unique_dimension(&name)?;
1565 let id = DimensionId(self.dimensions.len());
1566 self.dimensions.push(DimensionDef {
1567 name,
1568 size,
1569 is_unlimited,
1570 current_size: if is_unlimited { 0 } else { size },
1571 });
1572 Ok(id)
1573 }
1574
1575 fn add_variable_with_type(
1576 &mut self,
1577 name: impl Into<String>,
1578 dimensions: &[DimensionId],
1579 dtype: NcType,
1580 ) -> Result<VariableId> {
1581 let name = name.into();
1582 validate_name(&name, "variable")?;
1583 self.add_variable_def(name, dimensions, dtype)
1584 }
1585
1586 fn add_variable_path_with_type(
1587 &mut self,
1588 path: impl Into<String>,
1589 dimensions: &[DimensionId],
1590 dtype: NcType,
1591 ) -> Result<VariableId> {
1592 let path = validate_path_name(path.into(), "variable")?;
1593 self.add_variable_def(path, dimensions, dtype)
1594 }
1595
1596 fn add_variable_def(
1597 &mut self,
1598 name: String,
1599 dimensions: &[DimensionId],
1600 dtype: NcType,
1601 ) -> Result<VariableId> {
1602 if self.variables.iter().any(|v| v.name == name) {
1603 return Err(Error::InvalidDefinition(format!(
1604 "duplicate variable '{name}'"
1605 )));
1606 }
1607 for dim in dimensions {
1608 self.dimension(*dim)?;
1609 }
1610 let id = VariableId(self.variables.len());
1611 self.variables.push(VariableDef {
1612 name,
1613 dim_ids: dimensions.to_vec(),
1614 dtype,
1615 attributes: Vec::new(),
1616 data: Vec::new(),
1617 data_encoding: VariableDataEncoding::ClassicBigEndian,
1618 string_values: None,
1619 vlen_values: None,
1620 storage: VariableStorageDef::default(),
1621 fill_value: None,
1622 });
1623 Ok(id)
1624 }
1625
1626 fn dimension(&self, id: DimensionId) -> Result<&DimensionDef> {
1627 self.dimensions
1628 .get(id.0)
1629 .ok_or_else(|| Error::InvalidDefinition("invalid dimension handle".into()))
1630 }
1631
1632 fn variable(&self, id: VariableId) -> Result<&VariableDef> {
1633 self.variables
1634 .get(id.0)
1635 .ok_or_else(|| Error::InvalidDefinition("invalid variable handle".into()))
1636 }
1637
1638 fn variable_mut(&mut self, id: VariableId) -> Result<&mut VariableDef> {
1639 self.variables
1640 .get_mut(id.0)
1641 .ok_or_else(|| Error::InvalidDefinition("invalid variable handle".into()))
1642 }
1643
1644 fn resolve_variable_write_selection(
1645 &self,
1646 variable: &VariableDef,
1647 selection: &NcSliceInfo,
1648 elem_size: usize,
1649 ) -> Result<ResolvedWriteSelection> {
1650 let extents = self.variable_write_extents(variable, elem_size)?;
1651 resolve_write_selection(&variable.name, &extents, selection)
1652 }
1653
1654 fn resolve_char_string_write_selection(
1655 &self,
1656 variable: &VariableDef,
1657 selection: &NcSliceInfo,
1658 ) -> Result<ResolvedWriteSelection> {
1659 if variable.dim_ids.is_empty() {
1660 return Err(Error::InvalidDefinition(format!(
1661 "char variable '{}' must have a string-width dimension",
1662 variable.name
1663 )));
1664 }
1665 let extents = self.variable_write_extents(variable, 1)?;
1666 resolve_write_selection(&variable.name, &extents[..extents.len() - 1], selection)
1667 }
1668
1669 fn variable_write_extents(
1670 &self,
1671 variable: &VariableDef,
1672 _elem_size: usize,
1673 ) -> Result<Vec<WriteDimensionExtent>> {
1674 variable
1675 .dim_ids
1676 .iter()
1677 .enumerate()
1678 .map(|(position, id)| {
1679 let dimension = &self.dimensions[id.0];
1680 Ok(WriteDimensionExtent {
1681 current: if dimension.is_unlimited {
1682 dimension.current_size
1683 } else {
1684 dimension.size
1685 },
1686 is_unlimited: dimension.is_unlimited,
1687 position,
1688 })
1689 })
1690 .collect()
1691 }
1692
1693 fn char_string_axis_width(&self, variable: &VariableDef) -> Result<usize> {
1694 let width_dim_id = variable.dim_ids.last().ok_or_else(|| {
1695 Error::InvalidDefinition(format!(
1696 "char variable '{}' must have a string-width dimension",
1697 variable.name
1698 ))
1699 })?;
1700 let dimension = &self.dimensions[width_dim_id.0];
1701 let width = if dimension.is_unlimited {
1702 dimension.current_size
1703 } else {
1704 dimension.size
1705 };
1706 if dimension.is_unlimited && width == 0 {
1707 return Err(Error::InvalidDefinition(format!(
1708 "char variable '{}' string-width dimension cannot be inferred from string values",
1709 variable.name
1710 )));
1711 }
1712 require_usize(width, "char string width")
1713 }
1714
1715 fn validate_char_string_value_count(
1716 &self,
1717 variable: &VariableDef,
1718 value_count: usize,
1719 ) -> Result<Option<(DimensionId, u64)>> {
1720 if variable.dim_ids.is_empty() {
1721 return Err(Error::InvalidDefinition(format!(
1722 "char variable '{}' must have a string-width dimension",
1723 variable.name
1724 )));
1725 }
1726
1727 let value_count = u64::try_from(value_count).map_err(|_| {
1728 Error::InvalidDefinition("char string value count exceeds u64 capacity".into())
1729 })?;
1730 let mut known_product = 1u64;
1731 let mut unknown = Vec::new();
1732 for dim_id in &variable.dim_ids[..variable.dim_ids.len() - 1] {
1733 let dimension = &self.dimensions[dim_id.0];
1734 let size = if dimension.is_unlimited {
1735 dimension.current_size
1736 } else {
1737 dimension.size
1738 };
1739 if dimension.is_unlimited && size == 0 {
1740 if !unknown.contains(dim_id) {
1741 unknown.push(*dim_id);
1742 }
1743 continue;
1744 }
1745 known_product = checked_mul_u64(known_product, size, "char string value count shape")?;
1746 }
1747
1748 let mut inferred_unlimited = None;
1749 if unknown.is_empty() {
1750 if value_count != known_product {
1751 return Err(Error::DataLengthMismatch {
1752 expected: require_usize(known_product, "char string value count")?,
1753 actual: require_usize(value_count, "char string value count")?,
1754 });
1755 }
1756 } else if unknown.len() == 1 {
1757 if known_product == 0 {
1758 if value_count != 0 {
1759 return Err(Error::DataLengthMismatch {
1760 expected: 0,
1761 actual: require_usize(value_count, "char string value count")?,
1762 });
1763 }
1764 } else if value_count % known_product != 0 {
1765 return Err(Error::InvalidDefinition(format!(
1766 "char string value count {value_count} is not divisible by known leading dimension product {known_product}"
1767 )));
1768 } else {
1769 inferred_unlimited = Some((unknown[0], value_count / known_product));
1770 }
1771 } else {
1772 let names = unknown
1773 .iter()
1774 .map(|id| self.dimensions[id.0].name.as_str())
1775 .collect::<Vec<_>>()
1776 .join(", ");
1777 return Err(Error::InvalidDefinition(format!(
1778 "cannot infer multiple unlimited char string dimensions {names} for variable '{}'",
1779 variable.name
1780 )));
1781 }
1782
1783 Ok(inferred_unlimited)
1784 }
1785
1786 fn update_unlimited_extents_from_element_count(
1787 &mut self,
1788 variable: VariableId,
1789 elements: u64,
1790 ) -> Result<()> {
1791 let variable = self.variable(variable)?;
1792 let mut known_product = 1u64;
1793 let mut unknown = Vec::new();
1794 for dim_id in &variable.dim_ids {
1795 let dimension = &self.dimensions[dim_id.0];
1796 let size = if dimension.is_unlimited {
1797 dimension.current_size
1798 } else {
1799 dimension.size
1800 };
1801 if dimension.is_unlimited && size == 0 {
1802 if !unknown.contains(dim_id) {
1803 unknown.push(*dim_id);
1804 }
1805 continue;
1806 }
1807 known_product = match known_product.checked_mul(size) {
1808 Some(product) => product,
1809 None => return Ok(()),
1810 };
1811 }
1812
1813 if unknown.len() != 1 || known_product == 0 {
1814 return Ok(());
1815 }
1816 if elements % known_product != 0 {
1817 return Ok(());
1818 }
1819 self.dimensions[unknown[0].0].current_size = elements / known_product;
1820 Ok(())
1821 }
1822
1823 fn update_unlimited_extents_from_shape(
1824 &mut self,
1825 variable: VariableId,
1826 shape: &[u64],
1827 ) -> Result<()> {
1828 let dim_ids = self.variable(variable)?.dim_ids.clone();
1829 if dim_ids.len() != shape.len() {
1830 return Err(Error::InvalidDefinition(format!(
1831 "resolved shape rank {} does not match variable '{}' rank {}",
1832 shape.len(),
1833 self.variable(variable)?.name,
1834 dim_ids.len()
1835 )));
1836 }
1837 for (dim_id, &size) in dim_ids.iter().zip(shape) {
1838 let dimension = &mut self.dimensions[dim_id.0];
1839 if dimension.is_unlimited && size > dimension.current_size {
1840 dimension.current_size = size;
1841 }
1842 }
1843 Ok(())
1844 }
1845
1846 fn write_native_variable_slice_bytes(
1847 &mut self,
1848 variable: VariableId,
1849 selection: &NcSliceInfo,
1850 bytes: &[u8],
1851 elem_size: usize,
1852 ) -> Result<()> {
1853 let variable_def = self.variable(variable)?;
1854 let resolved = self.resolve_variable_write_selection(variable_def, selection, elem_size)?;
1855 let expected = checked_mul_usize(
1856 resolved.elements,
1857 elem_size,
1858 "native variable slice byte size",
1859 )?;
1860 if bytes.len() != expected {
1861 return Err(Error::DataLengthMismatch {
1862 expected,
1863 actual: bytes.len(),
1864 });
1865 }
1866
1867 let strides = row_major_strides(&resolved.shape, "writer native slice stride")?;
1868 let total_elements =
1869 checked_shape_elements(&resolved.shape, "writer native variable element count")?;
1870 {
1871 let variable_def = self.variable_mut(variable)?;
1872 ensure_variable_native_slice_buffer(
1873 variable_def,
1874 &resolved.old_shape,
1875 &resolved.shape,
1876 total_elements,
1877 elem_size,
1878 resolved.can_grow,
1879 )?;
1880 scatter_slice_bytes(
1881 &mut variable_def.data,
1882 elem_size,
1883 &resolved.dims,
1884 &strides,
1885 bytes,
1886 )?;
1887 }
1888 self.update_unlimited_extents_from_shape(variable, &resolved.shape)
1889 }
1890
1891 fn ensure_unique_dimension(&self, name: &str) -> Result<()> {
1892 if self.dimensions.iter().any(|d| d.name == name) {
1893 return Err(Error::InvalidDefinition(format!(
1894 "duplicate dimension '{name}'"
1895 )));
1896 }
1897 Ok(())
1898 }
1899
1900 fn select_format(&self, options: NcWriteOptions) -> Result<NcFormat> {
1901 match options.format {
1902 NcWriteFormat::Classic => {
1903 self.validate_for_format(NcFormat::Classic)?;
1904 Ok(NcFormat::Classic)
1905 }
1906 NcWriteFormat::Offset64 => {
1907 self.validate_for_format(NcFormat::Offset64)?;
1908 Ok(NcFormat::Offset64)
1909 }
1910 NcWriteFormat::Cdf5 => {
1911 self.validate_for_format(NcFormat::Cdf5)?;
1912 Ok(NcFormat::Cdf5)
1913 }
1914 NcWriteFormat::Nc4 => Ok(NcFormat::Nc4),
1915 NcWriteFormat::Nc4Classic => Ok(NcFormat::Nc4Classic),
1916 NcWriteFormat::AutoClassic => {
1917 let preferred = if self.requires_cdf5() {
1918 NcFormat::Cdf5
1919 } else {
1920 NcFormat::Classic
1921 };
1922 match self.validate_for_format(preferred) {
1923 Ok(()) => Ok(preferred),
1924 Err(_) if preferred == NcFormat::Classic => {
1925 self.validate_for_format(NcFormat::Offset64)?;
1926 Ok(NcFormat::Offset64)
1927 }
1928 Err(err) => Err(err),
1929 }
1930 }
1931 }
1932 }
1933
1934 fn requires_cdf5(&self) -> bool {
1935 self.dimensions.iter().any(|d| d.size > u32::MAX as u64)
1936 || self.variables.iter().any(|v| {
1937 matches!(
1938 v.dtype,
1939 NcType::UByte | NcType::UShort | NcType::UInt | NcType::Int64 | NcType::UInt64
1940 )
1941 })
1942 || self.attributes.iter().any(|a| attr_requires_cdf5(&a.value))
1943 || self
1944 .group_attributes
1945 .iter()
1946 .any(|a| attr_requires_cdf5(&a.attribute.value))
1947 || self
1948 .variables
1949 .iter()
1950 .flat_map(|v| &v.attributes)
1951 .any(|a| attr_requires_cdf5(&a.value))
1952 }
1953
1954 fn validate_for_format(&self, format: NcFormat) -> Result<()> {
1955 if !matches!(
1956 format,
1957 NcFormat::Classic | NcFormat::Offset64 | NcFormat::Cdf5
1958 ) {
1959 return Err(Error::UnsupportedFeature(
1960 "only classic-family NetCDF formats are implemented".into(),
1961 ));
1962 }
1963 let unlimited_count = self.dimensions.iter().filter(|d| d.is_unlimited).count();
1964 if unlimited_count > 1 {
1965 return Err(Error::RequiresNetcdf4 {
1966 reason: "classic NetCDF supports at most one unlimited dimension".into(),
1967 });
1968 }
1969 if format != NcFormat::Cdf5 && self.requires_cdf5() {
1970 return Err(Error::FormatCapacityExceeded {
1971 reason: "CDF-5 is required for unsigned integer, 64-bit integer, or 64-bit count \
1972 data"
1973 .into(),
1974 });
1975 }
1976 validate_root_only_names(self)?;
1977 for variable in &self.variables {
1978 if variable.storage.has_nc4_options() {
1979 return Err(Error::UnsupportedFeature(format!(
1980 "variable '{}' uses NetCDF-4 storage options",
1981 variable.name
1982 )));
1983 }
1984 validate_classic_type(&variable.dtype)?;
1985 let is_record = variable_is_record(self, variable)?;
1986 if variable
1987 .dim_ids
1988 .iter()
1989 .skip(1)
1990 .any(|id| self.dimensions[id.0].is_unlimited)
1991 {
1992 return Err(Error::InvalidDefinition(format!(
1993 "record dimension must be first for variable '{}'",
1994 variable.name
1995 )));
1996 }
1997 let expected = expected_variable_bytes(self, variable, is_record)?;
1998 if variable.data.len() != expected {
1999 return Err(Error::DataLengthMismatch {
2000 expected,
2001 actual: variable.data.len(),
2002 });
2003 }
2004 }
2005 Ok(())
2006 }
2007}
2008
2009#[derive(Debug, Clone)]
2010struct PlannedVar {
2011 def: VariableDef,
2012 is_record: bool,
2013 header_vsize: u64,
2016 layout_vsize: u64,
2021 begin: u64,
2022 fixed_data_len: u64,
2023 record_slab_len: u64,
2024}
2025
2026#[derive(Debug, Clone)]
2027struct ClassicWritePlan {
2028 num_records: u64,
2029 header: Vec<u8>,
2030 fixed_vars: Vec<PlannedVar>,
2031 record_vars: Vec<PlannedVar>,
2032}
2033
2034impl ClassicWritePlan {
2035 fn build(builder: &NcFileBuilder, format: NcFormat) -> Result<Self> {
2036 builder.validate_for_format(format)?;
2037 let num_records = infer_num_records(builder)?;
2038 let mut planned = plan_variables(builder, 0)?;
2039 let header_without_offsets = encode_header(builder, format, num_records, &planned)?;
2040 planned = plan_variables(builder, header_without_offsets.len() as u64)?;
2041 let header = encode_header(builder, format, num_records, &planned)?;
2042 let planned = plan_variables(builder, header.len() as u64)?;
2043 let header = encode_header(builder, format, num_records, &planned)?;
2044
2045 if format == NcFormat::Classic {
2046 for var in &planned {
2047 require_u32(var.begin, "CDF-1 variable offset")?;
2048 }
2049 }
2050 if matches!(format, NcFormat::Classic | NcFormat::Offset64) {
2051 require_u32(num_records, "record count")?;
2052 }
2053
2054 let fixed_vars = planned.iter().filter(|v| !v.is_record).cloned().collect();
2055 let record_vars = planned.iter().filter(|v| v.is_record).cloned().collect();
2056 Ok(Self {
2057 num_records,
2058 header,
2059 fixed_vars,
2060 record_vars,
2061 })
2062 }
2063
2064 fn write(&self, writer: &mut impl Write) -> Result<()> {
2065 writer.write_all(&self.header)?;
2066 for var in &self.fixed_vars {
2067 writer.write_all(&var.def.data)?;
2068 write_fill_padding(
2069 writer,
2070 var.layout_vsize - var.fixed_data_len,
2071 &variable_fill_pattern(&var.def)?,
2072 )?;
2073 }
2074 for record in 0..self.num_records {
2075 for var in &self.record_vars {
2076 let start = usize::try_from(record * var.record_slab_len).map_err(|_| {
2077 Error::InvalidDefinition("record byte offset exceeds platform usize".into())
2078 })?;
2079 let slab_len = usize::try_from(var.record_slab_len).map_err(|_| {
2080 Error::InvalidDefinition("record slab length exceeds platform usize".into())
2081 })?;
2082 writer.write_all(&var.def.data[start..start + slab_len])?;
2083 write_fill_padding(
2084 writer,
2085 var.layout_vsize - var.record_slab_len,
2086 &variable_fill_pattern(&var.def)?,
2087 )?;
2088 }
2089 }
2090 Ok(())
2091 }
2092}
2093
2094fn native_fill_pattern(def: &VariableDef, elem_size: usize) -> Result<Vec<u8>> {
2101 if let Some(fill_value) = &def.fill_value {
2102 return Ok(fill_value.hdf5_bytes.clone());
2103 }
2104 match default_classic_fill_bytes(&def.dtype) {
2105 Ok(classic) => convert_classic_be_data_to_hdf5_le(&def.dtype, &classic),
2106 Err(_) => Ok(vec![0; elem_size]),
2107 }
2108}
2109
2110fn variable_fill_pattern(def: &VariableDef) -> Result<Vec<u8>> {
2111 match &def.fill_value {
2112 Some(fill_value) => Ok(fill_value.classic_bytes.clone()),
2113 None => default_classic_fill_bytes(&def.dtype),
2114 }
2115}
2116
2117fn plan_variables(builder: &NcFileBuilder, data_start: u64) -> Result<Vec<PlannedVar>> {
2118 let mut fixed_offset = data_start;
2119 let mut planned = Vec::with_capacity(builder.variables.len());
2120 for def in &builder.variables {
2121 let is_record = variable_is_record(builder, def)?;
2122 let elem_size = def.dtype.size()? as u64;
2123 let record_slab_len = if is_record {
2124 variable_shape_elements(builder, def, 1)? * elem_size
2125 } else {
2126 0
2127 };
2128 let fixed_data_len = if is_record {
2129 0
2130 } else {
2131 variable_shape_elements(builder, def, 0)? * elem_size
2132 };
2133 let vsize = if is_record {
2134 record_slab_len
2135 } else {
2136 fixed_data_len
2137 };
2138 let header_vsize = pad4_u64(vsize)?;
2139 let begin = if is_record { 0 } else { fixed_offset };
2140 if !is_record {
2141 fixed_offset = checked_add(fixed_offset, header_vsize, "fixed data offset")?;
2142 }
2143 planned.push(PlannedVar {
2144 def: def.clone(),
2145 is_record,
2146 header_vsize,
2147 layout_vsize: header_vsize,
2148 begin,
2149 fixed_data_len,
2150 record_slab_len,
2151 });
2152 }
2153
2154 let record_var_count = planned.iter().filter(|var| var.is_record).count();
2157 if record_var_count == 1 {
2158 for var in &mut planned {
2159 if var.is_record {
2160 var.layout_vsize = var.record_slab_len;
2161 }
2162 }
2163 }
2164
2165 let record_data_start = fixed_offset;
2166 let mut record_offset = record_data_start;
2167 for var in &mut planned {
2168 if var.is_record {
2169 var.begin = record_offset;
2170 record_offset = checked_add(record_offset, var.layout_vsize, "record offset")?;
2171 }
2172 }
2173 Ok(planned)
2174}
2175
2176fn encode_header(
2177 builder: &NcFileBuilder,
2178 format: NcFormat,
2179 num_records: u64,
2180 planned: &[PlannedVar],
2181) -> Result<Vec<u8>> {
2182 let mut out = Vec::new();
2183 out.extend_from_slice(match format {
2184 NcFormat::Classic => b"CDF\x01",
2185 NcFormat::Offset64 => b"CDF\x02",
2186 NcFormat::Cdf5 => b"CDF\x05",
2187 _ => unreachable!("classic header only"),
2188 });
2189 write_count(&mut out, format, num_records)?;
2190 encode_dimensions(&mut out, builder, format)?;
2191 encode_attributes(&mut out, format, &builder.attributes)?;
2192 encode_variables(&mut out, builder, format, planned)?;
2193 Ok(out)
2194}
2195
2196fn encode_dimensions(out: &mut Vec<u8>, builder: &NcFileBuilder, format: NcFormat) -> Result<()> {
2197 if builder.dimensions.is_empty() {
2198 out.extend_from_slice(&ABSENT.to_be_bytes());
2199 write_count(out, format, 0)?;
2200 return Ok(());
2201 }
2202 out.extend_from_slice(&NC_DIMENSION.to_be_bytes());
2203 write_count(out, format, builder.dimensions.len() as u64)?;
2204 for dim in &builder.dimensions {
2205 write_name(out, format, &dim.name)?;
2206 write_count(out, format, if dim.is_unlimited { 0 } else { dim.size })?;
2207 }
2208 Ok(())
2209}
2210
2211fn encode_attributes(out: &mut Vec<u8>, format: NcFormat, attrs: &[NcAttribute]) -> Result<()> {
2212 if attrs.is_empty() {
2213 out.extend_from_slice(&ABSENT.to_be_bytes());
2214 write_count(out, format, 0)?;
2215 return Ok(());
2216 }
2217 out.extend_from_slice(&NC_ATTRIBUTE.to_be_bytes());
2218 write_count(out, format, attrs.len() as u64)?;
2219 for attr in attrs {
2220 write_name(out, format, &attr.name)?;
2221 let (dtype, count, bytes) = encode_attr_value(&attr.value)?;
2222 write_u32(
2223 out,
2224 dtype.classic_type_code().ok_or_else(|| {
2225 Error::InvalidDefinition(format!("{dtype:?} is not valid in classic attributes"))
2226 })?,
2227 );
2228 write_count(out, format, count)?;
2229 out.extend_from_slice(&bytes);
2230 pad_vec_to_4(out);
2231 }
2232 Ok(())
2233}
2234
2235fn encode_variables(
2236 out: &mut Vec<u8>,
2237 builder: &NcFileBuilder,
2238 format: NcFormat,
2239 planned: &[PlannedVar],
2240) -> Result<()> {
2241 if planned.is_empty() {
2242 out.extend_from_slice(&ABSENT.to_be_bytes());
2243 write_count(out, format, 0)?;
2244 return Ok(());
2245 }
2246 out.extend_from_slice(&NC_VARIABLE.to_be_bytes());
2247 write_count(out, format, planned.len() as u64)?;
2248 for var in planned {
2249 write_name(out, format, &var.def.name)?;
2250 write_count(out, format, var.def.dim_ids.len() as u64)?;
2251 for dim_id in &var.def.dim_ids {
2252 write_count(out, format, dim_id.0 as u64)?;
2253 }
2254 encode_attributes(out, format, &var.def.attributes)?;
2255 write_u32(
2256 out,
2257 var.def.dtype.classic_type_code().ok_or_else(|| {
2258 Error::InvalidDefinition(format!(
2259 "{:?} is not valid in classic variables",
2260 var.def.dtype
2261 ))
2262 })?,
2263 );
2264 let header_vsize = match format {
2268 NcFormat::Classic | NcFormat::Offset64 if var.header_vsize > u64::from(u32::MAX) => {
2269 u64::from(u32::MAX)
2270 }
2271 _ => var.header_vsize,
2272 };
2273 write_count(out, format, header_vsize)?;
2274 match format {
2275 NcFormat::Classic => write_u32(out, require_u32(var.begin, "CDF-1 begin")?),
2276 NcFormat::Offset64 | NcFormat::Cdf5 => write_u64(out, var.begin),
2277 _ => unreachable!("classic header only"),
2278 }
2279 let _ = builder;
2280 }
2281 Ok(())
2282}
2283
2284fn encode_attr_value(value: &NcAttrValue) -> Result<(NcType, u64, Vec<u8>)> {
2285 let mut out = Vec::new();
2286 let (dtype, count) = match value {
2287 NcAttrValue::Bytes(values) => {
2288 out.extend(values.iter().map(|v| *v as u8));
2289 (NcType::Byte, values.len() as u64)
2290 }
2291 NcAttrValue::Chars(value) => {
2292 out.extend_from_slice(value.as_bytes());
2293 (NcType::Char, value.len() as u64)
2294 }
2295 NcAttrValue::Shorts(values) => {
2296 for value in values {
2297 out.extend_from_slice(&value.to_be_bytes());
2298 }
2299 (NcType::Short, values.len() as u64)
2300 }
2301 NcAttrValue::Ints(values) => {
2302 for value in values {
2303 out.extend_from_slice(&value.to_be_bytes());
2304 }
2305 (NcType::Int, values.len() as u64)
2306 }
2307 NcAttrValue::Floats(values) => {
2308 for value in values {
2309 out.extend_from_slice(&value.to_be_bytes());
2310 }
2311 (NcType::Float, values.len() as u64)
2312 }
2313 NcAttrValue::Doubles(values) => {
2314 for value in values {
2315 out.extend_from_slice(&value.to_be_bytes());
2316 }
2317 (NcType::Double, values.len() as u64)
2318 }
2319 NcAttrValue::UBytes(values) => {
2320 out.extend_from_slice(values);
2321 (NcType::UByte, values.len() as u64)
2322 }
2323 NcAttrValue::UShorts(values) => {
2324 for value in values {
2325 out.extend_from_slice(&value.to_be_bytes());
2326 }
2327 (NcType::UShort, values.len() as u64)
2328 }
2329 NcAttrValue::UInts(values) => {
2330 for value in values {
2331 out.extend_from_slice(&value.to_be_bytes());
2332 }
2333 (NcType::UInt, values.len() as u64)
2334 }
2335 NcAttrValue::Int64s(values) => {
2336 for value in values {
2337 out.extend_from_slice(&value.to_be_bytes());
2338 }
2339 (NcType::Int64, values.len() as u64)
2340 }
2341 NcAttrValue::UInt64s(values) => {
2342 for value in values {
2343 out.extend_from_slice(&value.to_be_bytes());
2344 }
2345 (NcType::UInt64, values.len() as u64)
2346 }
2347 NcAttrValue::Strings(_) => {
2348 return Err(Error::UnsupportedFeature(
2349 "NC_STRING attributes require NetCDF-4".into(),
2350 ));
2351 }
2352 };
2353 Ok((dtype, count, out))
2354}
2355
2356#[cfg(feature = "netcdf4")]
2357fn nc_type_to_hdf5(dtype: &NcType) -> Result<H5Datatype> {
2358 let byte_order = H5ByteOrder::LittleEndian;
2359 match dtype {
2360 NcType::Byte => Ok(H5Datatype::FixedPoint {
2361 size: 1,
2362 signed: true,
2363 byte_order,
2364 }),
2365 NcType::UByte => Ok(H5Datatype::FixedPoint {
2366 size: 1,
2367 signed: false,
2368 byte_order,
2369 }),
2370 NcType::Short => Ok(H5Datatype::FixedPoint {
2371 size: 2,
2372 signed: true,
2373 byte_order,
2374 }),
2375 NcType::UShort => Ok(H5Datatype::FixedPoint {
2376 size: 2,
2377 signed: false,
2378 byte_order,
2379 }),
2380 NcType::Int => Ok(H5Datatype::FixedPoint {
2381 size: 4,
2382 signed: true,
2383 byte_order,
2384 }),
2385 NcType::UInt => Ok(H5Datatype::FixedPoint {
2386 size: 4,
2387 signed: false,
2388 byte_order,
2389 }),
2390 NcType::Int64 => Ok(H5Datatype::FixedPoint {
2391 size: 8,
2392 signed: true,
2393 byte_order,
2394 }),
2395 NcType::UInt64 => Ok(H5Datatype::FixedPoint {
2396 size: 8,
2397 signed: false,
2398 byte_order,
2399 }),
2400 NcType::Float => Ok(H5Datatype::FloatingPoint {
2401 size: 4,
2402 byte_order,
2403 }),
2404 NcType::Double => Ok(H5Datatype::FloatingPoint {
2405 size: 8,
2406 byte_order,
2407 }),
2408 NcType::Char => Ok(H5Datatype::String {
2409 size: H5StringSize::Fixed(1),
2410 encoding: H5StringEncoding::Ascii,
2411 padding: H5StringPadding::NullPad,
2412 }),
2413 NcType::String => Ok(H5Datatype::String {
2414 size: H5StringSize::Variable,
2415 encoding: H5StringEncoding::Utf8,
2416 padding: H5StringPadding::NullTerminate,
2417 }),
2418 NcType::Enum { base, members } => {
2419 let h5_base = nc_type_to_hdf5(base)?;
2420 let members = members
2421 .iter()
2422 .map(|member| {
2423 Ok(H5EnumMember {
2424 name: member.name.clone(),
2425 value: nc_enum_value_to_hdf5(base, member.value)?,
2426 })
2427 })
2428 .collect::<Result<Vec<_>>>()?;
2429 Ok(H5Datatype::Enum {
2430 base: Box::new(h5_base),
2431 members,
2432 })
2433 }
2434 NcType::Compound { size, fields } => {
2435 let fields = fields
2436 .iter()
2437 .map(|field| {
2438 let byte_offset = u32::try_from(field.offset).map_err(|_| {
2439 Error::UnsupportedFeature(format!(
2440 "compound field '{}' offset exceeds HDF5 u32 capacity",
2441 field.name
2442 ))
2443 })?;
2444 Ok(H5CompoundField {
2445 name: field.name.clone(),
2446 byte_offset,
2447 datatype: nc_type_to_hdf5(&field.dtype)?,
2448 })
2449 })
2450 .collect::<Result<Vec<_>>>()?;
2451 Ok(H5Datatype::Compound {
2452 size: *size,
2453 fields,
2454 })
2455 }
2456 NcType::Opaque { size, tag } => Ok(H5Datatype::Opaque {
2457 size: *size,
2458 tag: tag.clone(),
2459 }),
2460 NcType::Array { base, dims } => Ok(H5Datatype::Array {
2461 base: Box::new(nc_type_to_hdf5(base)?),
2462 dims: dims.clone(),
2463 }),
2464 NcType::VLen { base } => {
2465 validate_vlen_base_nc4_type(base)?;
2466 Ok(H5Datatype::VarLen {
2467 base: Box::new(nc_type_to_hdf5(base)?),
2468 kind: H5VarLenKind::Sequence,
2469 encoding: H5StringEncoding::Ascii,
2470 padding: H5StringPadding::NullTerminate,
2471 })
2472 }
2473 }
2474}
2475
2476#[cfg(feature = "netcdf4")]
2477fn nc_enum_value_to_hdf5(base: &NcType, value: NcIntegerValue) -> Result<Vec<u8>> {
2478 nc_enum_value_to_le_bytes(base, value)
2479}
2480
2481fn nc_enum_value_to_le_bytes(base: &NcType, value: NcIntegerValue) -> Result<Vec<u8>> {
2482 match (base, value) {
2483 (NcType::Byte, NcIntegerValue::I8(value)) => Ok(vec![value as u8]),
2484 (NcType::UByte, NcIntegerValue::U8(value)) => Ok(vec![value]),
2485 (NcType::Short, NcIntegerValue::I16(value)) => Ok(value.to_le_bytes().to_vec()),
2486 (NcType::UShort, NcIntegerValue::U16(value)) => Ok(value.to_le_bytes().to_vec()),
2487 (NcType::Int, NcIntegerValue::I32(value)) => Ok(value.to_le_bytes().to_vec()),
2488 (NcType::UInt, NcIntegerValue::U32(value)) => Ok(value.to_le_bytes().to_vec()),
2489 (NcType::Int64, NcIntegerValue::I64(value)) => Ok(value.to_le_bytes().to_vec()),
2490 (NcType::UInt64, NcIntegerValue::U64(value)) => Ok(value.to_le_bytes().to_vec()),
2491 (base, value) => Err(Error::InvalidDefinition(format!(
2492 "enum value {value:?} is incompatible with base type {base:?}"
2493 ))),
2494 }
2495}
2496
2497#[cfg(feature = "netcdf4")]
2498fn nc4_hdf5_element_size(dtype: &NcType) -> Result<usize> {
2499 match dtype {
2500 NcType::String | NcType::VLen { .. } => Ok(16),
2501 other => other.size().map_err(Error::Core),
2502 }
2503}
2504
2505fn convert_classic_be_data_to_hdf5_le(dtype: &NcType, data: &[u8]) -> Result<Vec<u8>> {
2506 let width = dtype.size()?;
2507 if width == 1 {
2508 return Ok(data.to_vec());
2509 }
2510 if data.len() % width != 0 {
2511 return Err(Error::InvalidDefinition(format!(
2512 "variable data length {} is not a multiple of element size {width}",
2513 data.len()
2514 )));
2515 }
2516
2517 match dtype {
2518 NcType::Short | NcType::UShort => Ok(data
2519 .chunks_exact(2)
2520 .flat_map(|chunk| [chunk[1], chunk[0]])
2521 .collect()),
2522 NcType::Int | NcType::UInt | NcType::Float => Ok(data
2523 .chunks_exact(4)
2524 .flat_map(|chunk| [chunk[3], chunk[2], chunk[1], chunk[0]])
2525 .collect()),
2526 NcType::Int64 | NcType::UInt64 | NcType::Double => Ok(data
2527 .chunks_exact(8)
2528 .flat_map(|chunk| {
2529 [
2530 chunk[7], chunk[6], chunk[5], chunk[4], chunk[3], chunk[2], chunk[1], chunk[0],
2531 ]
2532 })
2533 .collect()),
2534 other => Err(Error::UnsupportedFeature(format!(
2535 "NetCDF-4 data conversion is not implemented for {other:?}"
2536 ))),
2537 }
2538}
2539
2540#[cfg(feature = "netcdf4")]
2541fn nc_attr_to_hdf5(attribute: &NcAttribute) -> Result<H5AttributeBuilder> {
2542 match &attribute.value {
2543 NcAttrValue::Chars(value) => Ok(H5AttributeBuilder::fixed_string(&attribute.name, value)),
2544 NcAttrValue::Bytes(values) => hdf5_numeric_attr(&attribute.name, NcType::Byte, values),
2545 NcAttrValue::UBytes(values) => hdf5_numeric_attr(&attribute.name, NcType::UByte, values),
2546 NcAttrValue::Shorts(values) => hdf5_numeric_attr(&attribute.name, NcType::Short, values),
2547 NcAttrValue::UShorts(values) => hdf5_numeric_attr(&attribute.name, NcType::UShort, values),
2548 NcAttrValue::Ints(values) => hdf5_numeric_attr(&attribute.name, NcType::Int, values),
2549 NcAttrValue::UInts(values) => hdf5_numeric_attr(&attribute.name, NcType::UInt, values),
2550 NcAttrValue::Int64s(values) => hdf5_numeric_attr(&attribute.name, NcType::Int64, values),
2551 NcAttrValue::UInt64s(values) => hdf5_numeric_attr(&attribute.name, NcType::UInt64, values),
2552 NcAttrValue::Floats(values) => hdf5_numeric_attr(&attribute.name, NcType::Float, values),
2553 NcAttrValue::Doubles(values) => hdf5_numeric_attr(&attribute.name, NcType::Double, values),
2554 NcAttrValue::Strings(values) => H5AttributeBuilder::vlen_strings(&attribute.name, values)
2555 .map_err(hdf5_error_to_unsupported),
2556 }
2557}
2558
2559#[cfg(feature = "netcdf4")]
2560trait NcAttrElement {
2561 fn write_le(&self, dst: &mut Vec<u8>);
2562}
2563
2564#[cfg(feature = "netcdf4")]
2565macro_rules! impl_attr_element_bytes {
2566 ($ty:ty) => {
2567 impl NcAttrElement for $ty {
2568 fn write_le(&self, dst: &mut Vec<u8>) {
2569 dst.push(*self as u8);
2570 }
2571 }
2572 };
2573}
2574
2575#[cfg(feature = "netcdf4")]
2576macro_rules! impl_attr_element_le {
2577 ($ty:ty) => {
2578 impl NcAttrElement for $ty {
2579 fn write_le(&self, dst: &mut Vec<u8>) {
2580 dst.extend_from_slice(&self.to_le_bytes());
2581 }
2582 }
2583 };
2584}
2585
2586#[cfg(feature = "netcdf4")]
2587impl_attr_element_bytes!(i8);
2588#[cfg(feature = "netcdf4")]
2589impl_attr_element_bytes!(u8);
2590#[cfg(feature = "netcdf4")]
2591impl_attr_element_le!(i16);
2592#[cfg(feature = "netcdf4")]
2593impl_attr_element_le!(u16);
2594#[cfg(feature = "netcdf4")]
2595impl_attr_element_le!(i32);
2596#[cfg(feature = "netcdf4")]
2597impl_attr_element_le!(u32);
2598#[cfg(feature = "netcdf4")]
2599impl_attr_element_le!(i64);
2600#[cfg(feature = "netcdf4")]
2601impl_attr_element_le!(u64);
2602#[cfg(feature = "netcdf4")]
2603impl_attr_element_le!(f32);
2604#[cfg(feature = "netcdf4")]
2605impl_attr_element_le!(f64);
2606
2607#[cfg(feature = "netcdf4")]
2608fn hdf5_numeric_attr<T: NcAttrElement>(
2609 name: &str,
2610 dtype: NcType,
2611 values: &[T],
2612) -> Result<H5AttributeBuilder> {
2613 let mut raw = Vec::with_capacity(values.len() * dtype.size()?);
2614 for value in values {
2615 value.write_le(&mut raw);
2616 }
2617 Ok(H5AttributeBuilder::new(
2618 name,
2619 nc_type_to_hdf5(&dtype)?,
2620 vec![values.len() as u64],
2621 raw,
2622 ))
2623}
2624
2625#[cfg(feature = "netcdf4")]
2626fn empty_dimension_list_attribute() -> H5AttributeBuilder {
2627 H5AttributeBuilder::new(
2628 "DIMENSION_LIST",
2629 H5Datatype::VarLen {
2630 base: Box::new(H5Datatype::Reference {
2631 ref_type: H5ReferenceType::Object,
2632 size: 8,
2633 }),
2634 kind: H5VarLenKind::Sequence,
2635 encoding: H5StringEncoding::Ascii,
2636 padding: H5StringPadding::NullTerminate,
2637 },
2638 vec![0],
2639 Vec::new(),
2640 )
2641}
2642
2643#[cfg(feature = "netcdf4")]
2644fn hdf5_error_to_unsupported(err: hdf5_writer::Error) -> Error {
2645 Error::UnsupportedFeature(format!("HDF5 writer error: {err}"))
2646}
2647
2648#[cfg(feature = "netcdf4")]
2649fn checked_usize(value: u64, context: &str) -> Result<usize> {
2650 usize::try_from(value).map_err(|_| {
2651 Error::InvalidDefinition(format!(
2652 "{context} value {value} exceeds platform usize capacity"
2653 ))
2654 })
2655}
2656
2657fn expected_variable_bytes(
2658 builder: &NcFileBuilder,
2659 variable: &VariableDef,
2660 is_record: bool,
2661) -> Result<usize> {
2662 let elements = if is_record {
2663 let records = infer_num_records_for_var(builder, variable)?;
2664 records
2665 .checked_mul(variable_shape_elements(builder, variable, 1)?)
2666 .ok_or_else(|| Error::InvalidDefinition("variable element count overflow".into()))?
2667 } else {
2668 variable_shape_elements(builder, variable, 0)?
2669 };
2670 let bytes = elements
2671 .checked_mul(variable.dtype.size()? as u64)
2672 .ok_or_else(|| Error::InvalidDefinition("variable byte size overflow".into()))?;
2673 usize::try_from(bytes)
2674 .map_err(|_| Error::InvalidDefinition("variable byte size exceeds platform usize".into()))
2675}
2676
2677#[derive(Debug, Clone)]
2678struct ResolvedWriteSelection {
2679 dims: Vec<ResolvedWriteSelectionDim>,
2680 elements: usize,
2681 old_shape: Vec<u64>,
2682 shape: Vec<u64>,
2683 can_grow: bool,
2684}
2685
2686#[derive(Debug, Clone)]
2687enum ResolvedWriteSelectionDim {
2688 Index(u64),
2689 Slice { start: u64, step: u64, count: usize },
2690}
2691
2692#[derive(Debug, Clone, Copy)]
2693struct WriteDimensionExtent {
2694 current: u64,
2695 is_unlimited: bool,
2696 position: usize,
2697}
2698
2699fn resolve_write_selection(
2700 variable_name: &str,
2701 extents: &[WriteDimensionExtent],
2702 selection: &NcSliceInfo,
2703) -> Result<ResolvedWriteSelection> {
2704 if selection.selections.len() != extents.len() {
2705 return Err(Error::InvalidDefinition(format!(
2706 "selection has {} dimensions but variable '{}' has {}",
2707 selection.selections.len(),
2708 variable_name,
2709 extents.len()
2710 )));
2711 }
2712
2713 let mut dims = Vec::with_capacity(extents.len());
2714 let mut old_shape = Vec::with_capacity(extents.len());
2715 let mut shape = Vec::with_capacity(extents.len());
2716 let mut elements = 1usize;
2717 let mut can_grow = false;
2718 for (selection, extent) in selection.selections.iter().zip(extents) {
2719 let dim_size = extent.current;
2720 old_shape.push(dim_size);
2721 let mut target_dim_size = dim_size;
2722 match selection {
2723 NcSliceInfoElem::Index(index) => {
2724 if extent.is_unlimited {
2725 target_dim_size = checked_add(*index, 1, "unlimited slice extent")?;
2726 } else if *index >= dim_size {
2727 return Err(Error::InvalidDefinition(format!(
2728 "index {index} out of bounds for dimension {} (size {dim_size})",
2729 extent.position
2730 )));
2731 }
2732 dims.push(ResolvedWriteSelectionDim::Index(*index));
2733 }
2734 NcSliceInfoElem::Slice { start, end, step } => {
2735 if *step == 0 {
2736 return Err(Error::InvalidDefinition("slice step cannot be 0".into()));
2737 }
2738 if !extent.is_unlimited && *start > dim_size {
2739 return Err(Error::InvalidDefinition(format!(
2740 "slice start {start} out of bounds for dimension {} (size {dim_size})",
2741 extent.position
2742 )));
2743 }
2744 let actual_end = if extent.is_unlimited {
2745 if *end == u64::MAX {
2746 dim_size
2747 } else {
2748 *end
2749 }
2750 } else if *end == u64::MAX {
2751 dim_size
2752 } else {
2753 (*end).min(dim_size)
2754 };
2755 if extent.is_unlimited && *start > actual_end {
2756 return Err(Error::InvalidDefinition(format!(
2757 "slice start {start} exceeds end {actual_end} for unlimited dimension {}",
2758 extent.position
2759 )));
2760 }
2761 let count_u64 = if *start >= actual_end {
2762 0
2763 } else {
2764 (actual_end - *start).div_ceil(*step)
2765 };
2766 let count = require_usize(count_u64, "slice result dimension")?;
2767 if extent.is_unlimited && count > 0 {
2768 let last_index = checked_add(
2769 *start,
2770 checked_mul_u64((count - 1) as u64, *step, "slice coordinate step")?,
2771 "slice coordinate",
2772 )?;
2773 target_dim_size = checked_add(last_index, 1, "unlimited slice extent")?;
2774 }
2775 elements = elements.checked_mul(count).ok_or_else(|| {
2776 Error::InvalidDefinition(
2777 "slice result element count exceeds platform usize".into(),
2778 )
2779 })?;
2780 dims.push(ResolvedWriteSelectionDim::Slice {
2781 start: *start,
2782 step: *step,
2783 count,
2784 });
2785 }
2786 }
2787 if extent.is_unlimited && target_dim_size > dim_size {
2788 can_grow = true;
2789 }
2790 shape.push(target_dim_size.max(dim_size));
2791 }
2792
2793 Ok(ResolvedWriteSelection {
2794 dims,
2795 elements,
2796 old_shape,
2797 shape,
2798 can_grow,
2799 })
2800}
2801
2802fn ensure_variable_slice_buffer(
2803 variable: &mut VariableDef,
2804 old_shape: &[u64],
2805 new_shape: &[u64],
2806 total_elements: u64,
2807 elem_size: usize,
2808 can_grow: bool,
2809) -> Result<()> {
2810 ensure_variable_byte_slice_buffer(
2811 variable,
2812 old_shape,
2813 new_shape,
2814 total_elements,
2815 elem_size,
2816 can_grow,
2817 VariableDataEncoding::ClassicBigEndian,
2818 )
2819}
2820
2821fn ensure_variable_native_slice_buffer(
2822 variable: &mut VariableDef,
2823 old_shape: &[u64],
2824 new_shape: &[u64],
2825 total_elements: u64,
2826 elem_size: usize,
2827 can_grow: bool,
2828) -> Result<()> {
2829 ensure_variable_byte_slice_buffer(
2830 variable,
2831 old_shape,
2832 new_shape,
2833 total_elements,
2834 elem_size,
2835 can_grow,
2836 VariableDataEncoding::Hdf5Native,
2837 )
2838}
2839
2840fn ensure_variable_byte_slice_buffer(
2845 variable: &mut VariableDef,
2846 old_shape: &[u64],
2847 new_shape: &[u64],
2848 total_elements: u64,
2849 elem_size: usize,
2850 can_grow: bool,
2851 encoding: VariableDataEncoding,
2852) -> Result<()> {
2853 if variable.data_encoding != encoding && !variable.data.is_empty() {
2854 return Err(Error::UnsupportedFeature(format!(
2855 "slice writes cannot change the payload encoding of variable '{}'",
2856 variable.name
2857 )));
2858 }
2859
2860 let expected = checked_mul_usize(
2861 require_usize(total_elements, "variable element count")?,
2862 elem_size,
2863 "variable byte size",
2864 )?;
2865 let fill_pattern = match encoding {
2866 VariableDataEncoding::Hdf5Native => native_fill_pattern(variable, elem_size)?,
2867 VariableDataEncoding::ClassicBigEndian => match &variable.fill_value {
2868 Some(fill_value) => fill_value.classic_bytes.clone(),
2869 None => default_classic_fill_bytes(&variable.dtype)?,
2870 },
2871 };
2872 if variable.data.is_empty() {
2873 variable.data = filled_data_buffer(expected, elem_size, &fill_pattern)?;
2874 } else if variable.data.len() < expected && can_grow {
2875 resize_variable_data(
2876 &mut variable.data,
2877 old_shape,
2878 new_shape,
2879 elem_size,
2880 &fill_pattern,
2881 )?;
2882 } else if variable.data.len() != expected {
2883 return Err(Error::DataLengthMismatch {
2884 expected,
2885 actual: variable.data.len(),
2886 });
2887 }
2888
2889 variable.data_encoding = encoding;
2890 variable.string_values = None;
2891 variable.vlen_values = None;
2892 Ok(())
2893}
2894
2895fn encode_char_string_values<S: AsRef<str>>(
2896 variable_name: &str,
2897 width: usize,
2898 values: &[S],
2899) -> Result<Vec<u8>> {
2900 let mut encoded = Vec::with_capacity(checked_mul_usize(
2901 values.len(),
2902 width,
2903 "char string byte count",
2904 )?);
2905 for value in values {
2906 let value = value.as_ref();
2907 let bytes = value.as_bytes();
2908 if bytes.contains(&0) {
2909 return Err(Error::InvalidDefinition(format!(
2910 "char variable '{variable_name}' string values cannot contain NUL bytes"
2911 )));
2912 }
2913 if bytes.len() > width {
2914 return Err(Error::InvalidDefinition(format!(
2915 "char variable '{variable_name}' string value is {} bytes, exceeding fixed width {width}",
2916 bytes.len()
2917 )));
2918 }
2919 encoded.extend_from_slice(bytes);
2920 encoded.resize(encoded.len() + (width - bytes.len()), 0);
2921 }
2922 Ok(encoded)
2923}
2924
2925fn ensure_variable_string_slice_buffer(
2926 variable: &mut VariableDef,
2927 old_shape: &[u64],
2928 new_shape: &[u64],
2929 total_elements: u64,
2930 can_grow: bool,
2931) -> Result<()> {
2932 if variable.dtype != NcType::String {
2933 return Err(Error::TypeMismatch {
2934 expected: "String".into(),
2935 actual: format!("{:?}", variable.dtype),
2936 });
2937 }
2938 let expected = require_usize(total_elements, "string variable element count")?;
2939 let values = variable.string_values.get_or_insert_with(Vec::new);
2940 if values.is_empty() {
2941 values.resize(expected, String::new());
2942 } else if values.len() < expected && can_grow {
2943 resize_variable_values(values, old_shape, new_shape, String::new())?;
2944 } else if values.len() != expected {
2945 return Err(Error::DataLengthMismatch {
2946 expected,
2947 actual: values.len(),
2948 });
2949 }
2950
2951 variable.data.clear();
2952 variable.data_encoding = VariableDataEncoding::Hdf5Native;
2953 variable.vlen_values = None;
2954 Ok(())
2955}
2956
2957fn ensure_variable_vlen_slice_buffer(
2958 variable: &mut VariableDef,
2959 old_shape: &[u64],
2960 new_shape: &[u64],
2961 total_elements: u64,
2962 can_grow: bool,
2963) -> Result<()> {
2964 if !matches!(&variable.dtype, NcType::VLen { .. }) {
2965 return Err(Error::TypeMismatch {
2966 expected: "VLen".into(),
2967 actual: format!("{:?}", variable.dtype),
2968 });
2969 }
2970 let expected = require_usize(total_elements, "vlen variable element count")?;
2971 let values = variable.vlen_values.get_or_insert_with(Vec::new);
2972 if values.is_empty() {
2973 values.resize(expected, Vec::new());
2974 } else if values.len() < expected && can_grow {
2975 resize_variable_values(values, old_shape, new_shape, Vec::new())?;
2976 } else if values.len() != expected {
2977 return Err(Error::DataLengthMismatch {
2978 expected,
2979 actual: values.len(),
2980 });
2981 }
2982
2983 variable.data.clear();
2984 variable.data_encoding = VariableDataEncoding::Hdf5Native;
2985 variable.string_values = None;
2986 Ok(())
2987}
2988
2989fn resize_variable_data(
2990 data: &mut Vec<u8>,
2991 old_shape: &[u64],
2992 new_shape: &[u64],
2993 elem_size: usize,
2994 fill_pattern: &[u8],
2995) -> Result<()> {
2996 if old_shape.len() != new_shape.len() {
2997 return Err(Error::InvalidDefinition(format!(
2998 "cannot resize variable data from rank {} to rank {}",
2999 old_shape.len(),
3000 new_shape.len()
3001 )));
3002 }
3003 let old_elements = checked_shape_elements(old_shape, "old variable shape element count")?;
3004 let old_expected = checked_mul_usize(
3005 require_usize(old_elements, "old variable element count")?,
3006 elem_size,
3007 "old variable byte size",
3008 )?;
3009 if data.len() != old_expected {
3010 return Err(Error::DataLengthMismatch {
3011 expected: old_expected,
3012 actual: data.len(),
3013 });
3014 }
3015
3016 let new_elements = checked_shape_elements(new_shape, "new variable shape element count")?;
3017 let new_len = checked_mul_usize(
3018 require_usize(new_elements, "new variable element count")?,
3019 elem_size,
3020 "new variable byte size",
3021 )?;
3022 if old_shape.get(1..) == new_shape.get(1..) {
3023 let additional = new_len.checked_sub(data.len()).ok_or_else(|| {
3024 Error::InvalidDefinition("new variable byte size is smaller than old size".into())
3025 })?;
3026 data.extend(filled_data_buffer(additional, elem_size, fill_pattern)?);
3027 return Ok(());
3028 }
3029
3030 let old = std::mem::take(data);
3031 let mut resized = filled_data_buffer(new_len, elem_size, fill_pattern)?;
3032 copy_reshaped_variable_data(&old, &mut resized, old_shape, new_shape, elem_size)?;
3033 *data = resized;
3034 Ok(())
3035}
3036
3037fn resize_variable_values<T: Clone>(
3038 values: &mut Vec<T>,
3039 old_shape: &[u64],
3040 new_shape: &[u64],
3041 fill_value: T,
3042) -> Result<()> {
3043 if old_shape.len() != new_shape.len() {
3044 return Err(Error::InvalidDefinition(format!(
3045 "cannot resize variable values from rank {} to rank {}",
3046 old_shape.len(),
3047 new_shape.len()
3048 )));
3049 }
3050 let old_elements = require_usize(
3051 checked_shape_elements(old_shape, "old variable value shape element count")?,
3052 "old variable value element count",
3053 )?;
3054 if values.len() != old_elements {
3055 return Err(Error::DataLengthMismatch {
3056 expected: old_elements,
3057 actual: values.len(),
3058 });
3059 }
3060
3061 let new_len = require_usize(
3062 checked_shape_elements(new_shape, "new variable value shape element count")?,
3063 "new variable value element count",
3064 )?;
3065 if old_shape.get(1..) == new_shape.get(1..) {
3066 values.resize(new_len, fill_value);
3067 return Ok(());
3068 }
3069
3070 let old = std::mem::take(values);
3071 let mut resized = vec![fill_value; new_len];
3072 copy_reshaped_values(&old, &mut resized, old_shape, new_shape)?;
3073 *values = resized;
3074 Ok(())
3075}
3076
3077fn copy_reshaped_variable_data(
3078 old: &[u8],
3079 new: &mut [u8],
3080 old_shape: &[u64],
3081 new_shape: &[u64],
3082 elem_size: usize,
3083) -> Result<()> {
3084 let old_elements = checked_shape_elements(old_shape, "old variable copy element count")?;
3085 if old_elements == 0 {
3086 return Ok(());
3087 }
3088 if old_shape.is_empty() {
3089 if old.len() != elem_size || new.len() != elem_size {
3090 return Err(Error::DataLengthMismatch {
3091 expected: elem_size,
3092 actual: old.len().max(new.len()),
3093 });
3094 }
3095 new.copy_from_slice(old);
3096 return Ok(());
3097 }
3098
3099 let old_strides = row_major_strides(old_shape, "old variable copy stride")?;
3100 let new_strides = row_major_strides(new_shape, "new variable copy stride")?;
3101 let rank = old_shape.len();
3102 let mut coords = vec![0u64; rank];
3103 for old_index in 0..old_elements {
3104 let mut remainder = old_index;
3105 for dim in 0..rank {
3106 let stride = old_strides[dim];
3107 coords[dim] = remainder / stride;
3108 remainder %= stride;
3109 }
3110 if coords
3111 .iter()
3112 .zip(new_shape)
3113 .any(|(&coord, &extent)| coord >= extent)
3114 {
3115 continue;
3116 }
3117 let new_index =
3118 coords
3119 .iter()
3120 .zip(&new_strides)
3121 .try_fold(0u64, |acc, (&coord, &stride)| {
3122 checked_add(
3123 acc,
3124 checked_mul_u64(coord, stride, "new variable copy coordinate")?,
3125 "new variable copy element index",
3126 )
3127 })?;
3128 let old_start = checked_mul_usize(
3129 require_usize(old_index, "old variable copy element")?,
3130 elem_size,
3131 "old variable copy byte offset",
3132 )?;
3133 let new_start = checked_mul_usize(
3134 require_usize(new_index, "new variable copy element")?,
3135 elem_size,
3136 "new variable copy byte offset",
3137 )?;
3138 let old_end = old_start.checked_add(elem_size).ok_or_else(|| {
3139 Error::InvalidDefinition("old variable copy byte range exceeds usize".into())
3140 })?;
3141 let new_end = new_start.checked_add(elem_size).ok_or_else(|| {
3142 Error::InvalidDefinition("new variable copy byte range exceeds usize".into())
3143 })?;
3144 new[new_start..new_end].copy_from_slice(&old[old_start..old_end]);
3145 }
3146 Ok(())
3147}
3148
3149fn copy_reshaped_values<T: Clone>(
3150 old: &[T],
3151 new: &mut [T],
3152 old_shape: &[u64],
3153 new_shape: &[u64],
3154) -> Result<()> {
3155 let old_elements = checked_shape_elements(old_shape, "old variable value copy element count")?;
3156 if old_elements == 0 {
3157 return Ok(());
3158 }
3159 if old_shape.is_empty() {
3160 if old.len() != 1 || new.len() != 1 {
3161 return Err(Error::DataLengthMismatch {
3162 expected: 1,
3163 actual: old.len().max(new.len()),
3164 });
3165 }
3166 new[0] = old[0].clone();
3167 return Ok(());
3168 }
3169
3170 let old_strides = row_major_strides(old_shape, "old variable value copy stride")?;
3171 let new_strides = row_major_strides(new_shape, "new variable value copy stride")?;
3172 let rank = old_shape.len();
3173 let mut coords = vec![0u64; rank];
3174 for old_index in 0..old_elements {
3175 let mut remainder = old_index;
3176 for dim in 0..rank {
3177 let stride = old_strides[dim];
3178 coords[dim] = remainder / stride;
3179 remainder %= stride;
3180 }
3181 if coords
3182 .iter()
3183 .zip(new_shape)
3184 .any(|(&coord, &extent)| coord >= extent)
3185 {
3186 continue;
3187 }
3188 let new_index =
3189 coords
3190 .iter()
3191 .zip(&new_strides)
3192 .try_fold(0u64, |acc, (&coord, &stride)| {
3193 checked_add(
3194 acc,
3195 checked_mul_u64(coord, stride, "new variable value copy coordinate")?,
3196 "new variable value copy element index",
3197 )
3198 })?;
3199 let old_index = require_usize(old_index, "old variable value copy element")?;
3200 let new_index = require_usize(new_index, "new variable value copy element")?;
3201 new[new_index] = old[old_index].clone();
3202 }
3203 Ok(())
3204}
3205
3206fn filled_data_buffer(len: usize, elem_size: usize, fill_pattern: &[u8]) -> Result<Vec<u8>> {
3207 if elem_size == 0 {
3208 return if len == 0 {
3209 Ok(Vec::new())
3210 } else {
3211 Err(Error::InvalidDefinition(
3212 "non-empty variable data requires a non-zero element size".into(),
3213 ))
3214 };
3215 }
3216 if len % elem_size != 0 {
3217 return Err(Error::InvalidDefinition(format!(
3218 "variable byte length {len} is not a multiple of element size {elem_size}"
3219 )));
3220 }
3221 if fill_pattern.len() != elem_size {
3222 return Err(Error::InvalidDefinition(format!(
3223 "fill value byte length must match datatype element size: expected {elem_size}, got {}",
3224 fill_pattern.len()
3225 )));
3226 }
3227 let mut data = Vec::with_capacity(len);
3228 for _ in 0..len / elem_size {
3229 data.extend_from_slice(fill_pattern);
3230 }
3231 Ok(data)
3232}
3233
3234fn default_classic_fill_bytes(dtype: &NcType) -> Result<Vec<u8>> {
3235 match dtype {
3236 NcType::Byte => Ok(vec![NC_FILL_BYTE as u8]),
3237 NcType::Char => Ok(vec![NC_FILL_CHAR]),
3238 NcType::Short => Ok(NC_FILL_SHORT.to_be_bytes().to_vec()),
3239 NcType::Int => Ok(NC_FILL_INT.to_be_bytes().to_vec()),
3240 NcType::Float => Ok(NC_FILL_FLOAT.to_be_bytes().to_vec()),
3241 NcType::Double => Ok(NC_FILL_DOUBLE.to_be_bytes().to_vec()),
3242 NcType::UByte => Ok(vec![NC_FILL_UBYTE]),
3243 NcType::UShort => Ok(NC_FILL_USHORT.to_be_bytes().to_vec()),
3244 NcType::UInt => Ok(NC_FILL_UINT.to_be_bytes().to_vec()),
3245 NcType::Int64 => Ok(NC_FILL_INT64.to_be_bytes().to_vec()),
3246 NcType::UInt64 => Ok(NC_FILL_UINT64.to_be_bytes().to_vec()),
3247 other => Err(Error::UnsupportedFeature(format!(
3248 "{other:?} does not have a primitive NetCDF default fill value"
3249 ))),
3250 }
3251}
3252
3253fn scatter_slice_bytes(
3254 data: &mut [u8],
3255 elem_size: usize,
3256 dims: &[ResolvedWriteSelectionDim],
3257 strides: &[u64],
3258 encoded: &[u8],
3259) -> Result<()> {
3260 let mut src_elem = 0usize;
3261 scatter_slice_bytes_recursive(data, elem_size, dims, strides, encoded, 0, 0, &mut src_elem)?;
3262 let expected = encoded.len() / elem_size.max(1);
3263 if src_elem != expected {
3264 return Err(Error::InvalidDefinition(format!(
3265 "slice scatter wrote {src_elem} elements but expected {expected}"
3266 )));
3267 }
3268 Ok(())
3269}
3270
3271fn scatter_slice_values<T: Clone>(
3272 data: &mut [T],
3273 dims: &[ResolvedWriteSelectionDim],
3274 strides: &[u64],
3275 values: &[T],
3276) -> Result<()> {
3277 let mut src_elem = 0usize;
3278 scatter_slice_values_recursive(data, dims, strides, values, 0, 0, &mut src_elem)?;
3279 if src_elem != values.len() {
3280 return Err(Error::InvalidDefinition(format!(
3281 "slice scatter wrote {src_elem} elements but expected {}",
3282 values.len()
3283 )));
3284 }
3285 Ok(())
3286}
3287
3288fn scatter_slice_values_recursive<T: Clone>(
3289 data: &mut [T],
3290 dims: &[ResolvedWriteSelectionDim],
3291 strides: &[u64],
3292 values: &[T],
3293 dim: usize,
3294 dst_elem: u64,
3295 src_elem: &mut usize,
3296) -> Result<()> {
3297 if dim == dims.len() {
3298 let dst = require_usize(dst_elem, "slice destination element")?;
3299 if dst >= data.len() || *src_elem >= values.len() {
3300 return Err(Error::InvalidDefinition(
3301 "slice write exceeded planned value bounds".into(),
3302 ));
3303 }
3304 data[dst] = values[*src_elem].clone();
3305 *src_elem += 1;
3306 return Ok(());
3307 }
3308
3309 match dims[dim] {
3310 ResolvedWriteSelectionDim::Index(index) => {
3311 let next = checked_add(
3312 dst_elem,
3313 checked_mul_u64(index, strides[dim], "slice index stride")?,
3314 "slice destination element",
3315 )?;
3316 scatter_slice_values_recursive(data, dims, strides, values, dim + 1, next, src_elem)
3317 }
3318 ResolvedWriteSelectionDim::Slice { start, step, count } => {
3319 for offset in 0..count {
3320 let coord = checked_add(
3321 start,
3322 checked_mul_u64(offset as u64, step, "slice coordinate step")?,
3323 "slice coordinate",
3324 )?;
3325 let next = checked_add(
3326 dst_elem,
3327 checked_mul_u64(coord, strides[dim], "slice coordinate stride")?,
3328 "slice destination element",
3329 )?;
3330 scatter_slice_values_recursive(
3331 data,
3332 dims,
3333 strides,
3334 values,
3335 dim + 1,
3336 next,
3337 src_elem,
3338 )?;
3339 }
3340 Ok(())
3341 }
3342 }
3343}
3344
3345#[allow(clippy::too_many_arguments)]
3346fn scatter_slice_bytes_recursive(
3347 data: &mut [u8],
3348 elem_size: usize,
3349 dims: &[ResolvedWriteSelectionDim],
3350 strides: &[u64],
3351 encoded: &[u8],
3352 dim: usize,
3353 dst_elem: u64,
3354 src_elem: &mut usize,
3355) -> Result<()> {
3356 if dim == dims.len() {
3357 let dst_start = checked_mul_usize(
3358 require_usize(dst_elem, "slice destination element")?,
3359 elem_size,
3360 "slice destination byte offset",
3361 )?;
3362 let dst_end = dst_start.checked_add(elem_size).ok_or_else(|| {
3363 Error::InvalidDefinition("slice destination byte range exceeds usize".into())
3364 })?;
3365 let src_start = checked_mul_usize(*src_elem, elem_size, "slice source byte offset")?;
3366 let src_end = src_start.checked_add(elem_size).ok_or_else(|| {
3367 Error::InvalidDefinition("slice source byte range exceeds usize".into())
3368 })?;
3369 if dst_end > data.len() || src_end > encoded.len() {
3370 return Err(Error::InvalidDefinition(
3371 "slice write exceeded planned data bounds".into(),
3372 ));
3373 }
3374 data[dst_start..dst_end].copy_from_slice(&encoded[src_start..src_end]);
3375 *src_elem += 1;
3376 return Ok(());
3377 }
3378
3379 match dims[dim] {
3380 ResolvedWriteSelectionDim::Index(index) => {
3381 let offset = checked_add(
3382 dst_elem,
3383 checked_mul_u64(index, strides[dim], "slice index stride")?,
3384 "slice destination element",
3385 )?;
3386 scatter_slice_bytes_recursive(
3387 data,
3388 elem_size,
3389 dims,
3390 strides,
3391 encoded,
3392 dim + 1,
3393 offset,
3394 src_elem,
3395 )
3396 }
3397 ResolvedWriteSelectionDim::Slice { start, step, count } => {
3398 for i in 0..count {
3399 let coord = checked_add(
3400 start,
3401 checked_mul_u64(i as u64, step, "slice coordinate step")?,
3402 "slice coordinate",
3403 )?;
3404 let offset = checked_add(
3405 dst_elem,
3406 checked_mul_u64(coord, strides[dim], "slice coordinate stride")?,
3407 "slice destination element",
3408 )?;
3409 scatter_slice_bytes_recursive(
3410 data,
3411 elem_size,
3412 dims,
3413 strides,
3414 encoded,
3415 dim + 1,
3416 offset,
3417 src_elem,
3418 )?;
3419 }
3420 Ok(())
3421 }
3422 }
3423}
3424
3425fn row_major_strides(shape: &[u64], context: &str) -> Result<Vec<u64>> {
3426 if shape.is_empty() {
3427 return Ok(Vec::new());
3428 }
3429 let mut strides = vec![1u64; shape.len()];
3430 for dim in (0..shape.len() - 1).rev() {
3431 strides[dim] = checked_mul_u64(strides[dim + 1], shape[dim + 1], context)?;
3432 }
3433 Ok(strides)
3434}
3435
3436fn checked_shape_elements(shape: &[u64], context: &str) -> Result<u64> {
3437 shape
3438 .iter()
3439 .try_fold(1u64, |acc, &dim| checked_mul_u64(acc, dim, context))
3440}
3441
3442#[cfg(feature = "netcdf4")]
3443fn validate_nc4_variable_data(variable: &VariableDef, dimension_sizes: &[u64]) -> Result<()> {
3444 let elements = nc4_variable_elements(variable, dimension_sizes)?;
3445 validate_nc4_variable_storage(variable)?;
3446 validate_nc4_variable_fill_value(variable)?;
3447 if variable.dtype == NcType::String {
3448 let values = variable.string_values.as_ref().ok_or_else(|| {
3449 Error::InvalidDefinition(format!(
3450 "NC_STRING variable '{}' has no string data",
3451 variable.name
3452 ))
3453 })?;
3454 let expected = checked_usize(elements, "NetCDF-4 string variable element count")?;
3455 if values.len() != expected {
3456 return Err(Error::DataLengthMismatch {
3457 expected,
3458 actual: values.len(),
3459 });
3460 }
3461 return Ok(());
3462 }
3463 if let NcType::VLen { base } = &variable.dtype {
3464 let values = variable.vlen_values.as_ref().ok_or_else(|| {
3465 Error::InvalidDefinition(format!(
3466 "NC_VLEN variable '{}' has no sequence data",
3467 variable.name
3468 ))
3469 })?;
3470 validate_vlen_base_nc4_type(base)?;
3471 let expected = checked_usize(elements, "NetCDF-4 vlen variable element count")?;
3472 if values.len() != expected {
3473 return Err(Error::DataLengthMismatch {
3474 expected,
3475 actual: values.len(),
3476 });
3477 }
3478 let base_size = base.size()?;
3479 for value in values {
3480 if value.len() % base_size != 0 {
3481 return Err(Error::InvalidDefinition(format!(
3482 "NC_VLEN variable '{}' sequence byte length {} is not a multiple of base element size {base_size}",
3483 variable.name,
3484 value.len()
3485 )));
3486 }
3487 }
3488 return Ok(());
3489 }
3490
3491 let bytes = checked_mul_u64(
3492 elements,
3493 variable.dtype.size()? as u64,
3494 "NetCDF-4 variable byte size",
3495 )?;
3496 let expected = usize::try_from(bytes).map_err(|_| {
3497 Error::InvalidDefinition("NetCDF-4 variable byte size exceeds platform usize".into())
3498 })?;
3499 if variable.data.is_empty() && variable.fill_value.is_some() {
3500 return Ok(());
3501 }
3502 if variable.data.len() != expected {
3503 return Err(Error::DataLengthMismatch {
3504 expected,
3505 actual: variable.data.len(),
3506 });
3507 }
3508 Ok(())
3509}
3510
3511#[cfg(feature = "netcdf4")]
3512fn validate_nc4_variable_fill_value(variable: &VariableDef) -> Result<()> {
3513 let Some(fill_value) = &variable.fill_value else {
3514 return Ok(());
3515 };
3516 if matches!(&variable.dtype, NcType::String | NcType::VLen { .. }) {
3517 return Err(Error::UnsupportedFeature(format!(
3518 "NetCDF-4 variable-length fill values are not supported yet: '{}'",
3519 variable.name
3520 )));
3521 }
3522 let expected = nc4_hdf5_element_size(&variable.dtype)?;
3523 if fill_value.hdf5_bytes.len() != expected {
3524 return Err(Error::InvalidDefinition(format!(
3525 "variable '{}' fill value byte length must match datatype element size: expected {expected}, got {}",
3526 variable.name,
3527 fill_value.hdf5_bytes.len()
3528 )));
3529 }
3530 Ok(())
3531}
3532
3533#[cfg(feature = "netcdf4")]
3534fn nc4_variable_elements(variable: &VariableDef, dimension_sizes: &[u64]) -> Result<u64> {
3535 let elements = variable.dim_ids.iter().try_fold(1u64, |acc, id| {
3536 checked_mul_u64(
3537 acc,
3538 dimension_sizes[id.0],
3539 "NetCDF-4 variable element count",
3540 )
3541 })?;
3542 Ok(elements)
3543}
3544
3545#[cfg(feature = "netcdf4")]
3546fn infer_nc4_dimension_sizes(builder: &NcFileBuilder) -> Result<Vec<u64>> {
3547 let mut sizes = builder
3548 .dimensions
3549 .iter()
3550 .map(|dimension| {
3551 if dimension.is_unlimited {
3552 (dimension.current_size > 0).then_some(dimension.current_size)
3553 } else {
3554 Some(dimension.size)
3555 }
3556 })
3557 .collect::<Vec<_>>();
3558
3559 loop {
3560 let mut changed = false;
3561 for variable in &builder.variables {
3562 changed |= infer_nc4_dimension_size_from_variable(builder, variable, &mut sizes)?;
3563 }
3564 if !changed {
3565 break;
3566 }
3567 }
3568
3569 for variable in &builder.variables {
3570 let unresolved = variable
3571 .dim_ids
3572 .iter()
3573 .copied()
3574 .filter(|id| sizes[id.0].is_none())
3575 .collect::<Vec<_>>();
3576 if !unresolved.is_empty() && variable_has_data(variable) {
3577 let names = unresolved
3578 .iter()
3579 .map(|id| builder.dimensions[id.0].name.as_str())
3580 .collect::<Vec<_>>()
3581 .join(", ");
3582 return Err(Error::InvalidDefinition(format!(
3583 "cannot infer current size for NetCDF-4 unlimited dimension(s) {names} from variable '{}'",
3584 variable.name
3585 )));
3586 }
3587 }
3588
3589 Ok(sizes.into_iter().map(|size| size.unwrap_or(0)).collect())
3590}
3591
3592#[cfg(feature = "netcdf4")]
3593fn infer_nc4_dimension_size_from_variable(
3594 builder: &NcFileBuilder,
3595 variable: &VariableDef,
3596 sizes: &mut [Option<u64>],
3597) -> Result<bool> {
3598 let (elements, elem_size, actual_bytes) = variable_payload_element_count(variable)?;
3599 let mut known_product = 1u64;
3600 let mut unknown = Vec::new();
3601 for dim_id in &variable.dim_ids {
3602 match sizes[dim_id.0] {
3603 Some(size) => {
3604 known_product =
3605 checked_mul_u64(known_product, size, "NetCDF-4 known dimension product")?;
3606 }
3607 None if !unknown.contains(dim_id) => unknown.push(*dim_id),
3608 None => {
3609 return Err(Error::UnsupportedFeature(format!(
3610 "NetCDF-4 variable '{}' repeats unlimited dimension '{}'",
3611 variable.name, builder.dimensions[dim_id.0].name
3612 )));
3613 }
3614 }
3615 }
3616
3617 if unknown.len() != 1 || known_product == 0 {
3618 return Ok(false);
3619 }
3620 if elements % known_product != 0 {
3621 return Err(Error::DataLengthMismatch {
3622 expected: usize::try_from((elements / known_product + 1) * known_product * elem_size)
3623 .unwrap_or(usize::MAX),
3624 actual: actual_bytes,
3625 });
3626 }
3627 let inferred = elements / known_product;
3628 sizes[unknown[0].0] = Some(inferred);
3629 Ok(true)
3630}
3631
3632#[cfg(feature = "netcdf4")]
3633fn variable_has_data(variable: &VariableDef) -> bool {
3634 match &variable.dtype {
3635 NcType::String => variable
3636 .string_values
3637 .as_ref()
3638 .is_some_and(|values| !values.is_empty()),
3639 NcType::VLen { .. } => variable
3640 .vlen_values
3641 .as_ref()
3642 .is_some_and(|values| !values.is_empty()),
3643 _ => !variable.data.is_empty(),
3644 }
3645}
3646
3647#[cfg(feature = "netcdf4")]
3648fn variable_payload_element_count(variable: &VariableDef) -> Result<(u64, u64, usize)> {
3649 if variable.dtype == NcType::String {
3650 let values = variable.string_values.as_ref().map_or(0usize, Vec::len);
3651 return Ok((
3652 u64::try_from(values).map_err(|_| {
3653 Error::InvalidDefinition(
3654 "NC_STRING variable element count exceeds u64 capacity".into(),
3655 )
3656 })?,
3657 1,
3658 values,
3659 ));
3660 }
3661 if matches!(&variable.dtype, NcType::VLen { .. }) {
3662 let values = variable.vlen_values.as_ref().map_or(0usize, Vec::len);
3663 return Ok((
3664 u64::try_from(values).map_err(|_| {
3665 Error::InvalidDefinition(
3666 "NC_VLEN variable element count exceeds u64 capacity".into(),
3667 )
3668 })?,
3669 1,
3670 values,
3671 ));
3672 }
3673
3674 let elem_size = variable.dtype.size()? as u64;
3675 let data_len = variable.data.len() as u64;
3676 if data_len % elem_size != 0 {
3677 return Err(Error::DataLengthMismatch {
3678 expected: usize::try_from((data_len / elem_size + 1) * elem_size).unwrap_or(usize::MAX),
3679 actual: variable.data.len(),
3680 });
3681 }
3682 Ok((data_len / elem_size, elem_size, variable.data.len()))
3683}
3684
3685fn variable_shape_elements(
3686 builder: &NcFileBuilder,
3687 variable: &VariableDef,
3688 skip_dims: usize,
3689) -> Result<u64> {
3690 variable
3691 .dim_ids
3692 .iter()
3693 .skip(skip_dims)
3694 .try_fold(1u64, |acc, id| {
3695 let dim = &builder.dimensions[id.0];
3696 acc.checked_mul(dim.size).ok_or_else(|| {
3697 Error::InvalidDefinition(format!(
3698 "variable '{}' element count overflows u64",
3699 variable.name
3700 ))
3701 })
3702 })
3703}
3704
3705fn variable_is_record(builder: &NcFileBuilder, variable: &VariableDef) -> Result<bool> {
3706 Ok(variable
3707 .dim_ids
3708 .first()
3709 .is_some_and(|id| builder.dimensions[id.0].is_unlimited))
3710}
3711
3712fn infer_num_records(builder: &NcFileBuilder) -> Result<u64> {
3713 let mut records: Option<u64> = None;
3714 for variable in &builder.variables {
3715 if !variable_is_record(builder, variable)? {
3716 continue;
3717 }
3718 let current = infer_num_records_for_var(builder, variable)?;
3719 match records {
3720 Some(existing) if existing != current => {
3721 return Err(Error::InvalidDefinition(
3722 "all record variables must contain the same record count".into(),
3723 ));
3724 }
3725 Some(_) => {}
3726 None => records = Some(current),
3727 }
3728 }
3729 Ok(records.unwrap_or(0))
3730}
3731
3732fn infer_num_records_for_var(builder: &NcFileBuilder, variable: &VariableDef) -> Result<u64> {
3733 let record_elems = variable_shape_elements(builder, variable, 1)?;
3734 let elem_size = variable.dtype.size()? as u64;
3735 let record_bytes = record_elems
3736 .checked_mul(elem_size)
3737 .ok_or_else(|| Error::InvalidDefinition("record byte size overflow".into()))?;
3738 if record_bytes == 0 {
3739 return Ok(0);
3740 }
3741 let data_len = variable.data.len() as u64;
3742 if data_len % record_bytes != 0 {
3743 return Err(Error::DataLengthMismatch {
3744 expected: usize::try_from((data_len / record_bytes + 1) * record_bytes)
3745 .unwrap_or(usize::MAX),
3746 actual: variable.data.len(),
3747 });
3748 }
3749 Ok(data_len / record_bytes)
3750}
3751
3752fn validate_classic_type(dtype: &NcType) -> Result<()> {
3753 if dtype.classic_type_code().is_none() {
3754 return Err(Error::RequiresNetcdf4 {
3755 reason: format!("the {dtype:?} datatype is not representable in classic NetCDF"),
3756 });
3757 }
3758 Ok(())
3759}
3760
3761fn validate_chunk_shape_for_variable(variable: &VariableDef, chunk_shape: &[u64]) -> Result<()> {
3762 if variable.dim_ids.is_empty() {
3763 return Err(Error::InvalidDefinition(format!(
3764 "chunked NetCDF-4 scalar variables are not supported: '{}'",
3765 variable.name
3766 )));
3767 }
3768 if chunk_shape.len() != variable.dim_ids.len() {
3769 return Err(Error::InvalidDefinition(format!(
3770 "variable '{}' chunk rank must match variable rank: expected {}, got {}",
3771 variable.name,
3772 variable.dim_ids.len(),
3773 chunk_shape.len()
3774 )));
3775 }
3776 if chunk_shape.contains(&0) {
3777 return Err(Error::InvalidDefinition(format!(
3778 "variable '{}' chunk dimensions must be non-zero",
3779 variable.name
3780 )));
3781 }
3782 Ok(())
3783}
3784
3785fn reject_scalar_filtered_variable(variable: &VariableDef) -> Result<()> {
3786 if variable.dim_ids.is_empty() {
3787 return Err(Error::InvalidDefinition(format!(
3788 "filtered NetCDF-4 scalar variables are not supported: '{}'",
3789 variable.name
3790 )));
3791 }
3792 Ok(())
3793}
3794
3795#[cfg(feature = "netcdf4")]
3796fn validate_nc4_variable_storage(variable: &VariableDef) -> Result<()> {
3797 if let Some(chunk_shape) = &variable.storage.chunk_shape {
3798 validate_chunk_shape_for_variable(variable, chunk_shape)?;
3799 }
3800 if variable.storage.has_filters() {
3801 reject_scalar_filtered_variable(variable)?;
3802 }
3803 Ok(())
3804}
3805
3806#[cfg(feature = "netcdf4")]
3807fn nc4_variable_chunk_shape(
3808 variable: &VariableDef,
3809 shape: &[u64],
3810 requires_chunking: bool,
3811) -> Result<Option<Vec<u64>>> {
3812 if let Some(chunk_shape) = &variable.storage.chunk_shape {
3813 validate_chunk_shape_for_variable(variable, chunk_shape)?;
3814 return Ok(Some(chunk_shape.clone()));
3815 }
3816 if requires_chunking || variable.storage.has_filters() {
3817 return Ok(Some(nc4_default_chunk_shape(
3818 shape,
3819 nc4_hdf5_element_size(&variable.dtype)?,
3820 )?));
3821 }
3822 Ok(None)
3823}
3824
3825#[cfg(feature = "netcdf4")]
3826fn nc4_variable_filters(variable: &VariableDef) -> Vec<H5FilterDescription> {
3827 let mut filters = Vec::new();
3828 if variable.storage.shuffle {
3829 filters.push(H5FilterDescription {
3830 id: H5_FILTER_SHUFFLE,
3831 name: None,
3832 client_data: Vec::new(),
3833 });
3834 }
3835 if let Some(level) = variable.storage.deflate_level {
3836 filters.push(H5FilterDescription {
3837 id: H5_FILTER_DEFLATE,
3838 name: None,
3839 client_data: vec![u32::from(level)],
3840 });
3841 }
3842 if variable.storage.fletcher32 {
3843 filters.push(H5FilterDescription {
3844 id: H5_FILTER_FLETCHER32,
3845 name: None,
3846 client_data: Vec::new(),
3847 });
3848 }
3849 filters
3850}
3851
3852fn validate_supported_user_defined_type(dtype: &NcType) -> Result<()> {
3853 if let NcType::VLen { base } = dtype {
3854 return validate_vlen_base_nc4_type(base);
3855 }
3856 if !matches!(
3857 dtype,
3858 NcType::Enum { .. }
3859 | NcType::Compound { .. }
3860 | NcType::Opaque { .. }
3861 | NcType::Array { .. }
3862 ) {
3863 return Err(Error::InvalidDefinition(format!(
3864 "user-defined variables require enum, compound, opaque, fixed-size array, or vlen type, got {dtype:?}"
3865 )));
3866 }
3867 validate_fixed_width_nc4_type(dtype)
3868}
3869
3870fn validate_fixed_width_user_defined_type(dtype: &NcType) -> Result<()> {
3871 if !matches!(
3872 dtype,
3873 NcType::Enum { .. }
3874 | NcType::Compound { .. }
3875 | NcType::Opaque { .. }
3876 | NcType::Array { .. }
3877 ) {
3878 return Err(Error::InvalidDefinition(format!(
3879 "raw user-defined variable bytes require enum, compound, opaque, or fixed-size array type, got {dtype:?}"
3880 )));
3881 }
3882 validate_fixed_width_nc4_type(dtype)
3883}
3884
3885fn validate_fixed_width_nc4_type(dtype: &NcType) -> Result<()> {
3886 match dtype {
3887 NcType::Byte
3888 | NcType::Char
3889 | NcType::Short
3890 | NcType::Int
3891 | NcType::Float
3892 | NcType::Double
3893 | NcType::UByte
3894 | NcType::UShort
3895 | NcType::UInt
3896 | NcType::Int64
3897 | NcType::UInt64
3898 | NcType::Opaque { .. } => Ok(()),
3899 NcType::Enum { base, .. } => match base.as_ref() {
3900 NcType::Byte
3901 | NcType::UByte
3902 | NcType::Short
3903 | NcType::UShort
3904 | NcType::Int
3905 | NcType::UInt
3906 | NcType::Int64
3907 | NcType::UInt64 => Ok(()),
3908 other => Err(Error::InvalidDefinition(format!(
3909 "enum base type must be fixed-width integer, got {other:?}"
3910 ))),
3911 },
3912 NcType::Compound { fields, .. } => {
3913 for field in fields {
3914 validate_fixed_width_nc4_type(&field.dtype)?;
3915 }
3916 Ok(())
3917 }
3918 NcType::Array { base, .. } => validate_fixed_width_nc4_type(base),
3919 NcType::String | NcType::VLen { .. } => Err(Error::UnsupportedFeature(format!(
3920 "{dtype:?} user-defined variable payloads require heap-backed sequence writing"
3921 ))),
3922 }
3923}
3924
3925fn validate_vlen_base_nc4_type(dtype: &NcType) -> Result<()> {
3926 if dtype.size()? == 0 {
3927 return Err(Error::InvalidDefinition(
3928 "vlen base type must have non-zero byte size".into(),
3929 ));
3930 }
3931 match dtype {
3932 NcType::Byte
3933 | NcType::Char
3934 | NcType::Short
3935 | NcType::Int
3936 | NcType::Float
3937 | NcType::Double
3938 | NcType::UByte
3939 | NcType::UShort
3940 | NcType::UInt
3941 | NcType::Int64
3942 | NcType::UInt64
3943 | NcType::Opaque { .. } => Ok(()),
3944 NcType::Enum { .. } => validate_fixed_width_nc4_type(dtype),
3945 NcType::Compound { fields, .. } => {
3946 for field in fields {
3947 validate_vlen_base_nc4_type(&field.dtype)?;
3948 }
3949 Ok(())
3950 }
3951 NcType::Array { base, .. } => validate_vlen_base_nc4_type(base),
3952 NcType::String | NcType::VLen { .. } => Err(Error::UnsupportedFeature(format!(
3953 "{dtype:?} cannot be used as a vlen base until recursive heap-backed payload writing is implemented"
3954 ))),
3955 }
3956}
3957
3958#[cfg(feature = "netcdf4")]
3959fn validate_nc4_classic_attr_value(value: &NcAttrValue) -> Result<()> {
3960 if matches!(value, NcAttrValue::Strings(_)) {
3961 return Err(Error::UnsupportedFeature(
3962 "NC_STRING attributes require full NetCDF-4".into(),
3963 ));
3964 }
3965 Ok(())
3966}
3967
3968#[cfg(feature = "netcdf4")]
3969fn validate_nc4_classic_model(builder: &NcFileBuilder) -> Result<()> {
3970 validate_root_only_names(builder)?;
3971 let unlimited_count = builder
3972 .dimensions
3973 .iter()
3974 .filter(|dimension| dimension.is_unlimited)
3975 .count();
3976 if unlimited_count > 1 {
3977 return Err(Error::InvalidDefinition(
3978 "classic NetCDF supports at most one unlimited dimension".into(),
3979 ));
3980 }
3981 for variable in &builder.variables {
3982 if variable
3983 .dim_ids
3984 .iter()
3985 .skip(1)
3986 .any(|id| builder.dimensions[id.0].is_unlimited)
3987 {
3988 return Err(Error::InvalidDefinition(format!(
3989 "record dimension must be first for variable '{}'",
3990 variable.name
3991 )));
3992 }
3993 }
3994 Ok(())
3995}
3996
3997#[cfg(feature = "netcdf4")]
3998fn nc4_default_chunk_shape(shape: &[u64], element_size: usize) -> Result<Vec<u64>> {
3999 if shape.is_empty() {
4000 return Err(Error::InvalidDefinition(
4001 "chunked NetCDF-4 scalar variables are not supported".into(),
4002 ));
4003 }
4004 let element_size = element_size.max(1);
4005 let target_elements = (1024usize * 1024usize / element_size).max(1) as u64;
4006 let mut remaining = target_elements;
4007 let mut chunk_shape = vec![1u64; shape.len()];
4008 for dim in (0..shape.len()).rev() {
4009 let extent = shape[dim].max(1);
4010 let chunk = extent.min(remaining.max(1));
4011 chunk_shape[dim] = chunk;
4012 remaining = (remaining / chunk).max(1);
4013 }
4014 Ok(chunk_shape)
4015}
4016
4017fn attr_requires_cdf5(value: &NcAttrValue) -> bool {
4018 matches!(
4019 value,
4020 NcAttrValue::UBytes(_)
4021 | NcAttrValue::UShorts(_)
4022 | NcAttrValue::UInts(_)
4023 | NcAttrValue::Int64s(_)
4024 | NcAttrValue::UInt64s(_)
4025 )
4026}
4027
4028fn validate_name(name: &str, kind: &str) -> Result<()> {
4029 if name.is_empty() {
4030 return Err(Error::InvalidDefinition(format!(
4031 "{kind} name must not be empty"
4032 )));
4033 }
4034 if name.contains('/') || name.bytes().any(|b| b == 0) {
4035 return Err(Error::InvalidDefinition(format!(
4036 "{kind} name '{name}' contains invalid characters"
4037 )));
4038 }
4039 Ok(())
4040}
4041
4042fn validate_path_name(path: String, kind: &str) -> Result<String> {
4043 let trimmed = path.trim_matches('/').to_string();
4044 if trimmed.is_empty() {
4045 return Err(Error::InvalidDefinition(format!(
4046 "{kind} path must not be empty"
4047 )));
4048 }
4049 for component in trimmed.split('/') {
4050 validate_name(component, kind)?;
4051 }
4052 Ok(trimmed)
4053}
4054
4055#[cfg(feature = "netcdf4")]
4056fn path_leaf_name(path: &str) -> &str {
4057 path.rsplit('/').next().unwrap_or(path)
4058}
4059
4060fn validate_root_only_names(builder: &NcFileBuilder) -> Result<()> {
4061 for dimension in &builder.dimensions {
4062 if dimension.name.contains('/') {
4063 return Err(Error::RequiresNetcdf4 {
4064 reason: format!("grouped dimension '{}' needs NetCDF-4", dimension.name),
4065 });
4066 }
4067 }
4068 for variable in &builder.variables {
4069 if variable.name.contains('/') {
4070 return Err(Error::RequiresNetcdf4 {
4071 reason: format!("grouped variable '{}' needs NetCDF-4", variable.name),
4072 });
4073 }
4074 }
4075 if let Some(group_attr) = builder.group_attributes.first() {
4076 return Err(Error::RequiresNetcdf4 {
4077 reason: format!(
4078 "group attribute on '{}' needs NetCDF-4",
4079 group_attr.group_path
4080 ),
4081 });
4082 }
4083 Ok(())
4084}
4085
4086fn ensure_unique_attr(attrs: &[NcAttribute], name: &str) -> Result<()> {
4087 if attrs.iter().any(|a| a.name == name) {
4088 return Err(Error::InvalidDefinition(format!(
4089 "duplicate attribute '{name}'"
4090 )));
4091 }
4092 Ok(())
4093}
4094
4095fn set_variable_fill_value_metadata(
4096 variable: &mut VariableDef,
4097 value: NcAttrValue,
4098 classic_bytes: Vec<u8>,
4099 hdf5_bytes: Vec<u8>,
4100) -> Result<()> {
4101 if let Some(attribute) = variable
4102 .attributes
4103 .iter_mut()
4104 .find(|attribute| attribute.name == FILL_VALUE_ATTR_NAME)
4105 {
4106 if variable.fill_value.is_none() {
4107 return Err(Error::InvalidDefinition(format!(
4108 "variable '{}' already has a manual _FillValue attribute",
4109 variable.name
4110 )));
4111 }
4112 attribute.value = value;
4113 } else {
4114 variable.attributes.push(NcAttribute {
4115 name: FILL_VALUE_ATTR_NAME.to_string(),
4116 value,
4117 });
4118 }
4119
4120 variable.fill_value = Some(VariableFillValue {
4121 classic_bytes,
4122 hdf5_bytes,
4123 });
4124 Ok(())
4125}
4126
4127fn write_name(out: &mut Vec<u8>, format: NcFormat, name: &str) -> Result<()> {
4128 write_count(out, format, name.len() as u64)?;
4129 out.extend_from_slice(name.as_bytes());
4130 pad_vec_to_4(out);
4131 Ok(())
4132}
4133
4134fn write_count(out: &mut Vec<u8>, format: NcFormat, value: u64) -> Result<()> {
4135 match format {
4136 NcFormat::Cdf5 => write_u64(out, value),
4137 NcFormat::Classic | NcFormat::Offset64 => write_u32(out, require_u32(value, "count")?),
4138 _ => unreachable!("classic count only"),
4139 }
4140 Ok(())
4141}
4142
4143fn write_u32(out: &mut Vec<u8>, value: u32) {
4144 out.extend_from_slice(&value.to_be_bytes());
4145}
4146
4147fn write_u64(out: &mut Vec<u8>, value: u64) {
4148 out.extend_from_slice(&value.to_be_bytes());
4149}
4150
4151fn pad_vec_to_4(out: &mut Vec<u8>) {
4152 let pad = netcdf_core::padding_to_4(out.len());
4153 out.resize(out.len() + pad, 0);
4154}
4155
4156fn write_fill_padding(writer: &mut impl Write, len: u64, fill_pattern: &[u8]) -> Result<()> {
4159 if len == 0 {
4160 return Ok(());
4161 }
4162 let len = usize::try_from(len)
4163 .map_err(|_| Error::InvalidDefinition("padding exceeds platform usize".into()))?;
4164 let mut pad = [0u8; 4];
4165 if !fill_pattern.is_empty() {
4166 for (i, byte) in pad.iter_mut().enumerate().take(len.min(4)) {
4167 *byte = fill_pattern[i % fill_pattern.len()];
4168 }
4169 }
4170 writer.write_all(&pad[..len.min(4)])?;
4171 Ok(())
4172}
4173
4174fn pad4_u64(value: u64) -> Result<u64> {
4175 let rem = value % 4;
4176 if rem == 0 {
4177 Ok(value)
4178 } else {
4179 checked_add(value, 4 - rem, "4-byte padding")
4180 }
4181}
4182
4183fn checked_add(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
4184 lhs.checked_add(rhs)
4185 .ok_or_else(|| Error::InvalidDefinition(format!("{context} overflow")))
4186}
4187
4188fn checked_mul_u64(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
4189 lhs.checked_mul(rhs)
4190 .ok_or_else(|| Error::InvalidDefinition(format!("{context} overflow")))
4191}
4192
4193fn checked_mul_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
4194 lhs.checked_mul(rhs)
4195 .ok_or_else(|| Error::InvalidDefinition(format!("{context} overflow")))
4196}
4197
4198fn require_u32(value: u64, context: &str) -> Result<u32> {
4199 u32::try_from(value).map_err(|_| Error::FormatCapacityExceeded {
4200 reason: format!("{context} exceeds the 32-bit classic field capacity"),
4201 })
4202}
4203
4204fn require_usize(value: u64, context: &str) -> Result<usize> {
4205 usize::try_from(value)
4206 .map_err(|_| Error::InvalidDefinition(format!("{context} exceeds platform usize capacity")))
4207}