1use crate::error::{Error, Result};
8use crate::io::Cursor;
9pub use hdf5_core::{ChunkIndexing, DataLayout, DataLayoutMessage};
10
11pub fn parse(
13 cursor: &mut Cursor<'_>,
14 offset_size: u8,
15 length_size: u8,
16 msg_size: usize,
17) -> Result<DataLayoutMessage> {
18 let start = cursor.position();
19 let version = cursor.read_u8()?;
20
21 let layout = match version {
22 1 | 2 => parse_v1_v2(cursor, offset_size, length_size, version)?,
23 3 => parse_v3(cursor, offset_size, length_size)?,
24 4 | 5 => parse_v4_v5(cursor, offset_size, length_size, version)?,
25 v => return Err(Error::UnsupportedLayoutVersion(v)),
26 };
27
28 let consumed = (cursor.position() - start) as usize;
29 if consumed < msg_size {
30 cursor.skip(msg_size - consumed)?;
31 }
32
33 Ok(DataLayoutMessage { layout })
34}
35
36fn parse_v1_v2(
41 cursor: &mut Cursor<'_>,
42 offset_size: u8,
43 _length_size: u8,
44 version: u8,
45) -> Result<DataLayout> {
46 let dimensionality = cursor.read_u8()?;
47 let layout_class = cursor.read_u8()?;
48 let _reserved = cursor.read_bytes(if version == 1 { 5 } else { 3 })?;
49
50 let data_address = if layout_class != 0 {
53 cursor.read_offset(offset_size)?
54 } else {
55 cursor.read_offset(offset_size)?
57 };
58
59 let mut dim_values = Vec::with_capacity(dimensionality as usize);
63 for _ in 0..dimensionality {
64 dim_values.push(cursor.read_u32_le()?);
65 }
66
67 match layout_class {
68 0 => {
69 let compact_size = cursor.read_u32_le()? as usize;
71 let data = cursor.read_bytes(compact_size)?.to_vec();
72 Ok(DataLayout::Compact { data })
73 }
74 1 => {
75 let size = if dim_values.is_empty() {
80 0
81 } else {
82 dim_values.iter().map(|d| *d as u64).product()
83 };
84 Ok(DataLayout::Contiguous {
85 address: data_address,
86 size,
87 })
88 }
89 2 => {
90 let (element_size, chunk_dims) = if dim_values.is_empty() {
92 (0u32, vec![])
93 } else {
94 let es = *dim_values.last().unwrap();
95 let cd: Vec<u32> = dim_values[..dim_values.len() - 1].to_vec();
96 (es, cd)
97 };
98 Ok(DataLayout::Chunked {
99 address: data_address,
100 dims: chunk_dims,
101 element_size,
102 chunk_indexing: None,
103 })
104 }
105 c => Err(Error::UnsupportedLayoutClass(c)),
106 }
107}
108
109fn parse_v3(cursor: &mut Cursor<'_>, offset_size: u8, length_size: u8) -> Result<DataLayout> {
114 let layout_class = cursor.read_u8()?;
115
116 match layout_class {
117 0 => {
118 let size = cursor.read_u16_le()? as usize;
120 let data = cursor.read_bytes(size)?.to_vec();
121 Ok(DataLayout::Compact { data })
122 }
123 1 => {
124 let address = cursor.read_offset(offset_size)?;
126 let size = cursor.read_length(length_size)?;
127 Ok(DataLayout::Contiguous { address, size })
128 }
129 2 => {
130 let dimensionality = cursor.read_u8()?;
132 let address = cursor.read_offset(offset_size)?;
133
134 let n = dimensionality as usize;
136 let mut raw_dims = Vec::with_capacity(n);
137 for _ in 0..n {
138 raw_dims.push(cursor.read_u32_le()?);
139 }
140
141 let (element_size, chunk_dims) = if raw_dims.is_empty() {
142 (0, vec![])
143 } else {
144 let es = *raw_dims.last().unwrap();
145 let cd = raw_dims[..raw_dims.len() - 1].to_vec();
146 (es, cd)
147 };
148
149 Ok(DataLayout::Chunked {
150 address,
151 dims: chunk_dims,
152 element_size,
153 chunk_indexing: None,
154 })
155 }
156 c => Err(Error::UnsupportedLayoutClass(c)),
157 }
158}
159
160fn parse_v4_v5(
170 cursor: &mut Cursor<'_>,
171 offset_size: u8,
172 length_size: u8,
173 version: u8,
174) -> Result<DataLayout> {
175 let layout_class = cursor.read_u8()?;
176
177 match layout_class {
178 0 => {
179 let size = cursor.read_u16_le()? as usize;
181 let data = cursor.read_bytes(size)?.to_vec();
182 Ok(DataLayout::Compact { data })
183 }
184 1 => {
185 let address = cursor.read_offset(offset_size)?;
187 let size = cursor.read_u64_le()?;
188 Ok(DataLayout::Contiguous { address, size })
189 }
190 2 => {
191 let start = cursor.clone();
192 let direct = parse_v4_v5_chunked(cursor, offset_size, length_size, version, false);
193 match direct {
194 Ok(layout) => Ok(layout),
195 Err(err) if version == 4 && should_retry_v4_chunked_parse(&err) => {
196 *cursor = start;
197 parse_v4_v5_chunked(cursor, offset_size, length_size, version, true)
198 }
199 Err(err) => Err(err),
200 }
201 }
202 c => Err(Error::UnsupportedLayoutClass(c)),
203 }
204}
205
206fn parse_v4_v5_chunked(
207 cursor: &mut Cursor<'_>,
208 offset_size: u8,
209 length_size: u8,
210 version: u8,
211 legacy_dim_size_encoding: bool,
212) -> Result<DataLayout> {
213 let flags = cursor.read_u8()?;
214 let ndims_raw = cursor.read_u8()? as usize;
215 let dim_size_enc = cursor.read_u8()?;
216 let dim_bytes = if legacy_dim_size_encoding {
217 dim_size_enc as usize + 1
218 } else {
219 dim_size_enc as usize
220 };
221
222 let mut dims = Vec::with_capacity(ndims_raw);
223 for _ in 0..ndims_raw {
224 dims.push(cursor.read_uvar(dim_bytes)? as u32);
225 }
226
227 let index_type = cursor.read_u8()?;
228 let chunk_size_len = if version >= 5 {
229 offset_size
230 } else {
231 length_size
232 };
233 let chunk_indexing = parse_chunk_indexing_v4_v5(cursor, flags, index_type, chunk_size_len)?;
234 let address = cursor.read_offset(offset_size)?;
235
236 Ok(DataLayout::Chunked {
237 address,
238 dims,
239 element_size: 0,
240 chunk_indexing: Some(chunk_indexing),
241 })
242}
243
244fn should_retry_v4_chunked_parse(err: &Error) -> bool {
245 match err {
246 Error::UnexpectedEof { .. } | Error::UnsupportedChunkIndexType(_) => true,
247 Error::InvalidData(msg) => msg.starts_with("unsupported variable integer size:"),
248 _ => false,
249 }
250}
251
252fn parse_chunk_indexing_v4_v5(
255 cursor: &mut Cursor<'_>,
256 flags: u8,
257 index_type: u8,
258 chunk_size_len: u8,
259) -> Result<ChunkIndexing> {
260 match index_type {
261 1 => {
262 let idx_flags = if (flags & 0x01) != 0 {
264 let filtered_size = cursor.read_u64_le()?;
265 let filter_mask = cursor.read_u32_le()?;
266 Some((filtered_size, filter_mask))
267 } else {
268 None
269 };
270 let (fs, fm) = idx_flags.unwrap_or((0, 0));
271 Ok(ChunkIndexing::SingleChunk {
272 filtered_size: fs,
273 filters: fm,
274 })
275 }
276 2 => Ok(ChunkIndexing::Implicit),
277 3 => {
278 let page_bits = cursor.read_u8()?;
279 Ok(ChunkIndexing::FixedArray {
280 page_bits,
281 chunk_size_len,
282 })
283 }
284 4 => {
285 let max_bits = cursor.read_u8()?;
286 let index_bits = cursor.read_u8()?;
287 let min_pointers = cursor.read_u8()?;
288 let min_elements = cursor.read_u8()?;
289 let _max_dblk_page_bits = cursor.read_u8()?;
290 Ok(ChunkIndexing::ExtensibleArray {
291 max_bits,
292 index_bits,
293 min_pointers,
294 min_elements,
295 chunk_size_len,
296 })
297 }
298 5 => {
299 cursor.skip(6)?;
300 Ok(ChunkIndexing::BTreeV2)
301 }
302 t => Err(Error::UnsupportedChunkIndexType(t)),
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn parse_v3_contiguous() {
312 let mut data = vec![
313 0x03, 0x01, ];
316 data.extend_from_slice(&0x1000u64.to_le_bytes());
318 data.extend_from_slice(&4096u64.to_le_bytes());
320
321 let mut cursor = Cursor::new(&data);
322 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
323 match &msg.layout {
324 DataLayout::Contiguous { address, size } => {
325 assert_eq!(*address, 0x1000);
326 assert_eq!(*size, 4096);
327 }
328 other => panic!("expected Contiguous, got {:?}", other),
329 }
330 }
331
332 #[test]
333 fn parse_v3_compact() {
334 let mut data = vec![
335 0x03, 0x00, ];
338 data.extend_from_slice(&4u16.to_le_bytes());
340 data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
342
343 let mut cursor = Cursor::new(&data);
344 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
345 match &msg.layout {
346 DataLayout::Compact { data } => {
347 assert_eq!(data, &[0x01, 0x02, 0x03, 0x04]);
348 }
349 other => panic!("expected Compact, got {:?}", other),
350 }
351 }
352
353 #[test]
354 fn parse_v3_chunked() {
355 let mut data = vec![
356 0x03, 0x02, 0x03, ];
360 data.extend_from_slice(&0x2000u64.to_le_bytes());
362 data.extend_from_slice(&256u32.to_le_bytes());
364 data.extend_from_slice(&128u32.to_le_bytes());
366 data.extend_from_slice(&4u32.to_le_bytes());
368
369 let mut cursor = Cursor::new(&data);
370 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
371 match &msg.layout {
372 DataLayout::Chunked {
373 address,
374 dims,
375 element_size,
376 chunk_indexing,
377 } => {
378 assert_eq!(*address, 0x2000);
379 assert_eq!(dims, &[256, 128]);
380 assert_eq!(*element_size, 4);
381 assert!(chunk_indexing.is_none());
382 }
383 other => panic!("expected Chunked, got {:?}", other),
384 }
385 }
386
387 #[test]
388 fn parse_v4_chunked_direct_dim_size_encoding() {
389 let mut data = vec![
390 0x04, 0x02, 0x00, 0x02, 0x04, ];
396 data.extend_from_slice(&3u32.to_le_bytes());
397 data.extend_from_slice(&5u32.to_le_bytes());
398 data.push(0x03); data.push(0x00); data.extend_from_slice(&0x1122_3344_5566_7788u64.to_le_bytes());
401
402 let mut cursor = Cursor::new(&data);
403 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
404 match &msg.layout {
405 DataLayout::Chunked {
406 address,
407 dims,
408 element_size,
409 chunk_indexing,
410 } => {
411 assert_eq!(*address, 0x1122_3344_5566_7788);
412 assert_eq!(dims, &[3, 5]);
413 assert_eq!(*element_size, 0);
414 match chunk_indexing {
415 Some(ChunkIndexing::FixedArray {
416 page_bits,
417 chunk_size_len,
418 }) => {
419 assert_eq!(*page_bits, 0);
420 assert_eq!(*chunk_size_len, 8);
421 }
422 other => panic!("expected FixedArray indexing, got {:?}", other),
423 }
424 }
425 other => panic!("expected Chunked, got {:?}", other),
426 }
427 }
428
429 #[test]
430 fn parse_v4_chunked_legacy_dim_size_encoding() {
431 let mut data = vec![
432 0x04, 0x02, 0x00, 0x02, 0x03, ];
438 data.extend_from_slice(&3u32.to_le_bytes());
439 data.extend_from_slice(&5u32.to_le_bytes());
440 data.push(0x03); data.push(0x00); data.extend_from_slice(&0x8877_6655_4433_2211u64.to_le_bytes());
443
444 let mut cursor = Cursor::new(&data);
445 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
446 match &msg.layout {
447 DataLayout::Chunked {
448 address,
449 dims,
450 element_size,
451 chunk_indexing,
452 } => {
453 assert_eq!(*address, 0x8877_6655_4433_2211);
454 assert_eq!(dims, &[3, 5]);
455 assert_eq!(*element_size, 0);
456 match chunk_indexing {
457 Some(ChunkIndexing::FixedArray {
458 page_bits,
459 chunk_size_len,
460 }) => {
461 assert_eq!(*page_bits, 0);
462 assert_eq!(*chunk_size_len, 8);
463 }
464 other => panic!("expected FixedArray indexing, got {:?}", other),
465 }
466 }
467 other => panic!("expected Chunked, got {:?}", other),
468 }
469 }
470}