Skip to main content

rustyhdf5_format/
data_layout.rs

1//! HDF5 Data Layout message parsing (message type 0x0008).
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use crate::error::FormatError;
7
8/// Parsed HDF5 data layout message.
9#[derive(Debug, Clone, PartialEq)]
10pub enum DataLayout {
11    /// Compact: data stored inline in the message.
12    Compact {
13        /// The inline raw data bytes.
14        data: Vec<u8>,
15    },
16    /// Contiguous: data stored at a single address in the file.
17    Contiguous {
18        /// File address of the data, or `None` if undefined (all 0xFF).
19        address: Option<u64>,
20        /// Size of the data in bytes.
21        size: u64,
22    },
23    /// Chunked: data stored in chunks via a B-tree.
24    Chunked {
25        /// Chunk dimension sizes.
26        chunk_dimensions: Vec<u32>,
27        /// B-tree address, or `None` if undefined.
28        btree_address: Option<u64>,
29        /// Layout version (3 or 4).
30        version: u8,
31        /// Chunk index type (v4 only).
32        chunk_index_type: Option<u8>,
33        /// Filtered size for v4 single chunk with filters.
34        single_chunk_filtered_size: Option<u64>,
35        /// Filter mask for v4 single chunk with filters.
36        single_chunk_filter_mask: Option<u32>,
37    },
38    /// Virtual dataset layout (v4 only).
39    Virtual {
40        /// Layout version.
41        version: u8,
42    },
43}
44
45fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
46    match offset.checked_add(needed) {
47        Some(end) if end <= data.len() => Ok(()),
48        _ => Err(FormatError::UnexpectedEof {
49            expected: offset.saturating_add(needed),
50            available: data.len(),
51        }),
52    }
53}
54
55fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
56    let s = size as usize;
57    ensure_len(data, pos, s)?;
58    let slice = &data[pos..pos + s];
59    Ok(match size {
60        2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
61        4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
62        8 => u64::from_le_bytes([
63            slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
64        ]),
65        _ => {
66            return Err(FormatError::InvalidOffsetSize(size));
67        }
68    })
69}
70
71fn read_length(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
72    read_offset(data, pos, size)
73}
74
75/// Check if all bytes in a slice are 0xFF (undefined address).
76fn is_undefined(data: &[u8], pos: usize, size: u8) -> bool {
77    let s = size as usize;
78    if pos + s > data.len() {
79        return false;
80    }
81    data[pos..pos + s].iter().all(|&b| b == 0xFF)
82}
83
84impl DataLayout {
85    /// Parse a data layout message from raw message bytes.
86    ///
87    /// `offset_size` and `length_size` come from the superblock.
88    pub fn parse(data: &[u8], offset_size: u8, length_size: u8) -> Result<DataLayout, FormatError> {
89        ensure_len(data, 0, 2)?;
90        let version = data[0];
91        let layout_class = data[1];
92
93        match version {
94            3 => Self::parse_v3(data, layout_class, offset_size, length_size),
95            4 => Self::parse_v4(data, layout_class, offset_size, length_size),
96            _ => Err(FormatError::InvalidLayoutVersion(version)),
97        }
98    }
99
100    fn parse_v3(
101        data: &[u8],
102        layout_class: u8,
103        offset_size: u8,
104        length_size: u8,
105    ) -> Result<DataLayout, FormatError> {
106        let pos = 2;
107        match layout_class {
108            0 => {
109                // Compact
110                ensure_len(data, pos, 2)?;
111                let data_size = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
112                ensure_len(data, pos + 2, data_size)?;
113                let raw = data[pos + 2..pos + 2 + data_size].to_vec();
114                Ok(DataLayout::Compact { data: raw })
115            }
116            1 => {
117                // Contiguous
118                let os = offset_size as usize;
119                let ls = length_size as usize;
120                ensure_len(data, pos, os + ls)?;
121                let address = if is_undefined(data, pos, offset_size) {
122                    None
123                } else {
124                    Some(read_offset(data, pos, offset_size)?)
125                };
126                let size = read_length(data, pos + os, length_size)?;
127                Ok(DataLayout::Contiguous { address, size })
128            }
129            2 => {
130                // Chunked
131                ensure_len(data, pos, 1)?;
132                let dimensionality = data[pos] as usize;
133                let mut p = pos + 1;
134                // btree address first
135                let os = offset_size as usize;
136                ensure_len(data, p, os)?;
137                let btree_address = if is_undefined(data, p, offset_size) {
138                    None
139                } else {
140                    Some(read_offset(data, p, offset_size)?)
141                };
142                p += os;
143                // chunk dim sizes: dimensionality × 4 bytes each
144                ensure_len(data, p, dimensionality * 4)?;
145                let mut chunk_dimensions = Vec::with_capacity(dimensionality);
146                for _ in 0..dimensionality {
147                    let dim = u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]);
148                    chunk_dimensions.push(dim);
149                    p += 4;
150                }
151                Ok(DataLayout::Chunked {
152                    chunk_dimensions,
153                    btree_address,
154                    version: 3,
155                    chunk_index_type: None,
156                    single_chunk_filtered_size: None,
157                    single_chunk_filter_mask: None,
158                })
159            }
160            _ => Err(FormatError::InvalidLayoutClass(layout_class)),
161        }
162    }
163
164    fn parse_v4(
165        data: &[u8],
166        layout_class: u8,
167        offset_size: u8,
168        length_size: u8,
169    ) -> Result<DataLayout, FormatError> {
170        let pos = 2;
171        match layout_class {
172            0 => {
173                // Compact — same as v3
174                ensure_len(data, pos, 2)?;
175                let data_size = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
176                ensure_len(data, pos + 2, data_size)?;
177                let raw = data[pos + 2..pos + 2 + data_size].to_vec();
178                Ok(DataLayout::Compact { data: raw })
179            }
180            1 => {
181                // Contiguous — same as v3
182                let os = offset_size as usize;
183                let ls = length_size as usize;
184                ensure_len(data, pos, os + ls)?;
185                let address = if is_undefined(data, pos, offset_size) {
186                    None
187                } else {
188                    Some(read_offset(data, pos, offset_size)?)
189                };
190                let size = read_length(data, pos + os, length_size)?;
191                Ok(DataLayout::Contiguous { address, size })
192            }
193            2 => {
194                // Chunked v4
195                ensure_len(data, pos, 3)?;
196                let flags = data[pos];
197                let dimensionality = data[pos + 1] as usize;
198                let dim_size_encoded_length = data[pos + 2] as usize;
199                let mut p = pos + 3;
200
201                // dimension sizes
202                ensure_len(data, p, dimensionality * dim_size_encoded_length)?;
203                let mut chunk_dimensions = Vec::with_capacity(dimensionality);
204                for _ in 0..dimensionality {
205                    let val = match dim_size_encoded_length {
206                        1 => data[p] as u32,
207                        2 => u16::from_le_bytes([data[p], data[p + 1]]) as u32,
208                        4 => u32::from_le_bytes([
209                            data[p],
210                            data[p + 1],
211                            data[p + 2],
212                            data[p + 3],
213                        ]),
214                        8 => {
215                            // Truncate to u32
216                            u32::from_le_bytes([
217                                data[p],
218                                data[p + 1],
219                                data[p + 2],
220                                data[p + 3],
221                            ])
222                        }
223                        _ => {
224                            return Err(FormatError::UnexpectedEof {
225                                expected: p + dim_size_encoded_length,
226                                available: data.len(),
227                            });
228                        }
229                    };
230                    chunk_dimensions.push(val);
231                    p += dim_size_encoded_length;
232                }
233
234                // chunk index type
235                ensure_len(data, p, 1)?;
236                let chunk_index_type = data[p];
237                p += 1;
238
239                // Parse index-specific fields
240                let mut single_chunk_filtered_size = None;
241                let mut single_chunk_filter_mask = None;
242                let btree_address = match chunk_index_type {
243                    1 => {
244                        // Single chunk
245                        // H5O_LAYOUT_CHUNK_SINGLE_INDEX_WITH_FILTER = 0x02
246                        let filters_present = flags & 0x02 != 0;
247                        if filters_present {
248                            // filtered_size(length_size) + filter_mask(4) + address(offset_size)
249                            let ls = length_size as usize;
250                            let os = offset_size as usize;
251                            ensure_len(data, p, ls + 4 + os)?;
252                            single_chunk_filtered_size = Some(read_length(data, p, length_size)?);
253                            p += ls;
254                            single_chunk_filter_mask = Some(u32::from_le_bytes([
255                                data[p], data[p + 1], data[p + 2], data[p + 3],
256                            ]));
257                            p += 4;
258                            if is_undefined(data, p, offset_size) {
259                                None
260                            } else {
261                                Some(read_offset(data, p, offset_size)?)
262                            }
263                        } else {
264                            // just address(offset_size)
265                            ensure_len(data, p, offset_size as usize)?;
266                            if is_undefined(data, p, offset_size) {
267                                None
268                            } else {
269                                Some(read_offset(data, p, offset_size)?)
270                            }
271                        }
272                    }
273                    2 => {
274                        // Implicit: just address
275                        ensure_len(data, p, offset_size as usize)?;
276                        if is_undefined(data, p, offset_size) {
277                            None
278                        } else {
279                            Some(read_offset(data, p, offset_size)?)
280                        }
281                    }
282                    3 => {
283                        // Fixed Array: max_dblk_page_nelmts_bits(1) + address(offset_size)
284                        ensure_len(data, p, 1 + offset_size as usize)?;
285                        p += 1; // skip max_dblk_page_nelmts_bits
286                        if is_undefined(data, p, offset_size) {
287                            None
288                        } else {
289                            Some(read_offset(data, p, offset_size)?)
290                        }
291                    }
292                    4 => {
293                        // Extensible Array: 5 creation params + address(offset_size)
294                        ensure_len(data, p, 5 + offset_size as usize)?;
295                        p += 5; // skip EA creation parameters
296                        if is_undefined(data, p, offset_size) {
297                            None
298                        } else {
299                            Some(read_offset(data, p, offset_size)?)
300                        }
301                    }
302                    5 => {
303                        // B-tree v2: node_size(4) + split_percent(1) + merge_percent(1) + address
304                        ensure_len(data, p, 6 + offset_size as usize)?;
305                        p += 6;
306                        if is_undefined(data, p, offset_size) {
307                            None
308                        } else {
309                            Some(read_offset(data, p, offset_size)?)
310                        }
311                    }
312                    _ => {
313                        // Unknown index type: try just address
314                        ensure_len(data, p, offset_size as usize)?;
315                        if is_undefined(data, p, offset_size) {
316                            None
317                        } else {
318                            Some(read_offset(data, p, offset_size)?)
319                        }
320                    }
321                };
322
323                Ok(DataLayout::Chunked {
324                    chunk_dimensions,
325                    btree_address,
326                    version: 4,
327                    chunk_index_type: Some(chunk_index_type),
328                    single_chunk_filtered_size,
329                    single_chunk_filter_mask,
330                })
331            }
332            3 => {
333                // Virtual
334                Ok(DataLayout::Virtual { version: 4 })
335            }
336            _ => Err(FormatError::InvalidLayoutClass(layout_class)),
337        }
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn v3_compact() {
347        let mut buf = vec![3u8, 0]; // version=3, class=0 (compact)
348        buf.extend_from_slice(&5u16.to_le_bytes()); // data_size=5
349        buf.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]); // data
350        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
351        assert_eq!(
352            layout,
353            DataLayout::Compact {
354                data: vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE]
355            }
356        );
357    }
358
359    #[test]
360    fn v3_contiguous() {
361        let mut buf = vec![3u8, 1]; // version=3, class=1 (contiguous)
362        buf.extend_from_slice(&0x1000u64.to_le_bytes()); // address
363        buf.extend_from_slice(&256u64.to_le_bytes()); // size
364        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
365        assert_eq!(
366            layout,
367            DataLayout::Contiguous {
368                address: Some(0x1000),
369                size: 256,
370            }
371        );
372    }
373
374    #[test]
375    fn v3_contiguous_undefined_address() {
376        let mut buf = vec![3u8, 1];
377        buf.extend_from_slice(&[0xFF; 8]); // undefined address
378        buf.extend_from_slice(&0u64.to_le_bytes()); // size
379        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
380        assert_eq!(
381            layout,
382            DataLayout::Contiguous {
383                address: None,
384                size: 0,
385            }
386        );
387    }
388
389    #[test]
390    fn v3_chunked() {
391        let mut buf = vec![3u8, 2]; // version=3, class=2 (chunked)
392        buf.push(3); // dimensionality=3 (rank+1)
393        buf.extend_from_slice(&0x2000u64.to_le_bytes()); // btree address
394        // 3 chunk dim sizes × 4 bytes
395        buf.extend_from_slice(&100u32.to_le_bytes());
396        buf.extend_from_slice(&200u32.to_le_bytes());
397        buf.extend_from_slice(&8u32.to_le_bytes()); // last = element size
398        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
399        assert_eq!(
400            layout,
401            DataLayout::Chunked {
402                chunk_dimensions: vec![100, 200, 8],
403                btree_address: Some(0x2000),
404                version: 3,
405                chunk_index_type: None,
406                single_chunk_filtered_size: None,
407                single_chunk_filter_mask: None,
408            }
409        );
410    }
411
412    #[test]
413    fn v4_compact() {
414        let mut buf = vec![4u8, 0]; // version=4, class=0
415        buf.extend_from_slice(&3u16.to_le_bytes());
416        buf.extend_from_slice(&[1, 2, 3]);
417        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
418        assert_eq!(layout, DataLayout::Compact { data: vec![1, 2, 3] });
419    }
420
421    #[test]
422    fn v4_contiguous() {
423        let mut buf = vec![4u8, 1];
424        buf.extend_from_slice(&0x5000u64.to_le_bytes());
425        buf.extend_from_slice(&512u64.to_le_bytes());
426        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
427        assert_eq!(
428            layout,
429            DataLayout::Contiguous {
430                address: Some(0x5000),
431                size: 512,
432            }
433        );
434    }
435
436    #[test]
437    fn v4_chunked_single_chunk_no_filters() {
438        let mut buf = vec![4u8, 2]; // version=4, class=2
439        buf.push(0); // flags (no filters)
440        buf.push(2); // dimensionality=2
441        buf.push(4); // dim_size_encoded_length=4
442        buf.extend_from_slice(&64u32.to_le_bytes()); // dim 0
443        buf.extend_from_slice(&32u32.to_le_bytes()); // dim 1
444        buf.push(1); // chunk_index_type=1 (single chunk)
445        buf.extend_from_slice(&0x3000u64.to_le_bytes()); // chunk address
446        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
447        assert_eq!(
448            layout,
449            DataLayout::Chunked {
450                chunk_dimensions: vec![64, 32],
451                btree_address: Some(0x3000),
452                version: 4,
453                chunk_index_type: Some(1),
454                single_chunk_filtered_size: None,
455                single_chunk_filter_mask: None,
456            }
457        );
458    }
459
460    #[test]
461    fn v4_chunked_single_chunk_with_filters() {
462        let mut buf = vec![4u8, 2]; // version=4, class=2
463        buf.push(0x02); // flags bit 1 = single chunk with filter
464        buf.push(1); // dimensionality=1
465        buf.push(4); // dim_size_encoded_length=4
466        buf.extend_from_slice(&128u32.to_le_bytes()); // dim 0
467        buf.push(1); // chunk_index_type=1 (single chunk)
468        // filters present: filtered_size(8) + filter_mask(4) + address(8)
469        buf.extend_from_slice(&1024u64.to_le_bytes()); // filtered size
470        buf.extend_from_slice(&0u32.to_le_bytes()); // filter mask
471        buf.extend_from_slice(&0x4000u64.to_le_bytes()); // address
472        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
473        assert_eq!(
474            layout,
475            DataLayout::Chunked {
476                chunk_dimensions: vec![128],
477                btree_address: Some(0x4000),
478                version: 4,
479                chunk_index_type: Some(1),
480                single_chunk_filtered_size: Some(1024),
481                single_chunk_filter_mask: Some(0),
482            }
483        );
484    }
485
486    #[test]
487    fn invalid_version() {
488        let buf = vec![5u8, 0, 0, 0];
489        let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
490        assert_eq!(err, FormatError::InvalidLayoutVersion(5));
491    }
492
493    #[test]
494    fn invalid_class_v3() {
495        let buf = vec![3u8, 5];
496        let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
497        assert_eq!(err, FormatError::InvalidLayoutClass(5));
498    }
499
500    #[test]
501    fn invalid_class_v4() {
502        let buf = vec![4u8, 7];
503        let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
504        assert_eq!(err, FormatError::InvalidLayoutClass(7));
505    }
506
507    #[test]
508    fn v3_contiguous_4byte_offsets() {
509        let mut buf = vec![3u8, 1];
510        buf.extend_from_slice(&0x800u32.to_le_bytes());
511        buf.extend_from_slice(&24u32.to_le_bytes());
512        let layout = DataLayout::parse(&buf, 4, 4).unwrap();
513        assert_eq!(
514            layout,
515            DataLayout::Contiguous {
516                address: Some(0x800),
517                size: 24,
518            }
519        );
520    }
521
522    #[test]
523    fn v4_virtual() {
524        let buf = vec![4u8, 3];
525        let layout = DataLayout::parse(&buf, 8, 8).unwrap();
526        assert_eq!(layout, DataLayout::Virtual { version: 4 });
527    }
528}