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 let dim = cursor.read_uvar(dim_bytes)?;
225 let dim = u32::try_from(dim).map_err(|_| {
226 Error::InvalidData(format!("chunk dimension {dim} exceeds u32 capacity"))
227 })?;
228 dims.push(dim);
229 }
230
231 let index_type = cursor.read_u8()?;
232 let chunk_size_len = if version >= 5 {
233 offset_size
234 } else {
235 length_size
236 };
237 let chunk_indexing = parse_chunk_indexing_v4_v5(cursor, flags, index_type, chunk_size_len)?;
238 let address = cursor.read_offset(offset_size)?;
239
240 Ok(DataLayout::Chunked {
241 address,
242 dims,
243 element_size: 0,
244 chunk_indexing: Some(chunk_indexing),
245 })
246}
247
248fn should_retry_v4_chunked_parse(err: &Error) -> bool {
249 match err {
250 Error::UnexpectedEof { .. } | Error::UnsupportedChunkIndexType(_) => true,
251 Error::InvalidData(msg) => msg.starts_with("unsupported variable integer size:"),
252 _ => false,
253 }
254}
255
256fn parse_chunk_indexing_v4_v5(
259 cursor: &mut Cursor<'_>,
260 flags: u8,
261 index_type: u8,
262 chunk_size_len: u8,
263) -> Result<ChunkIndexing> {
264 match index_type {
265 1 => {
266 let idx_flags = if (flags & 0x02) != 0 {
270 let filtered_size = cursor.read_u64_le()?;
271 let filter_mask = cursor.read_u32_le()?;
272 Some((filtered_size, filter_mask))
273 } else {
274 None
275 };
276 let (fs, fm) = idx_flags.unwrap_or((0, 0));
277 Ok(ChunkIndexing::SingleChunk {
278 filtered_size: fs,
279 filters: fm,
280 })
281 }
282 2 => Ok(ChunkIndexing::Implicit),
283 3 => {
284 let page_bits = cursor.read_u8()?;
285 Ok(ChunkIndexing::FixedArray {
286 page_bits,
287 chunk_size_len,
288 })
289 }
290 4 => {
291 let max_bits = cursor.read_u8()?;
292 let index_bits = cursor.read_u8()?;
293 let min_pointers = cursor.read_u8()?;
294 let min_elements = cursor.read_u8()?;
295 let _max_dblk_page_bits = cursor.read_u8()?;
296 Ok(ChunkIndexing::ExtensibleArray {
297 max_bits,
298 index_bits,
299 min_pointers,
300 min_elements,
301 chunk_size_len,
302 })
303 }
304 5 => {
305 cursor.skip(6)?;
306 Ok(ChunkIndexing::BTreeV2)
307 }
308 t => Err(Error::UnsupportedChunkIndexType(t)),
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn parse_v3_contiguous() {
318 let mut data = vec![
319 0x03, 0x01, ];
322 data.extend_from_slice(&0x1000u64.to_le_bytes());
324 data.extend_from_slice(&4096u64.to_le_bytes());
326
327 let mut cursor = Cursor::new(&data);
328 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
329 match &msg.layout {
330 DataLayout::Contiguous { address, size } => {
331 assert_eq!(*address, 0x1000);
332 assert_eq!(*size, 4096);
333 }
334 other => panic!("expected Contiguous, got {:?}", other),
335 }
336 }
337
338 #[test]
339 fn parse_v3_compact() {
340 let mut data = vec![
341 0x03, 0x00, ];
344 data.extend_from_slice(&4u16.to_le_bytes());
346 data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]);
348
349 let mut cursor = Cursor::new(&data);
350 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
351 match &msg.layout {
352 DataLayout::Compact { data } => {
353 assert_eq!(data, &[0x01, 0x02, 0x03, 0x04]);
354 }
355 other => panic!("expected Compact, got {:?}", other),
356 }
357 }
358
359 #[test]
360 fn parse_v3_chunked() {
361 let mut data = vec![
362 0x03, 0x02, 0x03, ];
366 data.extend_from_slice(&0x2000u64.to_le_bytes());
368 data.extend_from_slice(&256u32.to_le_bytes());
370 data.extend_from_slice(&128u32.to_le_bytes());
372 data.extend_from_slice(&4u32.to_le_bytes());
374
375 let mut cursor = Cursor::new(&data);
376 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
377 match &msg.layout {
378 DataLayout::Chunked {
379 address,
380 dims,
381 element_size,
382 chunk_indexing,
383 } => {
384 assert_eq!(*address, 0x2000);
385 assert_eq!(dims, &[256, 128]);
386 assert_eq!(*element_size, 4);
387 assert!(chunk_indexing.is_none());
388 }
389 other => panic!("expected Chunked, got {:?}", other),
390 }
391 }
392
393 #[test]
394 fn parse_v4_chunked_direct_dim_size_encoding() {
395 let mut data = vec![
396 0x04, 0x02, 0x00, 0x02, 0x04, ];
402 data.extend_from_slice(&3u32.to_le_bytes());
403 data.extend_from_slice(&5u32.to_le_bytes());
404 data.push(0x03); data.push(0x00); data.extend_from_slice(&0x1122_3344_5566_7788u64.to_le_bytes());
407
408 let mut cursor = Cursor::new(&data);
409 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
410 match &msg.layout {
411 DataLayout::Chunked {
412 address,
413 dims,
414 element_size,
415 chunk_indexing,
416 } => {
417 assert_eq!(*address, 0x1122_3344_5566_7788);
418 assert_eq!(dims, &[3, 5]);
419 assert_eq!(*element_size, 0);
420 match chunk_indexing {
421 Some(ChunkIndexing::FixedArray {
422 page_bits,
423 chunk_size_len,
424 }) => {
425 assert_eq!(*page_bits, 0);
426 assert_eq!(*chunk_size_len, 8);
427 }
428 other => panic!("expected FixedArray indexing, got {:?}", other),
429 }
430 }
431 other => panic!("expected Chunked, got {:?}", other),
432 }
433 }
434
435 #[test]
436 fn parse_v4_chunked_legacy_dim_size_encoding() {
437 let mut data = vec![
438 0x04, 0x02, 0x00, 0x02, 0x03, ];
444 data.extend_from_slice(&3u32.to_le_bytes());
445 data.extend_from_slice(&5u32.to_le_bytes());
446 data.push(0x03); data.push(0x00); data.extend_from_slice(&0x8877_6655_4433_2211u64.to_le_bytes());
449
450 let mut cursor = Cursor::new(&data);
451 let msg = parse(&mut cursor, 8, 8, data.len()).unwrap();
452 match &msg.layout {
453 DataLayout::Chunked {
454 address,
455 dims,
456 element_size,
457 chunk_indexing,
458 } => {
459 assert_eq!(*address, 0x8877_6655_4433_2211);
460 assert_eq!(dims, &[3, 5]);
461 assert_eq!(*element_size, 0);
462 match chunk_indexing {
463 Some(ChunkIndexing::FixedArray {
464 page_bits,
465 chunk_size_len,
466 }) => {
467 assert_eq!(*page_bits, 0);
468 assert_eq!(*chunk_size_len, 8);
469 }
470 other => panic!("expected FixedArray indexing, got {:?}", other),
471 }
472 }
473 other => panic!("expected Chunked, got {:?}", other),
474 }
475 }
476}