1#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::bytes;
7use crate::error::FormatError;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum DataspaceType {
12 Scalar,
14 Simple,
16 Null,
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub struct Dataspace {
23 pub space_type: DataspaceType,
25 pub rank: u8,
27 pub dimensions: Vec<u64>,
29 pub max_dimensions: Option<Vec<MaxExtent>>,
31}
32
33impl Dataspace {
34 pub fn parse(data: &[u8], length_size: u8) -> Result<Dataspace, FormatError> {
38 bytes::ensure_len(data, 0, 4)?;
39
40 let version = data[0];
41 let rank = data[1];
42 let flags = data[2];
43
44 let (space_type, header_size) = match version {
45 1 => {
46 bytes::ensure_len(data, 0, 8)?;
48 let st = if rank == 0 {
49 DataspaceType::Scalar
50 } else {
51 DataspaceType::Simple
52 };
53 (st, 8usize)
54 }
55 2 => {
56 let type_byte = data[3];
58 let st = match type_byte {
59 0 => DataspaceType::Scalar,
60 1 => DataspaceType::Simple,
61 2 => DataspaceType::Null,
62 _ => return Err(FormatError::InvalidDataspaceType(type_byte)),
63 };
64 (st, 4usize)
65 }
66 _ => return Err(FormatError::InvalidDataspaceVersion(version)),
67 };
68
69 let ls = length_size as usize;
70 let mut pos = header_size;
71
72 let mut dimensions = Vec::with_capacity(rank as usize);
74 for _ in 0..rank {
75 let dim = bytes::read_length(data, pos, length_size)?;
76 dimensions.push(dim);
77 pos += ls;
78 }
79
80 let max_dimensions = if flags & 0x01 != 0 {
82 let mut max_dims = Vec::with_capacity(rank as usize);
83 for _ in 0..rank {
84 max_dims.push(MaxExtent::from_length(bytes::read_length(
85 data,
86 pos,
87 length_size,
88 )?));
89 pos += ls;
90 }
91 Some(max_dims)
92 } else {
93 None
94 };
95
96 Ok(Dataspace {
101 space_type,
102 rank,
103 dimensions,
104 max_dimensions,
105 })
106 }
107
108 pub fn serialize(&self, length_size: u8) -> Vec<u8> {
110 let mut buf = Vec::new();
111 buf.push(2); buf.push(self.rank);
113 let flags = if self.max_dimensions.is_some() {
114 0x01
115 } else {
116 0x00
117 };
118 buf.push(flags);
119 let type_byte = match self.space_type {
120 DataspaceType::Scalar => 0,
121 DataspaceType::Simple => 1,
122 DataspaceType::Null => 2,
123 };
124 buf.push(type_byte);
125 for &dim in &self.dimensions {
126 Self::write_length(&mut buf, dim, length_size);
127 }
128 if let Some(ref max_dims) = self.max_dimensions {
129 for &md in max_dims {
130 Self::write_length(&mut buf, md.to_length(), length_size);
131 }
132 }
133 buf
134 }
135
136 fn write_length(buf: &mut Vec<u8>, val: u64, size: u8) {
137 #[expect(
138 clippy::cast_possible_truncation,
139 reason = "size is the length byte width chosen to hold val, so each arm casts to a width that fits by construction"
140 )]
141 match size {
142 2 => buf.extend_from_slice(&(val as u16).to_le_bytes()),
143 4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
144 8 => buf.extend_from_slice(&val.to_le_bytes()),
145 _ => {}
146 }
147 }
148
149 pub fn num_elements(&self) -> u64 {
156 match self.space_type {
157 DataspaceType::Null => 0,
158 DataspaceType::Scalar => 1,
159 DataspaceType::Simple => {
160 if self.dimensions.is_empty() {
161 0
162 } else {
163 self.dimensions
164 .iter()
165 .fold(1u64, |acc, &dim| acc.saturating_mul(dim))
166 }
167 }
168 }
169 }
170
171 pub(crate) fn extensible_max_dimensions(&self) -> Option<&[MaxExtent]> {
176 let max_dims = self.max_dimensions.as_deref()?;
177 let fixed_at_current = max_dims.len() == self.dimensions.len()
178 && max_dims
179 .iter()
180 .zip(&self.dimensions)
181 .all(|(max, &dim)| max.size() == Some(dim));
182 (!fixed_at_current).then_some(max_dims)
183 }
184}
185
186#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
206pub enum MaxExtent {
207 Fixed(u64),
209 Unlimited,
211}
212
213impl MaxExtent {
214 pub(crate) const fn from_length(length: u64) -> Self {
219 if length == UNLIMITED_LENGTH {
220 Self::Unlimited
221 } else {
222 Self::Fixed(length)
223 }
224 }
225
226 pub const fn size(self) -> Option<u64> {
229 match self {
230 Self::Fixed(size) => Some(size),
231 Self::Unlimited => None,
232 }
233 }
234
235 pub(crate) const fn admits(self, dim: u64) -> bool {
237 match self {
238 Self::Fixed(size) => size >= dim,
239 Self::Unlimited => true,
240 }
241 }
242
243 pub(crate) const fn to_length(self) -> u64 {
246 match self {
247 Self::Fixed(size) => size,
248 Self::Unlimited => UNLIMITED_LENGTH,
249 }
250 }
251
252 const fn is_unlimited(self) -> bool {
253 matches!(self, Self::Unlimited)
254 }
255}
256
257#[derive(Clone, Copy, Debug)]
262pub(crate) struct Extent<'a> {
263 dims: &'a [u64],
265 max_dims: Option<&'a [MaxExtent]>,
267}
268
269impl<'a> Extent<'a> {
270 pub(crate) fn new(
279 dims: &'a [u64],
280 max_dims: Option<&'a [MaxExtent]>,
281 ) -> Result<Self, &'static str> {
282 if let Some(max_dims) = max_dims {
283 if max_dims.len() != dims.len() {
284 return Err("maxshape must have the same rank as the dataset shape");
285 }
286 if max_dims
287 .iter()
288 .zip(dims)
289 .any(|(max, &dim)| !max.admits(dim))
290 {
291 return Err("maxshape must be at least the current shape in every dimension");
292 }
293 if max_dims.contains(&MaxExtent::Fixed(UNLIMITED_LENGTH)) {
294 return Err(
295 "a fixed maximum of u64::MAX is the format's unlimited marker; \
296 name MaxExtent::Unlimited for a dimension that grows without bound",
297 );
298 }
299 }
300 Ok(Self { dims, max_dims })
301 }
302
303 pub(crate) const fn dims(self) -> &'a [u64] {
304 self.dims
305 }
306
307 pub(crate) fn unlimited_dimensions(self) -> usize {
311 self.max_dims.map_or(0, |max_dims| {
312 max_dims.iter().filter(|max| max.is_unlimited()).count()
313 })
314 }
315}
316
317const UNLIMITED_LENGTH: u64 = u64::MAX;
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 fn build_v1_dataspace(rank: u8, flags: u8, dims: &[u64], max_dims: Option<&[u64]>) -> Vec<u8> {
330 let length_size = 8u8;
331 let mut buf = Vec::new();
332 buf.push(1); buf.push(rank);
334 buf.push(flags);
335 buf.push(0); buf.extend_from_slice(&[0u8; 4]); for &d in dims {
338 buf.extend_from_slice(&d.to_le_bytes());
339 }
340 if let Some(md) = max_dims {
341 for &d in md {
342 buf.extend_from_slice(&d.to_le_bytes());
343 }
344 }
345 let _ = length_size;
346 buf
347 }
348
349 fn build_v2_dataspace(
350 rank: u8,
351 flags: u8,
352 type_byte: u8,
353 dims: &[u64],
354 max_dims: Option<&[u64]>,
355 ) -> Vec<u8> {
356 let mut buf = Vec::new();
357 buf.push(2); buf.push(rank);
359 buf.push(flags);
360 buf.push(type_byte);
361 for &d in dims {
362 buf.extend_from_slice(&d.to_le_bytes());
363 }
364 if let Some(md) = max_dims {
365 for &d in md {
366 buf.extend_from_slice(&d.to_le_bytes());
367 }
368 }
369 buf
370 }
371
372 #[test]
373 fn scalar_v1() {
374 let data = build_v1_dataspace(0, 0, &[], None);
375 let ds = Dataspace::parse(&data, 8).unwrap();
376 assert_eq!(ds.space_type, DataspaceType::Scalar);
377 assert_eq!(ds.rank, 0);
378 assert!(ds.dimensions.is_empty());
379 assert!(ds.max_dimensions.is_none());
380 assert_eq!(ds.num_elements(), 1);
381 }
382
383 #[test]
384 fn null_v2() {
385 let data = build_v2_dataspace(0, 0, 2, &[], None);
386 let ds = Dataspace::parse(&data, 8).unwrap();
387 assert_eq!(ds.space_type, DataspaceType::Null);
388 assert_eq!(ds.num_elements(), 0);
389 }
390
391 #[test]
392 fn simple_1d() {
393 let data = build_v1_dataspace(1, 0, &[5], None);
394 let ds = Dataspace::parse(&data, 8).unwrap();
395 assert_eq!(ds.space_type, DataspaceType::Simple);
396 assert_eq!(ds.rank, 1);
397 assert_eq!(ds.dimensions, vec![5]);
398 assert!(ds.max_dimensions.is_none());
399 assert_eq!(ds.num_elements(), 5);
400 }
401
402 #[test]
403 fn simple_2d() {
404 let data = build_v1_dataspace(2, 0, &[3, 4], None);
405 let ds = Dataspace::parse(&data, 8).unwrap();
406 assert_eq!(ds.rank, 2);
407 assert_eq!(ds.dimensions, vec![3, 4]);
408 assert_eq!(ds.num_elements(), 12);
409 }
410
411 #[test]
412 fn simple_3d_with_max_dims_unlimited() {
413 let data = build_v1_dataspace(3, 0x01, &[2, 3, 4], Some(&[10, u64::MAX, 100]));
414 let ds = Dataspace::parse(&data, 8).unwrap();
415 assert_eq!(ds.rank, 3);
416 assert_eq!(ds.dimensions, vec![2, 3, 4]);
417 let md = ds.max_dimensions.clone().unwrap();
418 assert_eq!(
419 md,
420 vec![
421 MaxExtent::Fixed(10),
422 MaxExtent::Unlimited,
423 MaxExtent::Fixed(100)
424 ]
425 );
426 assert_eq!(ds.num_elements(), 24);
427 }
428
429 #[test]
430 fn num_elements_saturates_on_overflow() {
431 let data = build_v1_dataspace(3, 0, &[1 << 40, 1 << 40, 1 << 40], None);
435 let ds = Dataspace::parse(&data, 8).unwrap();
436 assert_eq!(ds.num_elements(), u64::MAX);
437 }
438
439 #[test]
440 fn v2_simple() {
441 let data = build_v2_dataspace(1, 0, 1, &[7], None);
442 let ds = Dataspace::parse(&data, 8).unwrap();
443 assert_eq!(ds.space_type, DataspaceType::Simple);
444 assert_eq!(ds.dimensions, vec![7]);
445 }
446
447 #[test]
448 fn v2_scalar() {
449 let data = build_v2_dataspace(0, 0, 0, &[], None);
450 let ds = Dataspace::parse(&data, 8).unwrap();
451 assert_eq!(ds.space_type, DataspaceType::Scalar);
452 assert_eq!(ds.num_elements(), 1);
453 }
454
455 #[test]
456 fn v1_with_4byte_length() {
457 let mut buf = Vec::new();
458 buf.push(1); buf.push(1); buf.push(0); buf.push(0); buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&10u32.to_le_bytes()); let ds = Dataspace::parse(&buf, 4).unwrap();
465 assert_eq!(ds.dimensions, vec![10]);
466 }
467
468 #[test]
469 fn truncated_data_error() {
470 let data = [1u8, 2]; let err = Dataspace::parse(&data, 8).unwrap_err();
472 assert!(matches!(err, FormatError::UnexpectedEof { .. }));
473 }
474
475 #[test]
476 fn invalid_version_error() {
477 let data = [5u8, 0, 0, 0, 0, 0, 0, 0];
478 let err = Dataspace::parse(&data, 8).unwrap_err();
479 assert_eq!(err, FormatError::InvalidDataspaceVersion(5));
480 }
481
482 #[test]
483 fn invalid_v2_type_error() {
484 let data = build_v2_dataspace(0, 0, 5, &[], None);
485 let err = Dataspace::parse(&data, 8).unwrap_err();
486 assert_eq!(err, FormatError::InvalidDataspaceType(5));
487 }
488
489 #[test]
490 fn v1_with_max_dims() {
491 let data = build_v1_dataspace(1, 0x01, &[5], Some(&[10]));
492 let ds = Dataspace::parse(&data, 8).unwrap();
493 assert_eq!(ds.max_dimensions, Some(vec![MaxExtent::Fixed(10)]));
494 }
495
496 #[test]
497 fn all_ones_max_dimension_is_unlimited_only_at_eight_bytes() {
498 let eight = build_v1_dataspace(1, 0x01, &[5], Some(&[u64::MAX]));
499 let ds = Dataspace::parse(&eight, 8).unwrap();
500 assert_eq!(ds.max_dimensions, Some(vec![MaxExtent::Unlimited]));
501
502 let mut four = Vec::new();
505 four.extend_from_slice(&[1, 1, 0x01, 0]);
506 four.extend_from_slice(&[0u8; 4]);
507 four.extend_from_slice(&5u32.to_le_bytes());
508 four.extend_from_slice(&u32::MAX.to_le_bytes());
509 let ds = Dataspace::parse(&four, 4).unwrap();
510 assert_eq!(
511 ds.max_dimensions,
512 Some(vec![MaxExtent::Fixed(u64::from(u32::MAX))])
513 );
514 }
515
516 #[test]
517 fn serialize_writes_all_ones_for_an_unlimited_maximum() {
518 let ds = Dataspace {
519 space_type: DataspaceType::Simple,
520 rank: 2,
521 dimensions: vec![4, 6],
522 max_dimensions: Some(vec![MaxExtent::Unlimited, MaxExtent::Fixed(6)]),
523 };
524 let bytes = ds.serialize(8);
525 assert_eq!(bytes[..4], [2, 2, 0x01, 1]);
526 assert_eq!(bytes[20..28], u64::MAX.to_le_bytes());
527 assert_eq!(bytes[28..36], 6u64.to_le_bytes());
528 assert_eq!(Dataspace::parse(&bytes, 8).unwrap(), ds);
529 }
530
531 #[test]
532 fn extensible_max_dimensions_skips_a_maximum_at_the_current_shape() {
533 let fixed = Dataspace {
534 space_type: DataspaceType::Simple,
535 rank: 2,
536 dimensions: vec![4, 6],
537 max_dimensions: Some(vec![MaxExtent::Fixed(4), MaxExtent::Fixed(6)]),
538 };
539 assert_eq!(fixed.extensible_max_dimensions(), None);
540
541 let none = Dataspace {
542 max_dimensions: None,
543 ..fixed.clone()
544 };
545 assert_eq!(none.extensible_max_dimensions(), None);
546
547 let grown = Dataspace {
548 max_dimensions: Some(vec![MaxExtent::Fixed(4), MaxExtent::Unlimited]),
549 ..fixed
550 };
551 assert_eq!(
552 grown.extensible_max_dimensions(),
553 Some(&[MaxExtent::Fixed(4), MaxExtent::Unlimited][..])
554 );
555 }
556
557 #[test]
558 fn an_extent_joins_a_shape_with_a_maximum_that_bounds_it() {
559 let extent = Extent::new(&[4, 6], Some(&[MaxExtent::Unlimited, MaxExtent::Fixed(6)]))
560 .expect("an unlimited dimension bounds anything, and 6 bounds 6");
561 assert_eq!(extent.dims(), &[4, 6]);
562 assert_eq!(extent.unlimited_dimensions(), 1);
563
564 let bounded = Extent::new(&[4, 6], None).unwrap();
565 assert_eq!(bounded.unlimited_dimensions(), 0);
566 }
567
568 #[test]
569 fn an_extent_rejects_a_maximum_shape_that_disagrees_with_its_shape() {
570 assert_eq!(
571 Extent::new(&[4], Some(&[MaxExtent::Fixed(4), MaxExtent::Fixed(4)])).unwrap_err(),
572 "maxshape must have the same rank as the dataset shape"
573 );
574 assert_eq!(
575 Extent::new(&[4], Some(&[MaxExtent::Fixed(3)])).unwrap_err(),
576 "maxshape must be at least the current shape in every dimension"
577 );
578 }
579
580 #[test]
581 fn an_extent_rejects_a_fixed_maximum_at_the_unlimited_size() {
582 let err = Extent::new(&[4], Some(&[MaxExtent::Fixed(u64::MAX)])).unwrap_err();
583 assert!(err.contains("the format's unlimited marker"), "{err}");
584 }
585
586 #[test]
587 fn max_extent_reports_its_size_and_bounds_a_dimension() {
588 assert_eq!(MaxExtent::Fixed(7).size(), Some(7));
589 assert_eq!(MaxExtent::Unlimited.size(), None);
590 assert!(!MaxExtent::Fixed(7).is_unlimited());
591 assert!(MaxExtent::Unlimited.is_unlimited());
592 assert!(MaxExtent::Fixed(7).admits(7));
593 assert!(!MaxExtent::Fixed(7).admits(8));
594 assert!(MaxExtent::Unlimited.admits(u64::MAX));
595 }
596}