Skip to main content

rustyhdf5_format/
dataspace.rs

1//! HDF5 Dataspace message parsing (message type 0x0001).
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::FormatError;
7
8/// Type of dataspace.
9#[derive(Debug, Clone, PartialEq)]
10pub enum DataspaceType {
11    /// Scalar (single element).
12    Scalar,
13    /// Simple (N-dimensional array).
14    Simple,
15    /// Null (no data).
16    Null,
17}
18
19/// Parsed HDF5 dataspace message.
20#[derive(Debug, Clone, PartialEq)]
21pub struct Dataspace {
22    /// The type of this dataspace.
23    pub space_type: DataspaceType,
24    /// Number of dimensions (0 for scalar).
25    pub rank: u8,
26    /// Current dimension sizes.
27    pub dimensions: Vec<u64>,
28    /// Maximum dimension sizes, if present. `u64::MAX` means unlimited.
29    pub max_dimensions: Option<Vec<u64>>,
30}
31
32fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
33    match offset.checked_add(needed) {
34        Some(end) if end <= data.len() => Ok(()),
35        _ => Err(FormatError::UnexpectedEof {
36            expected: offset.saturating_add(needed),
37            available: data.len(),
38        }),
39    }
40}
41
42fn read_length(data: &[u8], offset: usize, length_size: u8) -> Result<u64, FormatError> {
43    let s = length_size as usize;
44    ensure_len(data, offset, s)?;
45    let slice = &data[offset..offset + s];
46    Ok(match length_size {
47        2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
48        4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
49        8 => u64::from_le_bytes([
50            slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
51        ]),
52        _ => {
53            return Err(FormatError::InvalidLengthSize(length_size));
54        }
55    })
56}
57
58impl Dataspace {
59    /// Parse a dataspace message from raw message bytes.
60    ///
61    /// `length_size` is needed for dimension value width (from superblock).
62    pub fn parse(data: &[u8], length_size: u8) -> Result<Dataspace, FormatError> {
63        ensure_len(data, 0, 4)?;
64
65        let version = data[0];
66        let rank = data[1];
67        let flags = data[2];
68
69        let (space_type, header_size) = match version {
70            1 => {
71                // v1: byte 3 is reserved, then 4 reserved bytes
72                ensure_len(data, 0, 8)?;
73                let st = if rank == 0 {
74                    DataspaceType::Scalar
75                } else {
76                    DataspaceType::Simple
77                };
78                (st, 8usize)
79            }
80            2 => {
81                // v2: byte 3 is type
82                let type_byte = data[3];
83                let st = match type_byte {
84                    0 => DataspaceType::Scalar,
85                    1 => DataspaceType::Simple,
86                    2 => DataspaceType::Null,
87                    _ => return Err(FormatError::InvalidDataspaceType(type_byte)),
88                };
89                (st, 4usize)
90            }
91            _ => return Err(FormatError::InvalidDataspaceVersion(version)),
92        };
93
94        let ls = length_size as usize;
95        let mut pos = header_size;
96
97        // Read current dimensions
98        let mut dimensions = Vec::with_capacity(rank as usize);
99        for _ in 0..rank {
100            let dim = read_length(data, pos, length_size)?;
101            dimensions.push(dim);
102            pos += ls;
103        }
104
105        // Read max dimensions if flags bit 0 is set
106        let max_dimensions = if flags & 0x01 != 0 {
107            let mut max_dims = Vec::with_capacity(rank as usize);
108            for _ in 0..rank {
109                let val = read_length(data, pos, length_size)?;
110                max_dims.push(val);
111                pos += ls;
112            }
113            Some(max_dims)
114        } else {
115            None
116        };
117
118        // v1 flags bit 1 = permutation indices present (skip them)
119        if version == 1 && flags & 0x02 != 0 {
120            // rank × length_size bytes of permutation indices — skip
121            let _skip = rank as usize * ls;
122            // pos += _skip; // not needed since we don't use pos after this
123        }
124
125        Ok(Dataspace {
126            space_type,
127            rank,
128            dimensions,
129            max_dimensions,
130        })
131    }
132
133    /// Serialize dataspace to HDF5 message bytes (v2 format).
134    pub fn serialize(&self, length_size: u8) -> Vec<u8> {
135        let mut buf = Vec::new();
136        buf.push(2); // version 2
137        buf.push(self.rank);
138        let flags = if self.max_dimensions.is_some() { 0x01 } else { 0x00 };
139        buf.push(flags);
140        let type_byte = match self.space_type {
141            DataspaceType::Scalar => 0,
142            DataspaceType::Simple => 1,
143            DataspaceType::Null => 2,
144        };
145        buf.push(type_byte);
146        for &dim in &self.dimensions {
147            Self::write_length(&mut buf, dim, length_size);
148        }
149        if let Some(ref max_dims) = self.max_dimensions {
150            for &md in max_dims {
151                Self::write_length(&mut buf, md, length_size);
152            }
153        }
154        buf
155    }
156
157    fn write_length(buf: &mut Vec<u8>, val: u64, size: u8) {
158        match size {
159            2 => buf.extend_from_slice(&(val as u16).to_le_bytes()),
160            4 => buf.extend_from_slice(&(val as u32).to_le_bytes()),
161            8 => buf.extend_from_slice(&val.to_le_bytes()),
162            _ => {}
163        }
164    }
165
166    /// Total number of elements. Scalar = 1, Null = 0.
167    pub fn num_elements(&self) -> u64 {
168        match self.space_type {
169            DataspaceType::Null => 0,
170            DataspaceType::Scalar => 1,
171            DataspaceType::Simple => {
172                if self.dimensions.is_empty() {
173                    0
174                } else {
175                    self.dimensions.iter().product()
176                }
177            }
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    fn build_v1_dataspace(rank: u8, flags: u8, dims: &[u64], max_dims: Option<&[u64]>) -> Vec<u8> {
187        let length_size = 8u8;
188        let mut buf = Vec::new();
189        buf.push(1); // version
190        buf.push(rank);
191        buf.push(flags);
192        buf.push(0); // reserved
193        buf.extend_from_slice(&[0u8; 4]); // reserved(4)
194        for &d in dims {
195            buf.extend_from_slice(&d.to_le_bytes());
196        }
197        if let Some(md) = max_dims {
198            for &d in md {
199                buf.extend_from_slice(&d.to_le_bytes());
200            }
201        }
202        let _ = length_size;
203        buf
204    }
205
206    fn build_v2_dataspace(
207        rank: u8,
208        flags: u8,
209        type_byte: u8,
210        dims: &[u64],
211        max_dims: Option<&[u64]>,
212    ) -> Vec<u8> {
213        let mut buf = Vec::new();
214        buf.push(2); // version
215        buf.push(rank);
216        buf.push(flags);
217        buf.push(type_byte);
218        for &d in dims {
219            buf.extend_from_slice(&d.to_le_bytes());
220        }
221        if let Some(md) = max_dims {
222            for &d in md {
223                buf.extend_from_slice(&d.to_le_bytes());
224            }
225        }
226        buf
227    }
228
229    #[test]
230    fn scalar_v1() {
231        let data = build_v1_dataspace(0, 0, &[], None);
232        let ds = Dataspace::parse(&data, 8).unwrap();
233        assert_eq!(ds.space_type, DataspaceType::Scalar);
234        assert_eq!(ds.rank, 0);
235        assert!(ds.dimensions.is_empty());
236        assert!(ds.max_dimensions.is_none());
237        assert_eq!(ds.num_elements(), 1);
238    }
239
240    #[test]
241    fn null_v2() {
242        let data = build_v2_dataspace(0, 0, 2, &[], None);
243        let ds = Dataspace::parse(&data, 8).unwrap();
244        assert_eq!(ds.space_type, DataspaceType::Null);
245        assert_eq!(ds.num_elements(), 0);
246    }
247
248    #[test]
249    fn simple_1d() {
250        let data = build_v1_dataspace(1, 0, &[5], None);
251        let ds = Dataspace::parse(&data, 8).unwrap();
252        assert_eq!(ds.space_type, DataspaceType::Simple);
253        assert_eq!(ds.rank, 1);
254        assert_eq!(ds.dimensions, vec![5]);
255        assert!(ds.max_dimensions.is_none());
256        assert_eq!(ds.num_elements(), 5);
257    }
258
259    #[test]
260    fn simple_2d() {
261        let data = build_v1_dataspace(2, 0, &[3, 4], None);
262        let ds = Dataspace::parse(&data, 8).unwrap();
263        assert_eq!(ds.rank, 2);
264        assert_eq!(ds.dimensions, vec![3, 4]);
265        assert_eq!(ds.num_elements(), 12);
266    }
267
268    #[test]
269    fn simple_3d_with_max_dims_unlimited() {
270        let data = build_v1_dataspace(
271            3,
272            0x01,
273            &[2, 3, 4],
274            Some(&[10, u64::MAX, 100]),
275        );
276        let ds = Dataspace::parse(&data, 8).unwrap();
277        assert_eq!(ds.rank, 3);
278        assert_eq!(ds.dimensions, vec![2, 3, 4]);
279        let md = ds.max_dimensions.clone().unwrap();
280        assert_eq!(md, vec![10, u64::MAX, 100]);
281        assert_eq!(ds.num_elements(), 24);
282    }
283
284    #[test]
285    fn v2_simple() {
286        let data = build_v2_dataspace(1, 0, 1, &[7], None);
287        let ds = Dataspace::parse(&data, 8).unwrap();
288        assert_eq!(ds.space_type, DataspaceType::Simple);
289        assert_eq!(ds.dimensions, vec![7]);
290    }
291
292    #[test]
293    fn v2_scalar() {
294        let data = build_v2_dataspace(0, 0, 0, &[], None);
295        let ds = Dataspace::parse(&data, 8).unwrap();
296        assert_eq!(ds.space_type, DataspaceType::Scalar);
297        assert_eq!(ds.num_elements(), 1);
298    }
299
300    #[test]
301    fn v1_with_4byte_length() {
302        let mut buf = Vec::new();
303        buf.push(1); // version
304        buf.push(1); // rank
305        buf.push(0); // flags
306        buf.push(0); // reserved
307        buf.extend_from_slice(&[0u8; 4]); // reserved(4)
308        buf.extend_from_slice(&10u32.to_le_bytes()); // dim with length_size=4
309        let ds = Dataspace::parse(&buf, 4).unwrap();
310        assert_eq!(ds.dimensions, vec![10]);
311    }
312
313    #[test]
314    fn truncated_data_error() {
315        let data = [1u8, 2]; // too short
316        let err = Dataspace::parse(&data, 8).unwrap_err();
317        assert!(matches!(err, FormatError::UnexpectedEof { .. }));
318    }
319
320    #[test]
321    fn invalid_version_error() {
322        let data = [5u8, 0, 0, 0, 0, 0, 0, 0];
323        let err = Dataspace::parse(&data, 8).unwrap_err();
324        assert_eq!(err, FormatError::InvalidDataspaceVersion(5));
325    }
326
327    #[test]
328    fn invalid_v2_type_error() {
329        let data = build_v2_dataspace(0, 0, 5, &[], None);
330        let err = Dataspace::parse(&data, 8).unwrap_err();
331        assert_eq!(err, FormatError::InvalidDataspaceType(5));
332    }
333
334    #[test]
335    fn v1_with_max_dims() {
336        let data = build_v1_dataspace(1, 0x01, &[5], Some(&[10]));
337        let ds = Dataspace::parse(&data, 8).unwrap();
338        assert_eq!(ds.max_dimensions, Some(vec![10]));
339    }
340}