1use std::mem::MaybeUninit;
2use std::num::NonZeroUsize;
3use std::sync::{Arc, OnceLock};
4
5use lru::LruCache;
6use ndarray::{ArrayD, IxDyn};
7use parking_lot::Mutex;
8#[cfg(feature = "rayon")]
9use rayon::prelude::*;
10use smallvec::SmallVec;
11
12use crate::attribute_api::{
13 collect_attribute_messages_storage, decode_string, decoded_vlen_strings_storage,
14 read_one_vlen_string_storage, resolve_vlen_bytes_storage, Attribute,
15};
16use crate::cache::{ChunkCache, ChunkCacheStats, ChunkKey};
17use crate::chunk_index;
18use crate::datatype_api::H5Type;
19use crate::error::{ByteOrder, Error, Result};
20use crate::filters::{self, FilterRegistry};
21use crate::io::Cursor;
22use crate::local_heap::LocalHeap;
23use crate::messages::attribute::AttributeMessage;
24use crate::messages::dataspace::{DataspaceMessage, DataspaceType};
25use crate::messages::datatype::{Datatype, StringSize, VarLenKind};
26use crate::messages::external_files::ExternalFilesMessage;
27use crate::messages::fill_value::{FillTime, FillValueMessage};
28use crate::messages::filter_pipeline::FilterPipelineMessage;
29use crate::messages::layout::{ChunkIndexing, DataLayout};
30use crate::messages::HdfMessage;
31use crate::object_header::ObjectHeader;
32use crate::storage::DynStorage;
33use crate::FileContext;
34
35const HOT_FULL_DATASET_CACHE_MAX_BYTES: usize = 32 * 1024 * 1024;
36
37#[derive(Clone, Copy)]
38struct FlatBufferPtr {
39 ptr: *mut u8,
40 len: usize,
41}
42
43#[derive(Clone, Copy)]
44struct ChunkCopyLayout<'a> {
45 chunk_offsets: &'a [u64],
46 chunk_shape: &'a [u64],
47 dataset_shape: &'a [u64],
48 dataset_strides: &'a [usize],
49 chunk_strides: &'a [usize],
50 elem_size: usize,
51}
52
53#[derive(Clone, Copy)]
54struct UnitStrideCopyLayout<'a> {
55 chunk_offsets: &'a [u64],
56 chunk_shape: &'a [u64],
57 dataset_shape: &'a [u64],
58 resolved: &'a ResolvedSelection,
59 chunk_strides: &'a [usize],
60 result_strides: &'a [usize],
61 elem_size: usize,
62}
63
64#[derive(Clone, Copy)]
65struct ContiguousSliceDirectLayout<'a> {
66 dataset_strides: &'a [usize],
67 result_strides: &'a [usize],
68 elem_size: usize,
69 result_total_bytes: usize,
70}
71
72#[derive(Clone)]
73struct ResolvedExternalRawSlot {
74 logical_offset: u64,
75 storage: DynStorage,
76 file_offset: u64,
77 size: u64,
78}
79
80pub(crate) struct DatasetParseContext {
81 pub(crate) context: Arc<FileContext>,
82}
83
84#[derive(Clone, Copy)]
85struct ChunkEntrySelection<'a> {
86 shape: &'a [u64],
87 ndim: usize,
88 elem_size: usize,
89 chunk_bounds: Option<(&'a [u64], &'a [u64])>,
90}
91
92unsafe impl Send for FlatBufferPtr {}
93
94unsafe impl Sync for FlatBufferPtr {}
95
96impl FlatBufferPtr {
97 #[cfg(feature = "rayon")]
98 #[inline(always)]
99 unsafe fn copy_chunk(self, chunk_data: &[u8], layout: ChunkCopyLayout<'_>) -> Result<()> {
100 copy_chunk_to_flat_with_strides_ptr(chunk_data, self, layout)
101 }
102
103 #[cfg(feature = "rayon")]
104 #[inline(always)]
105 unsafe fn copy_selected(
106 self,
107 chunk_data: &[u8],
108 dim_indices: &[Vec<(usize, usize)>],
109 chunk_strides: &[usize],
110 result_strides: &[usize],
111 elem_size: usize,
112 ndim: usize,
113 ) -> Result<()> {
114 copy_selected_elements_ptr(
115 chunk_data,
116 self.ptr,
117 self.len,
118 dim_indices,
119 chunk_strides,
120 result_strides,
121 elem_size,
122 ndim,
123 )
124 }
125
126 #[cfg(feature = "rayon")]
127 #[inline(always)]
128 unsafe fn copy_unit_stride_chunk_overlap(
129 self,
130 chunk_data: &[u8],
131 layout: UnitStrideCopyLayout<'_>,
132 ) -> Result<()> {
133 copy_unit_stride_chunk_overlap_ptr(chunk_data, self, layout)
134 }
135}
136
137#[derive(Debug, Clone)]
139pub struct SliceInfo {
140 pub selections: Vec<SliceInfoElem>,
141}
142
143#[derive(Debug, Clone)]
145pub enum SliceInfoElem {
146 Index(u64),
148 Slice { start: u64, end: u64, step: u64 },
150}
151
152#[derive(Clone, Debug)]
153struct ResolvedSelectionDim {
154 start: u64,
155 end: u64,
156 step: u64,
157 count: usize,
158}
159
160#[derive(Clone, Debug, PartialEq, Eq, Hash)]
161struct ChunkEntryCacheKey {
162 index_address: u64,
163 first_chunk: SmallVec<[u64; 4]>,
164 last_chunk: SmallVec<[u64; 4]>,
165}
166
167impl ResolvedSelectionDim {
168 fn chunk_index_range(&self, chunk_extent: u64) -> Option<(u64, u64)> {
169 if self.count == 0 {
170 return None;
171 }
172
173 Some((self.start / chunk_extent, (self.end - 1) / chunk_extent))
174 }
175}
176
177#[derive(Clone, Debug)]
178struct ResolvedSelection {
179 dims: Vec<ResolvedSelectionDim>,
180 result_shape: Vec<usize>,
181 result_elements: usize,
182}
183
184impl ResolvedSelection {
185 fn result_dims_with_collapsed(&self) -> Vec<usize> {
186 self.dims.iter().map(|dim| dim.count).collect()
187 }
188
189 fn is_unit_stride(&self) -> bool {
190 self.dims.iter().all(|dim| dim.step == 1)
191 }
192}
193
194impl SliceInfo {
195 pub fn all(ndim: usize) -> Self {
197 SliceInfo {
198 selections: vec![
199 SliceInfoElem::Slice {
200 start: 0,
201 end: u64::MAX,
202 step: 1,
203 };
204 ndim
205 ],
206 }
207 }
208}
209
210fn checked_usize(value: u64, context: &str) -> Result<usize> {
211 usize::try_from(value).map_err(|_| {
212 Error::InvalidData(format!(
213 "{context} value {value} exceeds platform usize capacity"
214 ))
215 })
216}
217
218fn checked_mul_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
219 lhs.checked_mul(rhs)
220 .ok_or_else(|| Error::InvalidData(format!("{context} exceeds platform usize capacity")))
221}
222
223fn checked_add_usize(lhs: usize, rhs: usize, context: &str) -> Result<usize> {
224 lhs.checked_add(rhs)
225 .ok_or_else(|| Error::InvalidData(format!("{context} exceeds platform usize capacity")))
226}
227
228fn checked_mul_u64(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
229 lhs.checked_mul(rhs)
230 .ok_or_else(|| Error::InvalidData(format!("{context} exceeds u64 capacity")))
231}
232
233fn checked_add_u64(lhs: u64, rhs: u64, context: &str) -> Result<u64> {
234 lhs.checked_add(rhs)
235 .ok_or_else(|| Error::InvalidData(format!("{context} exceeds u64 capacity")))
236}
237
238fn checked_shape_elements_usize(shape: &[u64], context: &str) -> Result<usize> {
239 let mut total = 1usize;
240 for &dim in shape {
241 total = checked_mul_usize(total, checked_usize(dim, context)?, context)?;
242 }
243 Ok(total)
244}
245
246fn full_dataset_chunk_bounds(
247 shape: &[u64],
248 chunk_shape: &[u64],
249) -> Result<Option<(Vec<u64>, Vec<u64>)>> {
250 validate_chunk_shape(shape, chunk_shape)?;
251 if shape.contains(&0) {
252 return Ok(None);
253 }
254
255 let first_chunk = vec![0u64; shape.len()];
256 let last_chunk = shape
257 .iter()
258 .zip(chunk_shape.iter())
259 .map(|(&dim, &chunk)| dim.div_ceil(chunk) - 1)
260 .collect();
261 Ok(Some((first_chunk, last_chunk)))
262}
263
264fn validate_chunk_shape(shape: &[u64], chunk_shape: &[u64]) -> Result<()> {
265 if chunk_shape.len() != shape.len() {
266 return Err(Error::InvalidData(format!(
267 "chunk rank {} does not match dataset rank {}",
268 chunk_shape.len(),
269 shape.len()
270 )));
271 }
272 if let Some((dim, _)) = chunk_shape
273 .iter()
274 .enumerate()
275 .find(|(_, chunk)| **chunk == 0)
276 {
277 return Err(Error::InvalidData(format!(
278 "chunk dimension {dim} has zero extent"
279 )));
280 }
281 Ok(())
282}
283
284fn validate_decoded_chunk_len(
285 entry: &chunk_index::ChunkEntry,
286 chunk_shape: &[u64],
287 elem_size: usize,
288 actual_len: usize,
289) -> Result<()> {
290 let expected_len = decoded_chunk_expected_len(chunk_shape, elem_size)?;
291 if actual_len != expected_len {
292 return Err(Error::InvalidData(format!(
293 "chunk at offsets {:?} decoded to {} bytes, expected {} bytes",
294 entry.offsets, actual_len, expected_len
295 )));
296 }
297 Ok(())
298}
299
300fn decoded_chunk_expected_len(chunk_shape: &[u64], elem_size: usize) -> Result<usize> {
301 let chunk_elements = checked_shape_elements_usize(chunk_shape, "decoded chunk element count")?;
302 checked_mul_usize(chunk_elements, elem_size, "decoded chunk byte length")
303}
304
305fn validate_chunk_grid_coverage(
306 entries: &mut [chunk_index::ChunkEntry],
307 shape: &[u64],
308 chunk_shape: &[u64],
309 first_chunk: &[u64],
310 last_chunk: &[u64],
311) -> Result<bool> {
312 validate_chunk_shape(shape, chunk_shape)?;
313 if first_chunk.len() != shape.len() || last_chunk.len() != shape.len() {
314 return Err(Error::InvalidData(format!(
315 "chunk grid bounds rank does not match dataset rank {}",
316 shape.len()
317 )));
318 }
319
320 if shape.contains(&0) {
321 if entries.is_empty() {
322 return Ok(true);
323 }
324 return Err(Error::InvalidData(
325 "chunk index contains entries for an empty dataset".into(),
326 ));
327 }
328
329 for dim in 0..shape.len() {
330 if first_chunk[dim] > last_chunk[dim] {
331 return Err(Error::InvalidData(format!(
332 "invalid chunk grid bounds for dimension {dim}: {} > {}",
333 first_chunk[dim], last_chunk[dim]
334 )));
335 }
336 }
337
338 entries.sort_by(|a, b| a.offsets.cmp(&b.offsets));
339
340 for i in 0..entries.len() {
341 validate_chunk_entry_offsets(&entries[i], shape, chunk_shape, first_chunk, last_chunk)?;
342 if i > 0 && entries[i].offsets == entries[i - 1].offsets {
343 return Err(Error::InvalidData(format!(
344 "duplicate chunk output offsets {:?} (addresses {:#x} and {:#x})",
345 entries[i].offsets,
346 entries[i - 1].address,
347 entries[i].address
348 )));
349 }
350 }
351
352 let mut entry_idx = 0usize;
353 let mut expected = first_chunk.to_vec();
354 loop {
355 let expected_offsets: Vec<u64> = expected
356 .iter()
357 .enumerate()
358 .map(|(dim, chunk_index)| chunk_index * chunk_shape[dim])
359 .collect();
360
361 if entry_idx >= entries.len() || entries[entry_idx].offsets != expected_offsets {
362 return Ok(false);
363 }
364 entry_idx += 1;
365
366 if !advance_chunk_index(&mut expected, first_chunk, last_chunk) {
367 break;
368 }
369 }
370
371 Ok(entry_idx == entries.len())
372}
373
374fn validate_chunk_entry_offsets(
375 entry: &chunk_index::ChunkEntry,
376 shape: &[u64],
377 chunk_shape: &[u64],
378 first_chunk: &[u64],
379 last_chunk: &[u64],
380) -> Result<()> {
381 if entry.offsets.len() != shape.len() {
382 return Err(Error::InvalidData(format!(
383 "chunk at address {:#x} has rank {}, expected {}",
384 entry.address,
385 entry.offsets.len(),
386 shape.len()
387 )));
388 }
389
390 for dim in 0..shape.len() {
391 let offset = entry.offsets[dim];
392 if offset >= shape[dim] {
393 return Err(Error::InvalidData(format!(
394 "chunk at address {:#x} has out-of-bounds offset {} for dimension {} of size {}",
395 entry.address, offset, dim, shape[dim]
396 )));
397 }
398 if offset % chunk_shape[dim] != 0 {
399 return Err(Error::InvalidData(format!(
400 "chunk at address {:#x} has non-grid offset {} for dimension {} with chunk extent {}",
401 entry.address, offset, dim, chunk_shape[dim]
402 )));
403 }
404
405 let chunk_index = offset / chunk_shape[dim];
406 if chunk_index < first_chunk[dim] || chunk_index > last_chunk[dim] {
407 return Err(Error::InvalidData(format!(
408 "chunk at address {:#x} has offset {:?} outside requested chunk grid",
409 entry.address, entry.offsets
410 )));
411 }
412 }
413
414 Ok(())
415}
416
417fn advance_chunk_index(index: &mut [u64], first_chunk: &[u64], last_chunk: &[u64]) -> bool {
418 if index.is_empty() {
419 return false;
420 }
421
422 for dim in (0..index.len()).rev() {
423 if index[dim] < last_chunk[dim] {
424 index[dim] += 1;
425 if dim + 1 < index.len() {
426 index[(dim + 1)..].copy_from_slice(&first_chunk[(dim + 1)..]);
427 }
428 return true;
429 }
430 }
431
432 false
433}
434
435fn row_major_strides(shape: &[u64], context: &str) -> Result<Vec<usize>> {
436 let ndim = shape.len();
437 if ndim == 0 {
438 return Ok(Vec::new());
439 }
440
441 let mut strides = vec![1usize; ndim];
442 for i in (0..ndim - 1).rev() {
443 let next_extent = checked_usize(shape[i + 1], context)?;
444 strides[i] = checked_mul_usize(strides[i + 1], next_extent, context)?;
445 }
446 Ok(strides)
447}
448
449fn assume_init_u8_vec(mut buffer: Vec<MaybeUninit<u8>>) -> Vec<u8> {
450 let ptr = buffer.as_mut_ptr() as *mut u8;
451 let len = buffer.len();
452 let capacity = buffer.capacity();
453 std::mem::forget(buffer);
454 unsafe { Vec::from_raw_parts(ptr, len, capacity) }
455}
456
457fn assume_init_vec<T>(mut buffer: Vec<MaybeUninit<T>>) -> Vec<T> {
458 let ptr = buffer.as_mut_ptr() as *mut T;
459 let len = buffer.len();
460 let capacity = buffer.capacity();
461 std::mem::forget(buffer);
462 unsafe { Vec::from_raw_parts(ptr, len, capacity) }
463}
464
465fn normalize_selection(selection: &SliceInfo, shape: &[u64]) -> Result<ResolvedSelection> {
466 if selection.selections.len() != shape.len() {
467 return Err(Error::InvalidData(format!(
468 "slice has {} dimensions but dataset has {}",
469 selection.selections.len(),
470 shape.len()
471 )));
472 }
473
474 let mut dims = Vec::with_capacity(shape.len());
475 let mut result_shape = Vec::new();
476 let mut result_elements = 1usize;
477
478 for (i, sel) in selection.selections.iter().enumerate() {
479 let dim_size = shape[i];
480 match sel {
481 SliceInfoElem::Index(idx) => {
482 if *idx >= dim_size {
483 return Err(Error::SliceOutOfBounds {
484 dim: i,
485 index: *idx,
486 size: dim_size,
487 });
488 }
489 dims.push(ResolvedSelectionDim {
490 start: *idx,
491 end: *idx + 1,
492 step: 1,
493 count: 1,
494 });
495 }
496 SliceInfoElem::Slice { start, end, step } => {
497 if *step == 0 {
498 return Err(Error::InvalidData("slice step cannot be 0".into()));
499 }
500 if *start > dim_size {
501 return Err(Error::SliceOutOfBounds {
502 dim: i,
503 index: *start,
504 size: dim_size,
505 });
506 }
507
508 let actual_end = if *end == u64::MAX {
509 dim_size
510 } else {
511 (*end).min(dim_size)
512 };
513 let count_u64 = if *start >= actual_end {
514 0
515 } else {
516 (actual_end - *start).div_ceil(*step)
517 };
518 let count = checked_usize(count_u64, "slice element count")?;
519
520 dims.push(ResolvedSelectionDim {
521 start: *start,
522 end: actual_end,
523 step: *step,
524 count,
525 });
526 result_shape.push(count);
527 result_elements =
528 checked_mul_usize(result_elements, count, "slice result element count")?;
529 }
530 }
531 }
532
533 Ok(ResolvedSelection {
534 dims,
535 result_shape,
536 result_elements,
537 })
538}
539
540fn selection_dim_is_full_unit(dim: &ResolvedSelectionDim, dim_size: u64) -> bool {
541 dim.step == 1
542 && dim.start == 0
543 && dim.end == dim_size
544 && u64::try_from(dim.count).ok() == Some(dim_size)
545}
546
547fn selection_covers_full_dataset(resolved: &ResolvedSelection, shape: &[u64]) -> bool {
548 resolved.result_shape.len() == shape.len()
549 && resolved
550 .dims
551 .iter()
552 .zip(shape.iter())
553 .all(|(dim, &dim_size)| selection_dim_is_full_unit(dim, dim_size))
554}
555
556fn contiguous_slice_tail_start(shape: &[u64], resolved: &ResolvedSelection) -> usize {
557 let ndim = shape.len();
558 if ndim == 0 {
559 return 0;
560 }
561
562 let mut tail_start = if resolved.dims[ndim - 1].step == 1 {
563 ndim - 1
564 } else {
565 ndim
566 };
567
568 while tail_start > 0 {
569 let prev = tail_start - 1;
570 let later_dims_are_full =
571 (tail_start..ndim).all(|d| selection_dim_is_full_unit(&resolved.dims[d], shape[d]));
572 if resolved.dims[prev].step == 1 && later_dims_are_full {
573 tail_start = prev;
574 } else {
575 break;
576 }
577 }
578
579 tail_start
580}
581
582fn contiguous_slice_block_elements(
583 resolved: &ResolvedSelection,
584 tail_start: usize,
585) -> Result<usize> {
586 let mut elements = 1usize;
587 for dim in &resolved.dims[tail_start..] {
588 elements = checked_mul_usize(elements, dim.count, "contiguous slice block elements")?;
589 }
590 Ok(elements)
591}
592
593fn result_strides_for_dims(result_dims: &[usize]) -> Result<Vec<usize>> {
594 let ndim = result_dims.len();
595 let mut result_strides = vec![1usize; ndim];
596 for d in (0..ndim.saturating_sub(1)).rev() {
597 result_strides[d] =
598 checked_mul_usize(result_strides[d + 1], result_dims[d + 1], "result stride")?;
599 }
600 Ok(result_strides)
601}
602
603#[derive(Clone)]
605pub struct Dataset {
606 pub(crate) context: Arc<FileContext>,
607 pub(crate) name: String,
608 pub(crate) data_address: u64,
609 pub(crate) dataspace: DataspaceMessage,
610 pub(crate) datatype: Datatype,
611 pub(crate) layout: DataLayout,
612 pub(crate) fill_value: Option<FillValueMessage>,
613 pub(crate) filters: Option<FilterPipelineMessage>,
614 pub(crate) external_files: Option<ExternalFilesMessage>,
615 pub(crate) attributes: Vec<AttributeMessage>,
616 pub(crate) chunk_cache: Arc<ChunkCache>,
617 chunk_entry_cache: Arc<Mutex<LruCache<ChunkEntryCacheKey, Arc<Vec<chunk_index::ChunkEntry>>>>>,
618 full_chunk_entries: Arc<OnceLock<Arc<Vec<chunk_index::ChunkEntry>>>>,
619 full_dataset_bytes: Arc<OnceLock<Arc<Vec<u8>>>>,
620 external_slots: Arc<OnceLock<Arc<Vec<ResolvedExternalRawSlot>>>>,
621 pub(crate) filter_registry: Arc<FilterRegistry>,
622}
623
624pub struct DatasetChunk {
626 offsets: Vec<u64>,
627 shape: Vec<u64>,
628 filter_mask: u32,
629 bytes: Arc<Vec<u8>>,
630}
631
632impl DatasetChunk {
633 pub fn offsets(&self) -> &[u64] {
635 &self.offsets
636 }
637
638 pub fn shape(&self) -> &[u64] {
640 &self.shape
641 }
642
643 pub fn filter_mask(&self) -> u32 {
645 self.filter_mask
646 }
647
648 pub fn bytes(&self) -> &[u8] {
650 self.bytes.as_ref()
651 }
652}
653
654pub struct DatasetChunkIterator {
656 dataset: Dataset,
657 entries: Vec<chunk_index::ChunkEntry>,
658 index_address: u64,
659 chunk_shape: Vec<u64>,
660 elem_size: usize,
661 next: usize,
662}
663
664impl Iterator for DatasetChunkIterator {
665 type Item = Result<DatasetChunk>;
666
667 fn next(&mut self) -> Option<Self::Item> {
668 let entry = self.entries.get(self.next)?;
669 self.next += 1;
670
671 Some(
672 self.dataset
673 .load_exact_chunk_data(entry, self.index_address, &self.chunk_shape, self.elem_size)
674 .map(|bytes| DatasetChunk {
675 offsets: entry.offsets.clone(),
676 shape: self.chunk_shape.clone(),
677 filter_mask: entry.filter_mask,
678 bytes,
679 }),
680 )
681 }
682}
683
684pub(crate) struct DatasetTemplate {
685 name: String,
686 data_address: u64,
687 dataspace: DataspaceMessage,
688 datatype: Datatype,
689 layout: DataLayout,
690 fill_value: Option<FillValueMessage>,
691 filters: Option<FilterPipelineMessage>,
692 external_files: Option<ExternalFilesMessage>,
693 attributes: Vec<AttributeMessage>,
694 chunk_entry_cache: Arc<Mutex<LruCache<ChunkEntryCacheKey, Arc<Vec<chunk_index::ChunkEntry>>>>>,
695 full_chunk_entries: Arc<OnceLock<Arc<Vec<chunk_index::ChunkEntry>>>>,
696 full_dataset_bytes: Arc<OnceLock<Arc<Vec<u8>>>>,
697 external_slots: Arc<OnceLock<Arc<Vec<ResolvedExternalRawSlot>>>>,
698}
699
700impl Dataset {
701 pub(crate) fn from_template(context: Arc<FileContext>, template: Arc<DatasetTemplate>) -> Self {
702 Dataset {
703 chunk_cache: context.chunk_cache.clone(),
704 filter_registry: context.filter_registry.clone(),
705 context,
706 name: template.name.clone(),
707 data_address: template.data_address,
708 dataspace: template.dataspace.clone(),
709 datatype: template.datatype.clone(),
710 layout: template.layout.clone(),
711 fill_value: template.fill_value.clone(),
712 filters: template.filters.clone(),
713 external_files: template.external_files.clone(),
714 attributes: template.attributes.clone(),
715 chunk_entry_cache: template.chunk_entry_cache.clone(),
716 full_chunk_entries: template.full_chunk_entries.clone(),
717 full_dataset_bytes: template.full_dataset_bytes.clone(),
718 external_slots: template.external_slots.clone(),
719 }
720 }
721
722 pub(crate) fn template(&self) -> Arc<DatasetTemplate> {
723 Arc::new(DatasetTemplate {
724 name: self.name.clone(),
725 data_address: self.data_address,
726 dataspace: self.dataspace.clone(),
727 datatype: self.datatype.clone(),
728 layout: self.layout.clone(),
729 fill_value: self.fill_value.clone(),
730 filters: self.filters.clone(),
731 external_files: self.external_files.clone(),
732 attributes: self.attributes.clone(),
733 chunk_entry_cache: self.chunk_entry_cache.clone(),
734 full_chunk_entries: self.full_chunk_entries.clone(),
735 full_dataset_bytes: self.full_dataset_bytes.clone(),
736 external_slots: self.external_slots.clone(),
737 })
738 }
739
740 pub(crate) fn from_parsed_header(
741 context: DatasetParseContext,
742 address: u64,
743 name: String,
744 header: &ObjectHeader,
745 ) -> Result<Self> {
746 let mut dataspace: Option<DataspaceMessage> = None;
747 let mut datatype: Option<Datatype> = None;
748 let mut layout: Option<DataLayout> = None;
749 let mut fill_value: Option<FillValueMessage> = None;
750 let mut filter_pipeline: Option<FilterPipelineMessage> = None;
751 let mut external_files: Option<ExternalFilesMessage> = None;
752 let attributes = collect_attribute_messages_storage(
753 header,
754 context.context.storage.as_ref(),
755 context.context.superblock.offset_size,
756 context.context.superblock.length_size,
757 Some(context.context.filter_registry.as_ref()),
758 )?;
759
760 for msg in &header.messages {
761 match msg {
762 HdfMessage::Dataspace(ds) => dataspace = Some(ds.clone()),
763 HdfMessage::Datatype(dt) => datatype = Some(dt.datatype.clone()),
764 HdfMessage::DataLayout(dl) => layout = Some(dl.layout.clone()),
765 HdfMessage::FillValue(fv) => fill_value = Some(fv.clone()),
766 HdfMessage::FilterPipeline(fp) => filter_pipeline = Some(fp.clone()),
767 HdfMessage::ExternalFiles(ef) => external_files = Some(ef.clone()),
768 _ => {}
769 }
770 }
771
772 let dataspace =
773 dataspace.ok_or_else(|| Error::InvalidData("dataset missing dataspace".into()))?;
774 let dt = datatype.ok_or_else(|| Error::InvalidData("dataset missing datatype".into()))?;
775 let layout =
776 layout.ok_or_else(|| Error::InvalidData("dataset missing data layout".into()))?;
777 let layout = normalize_layout(layout, &dataspace);
778 let attr_fill_value = attributes
779 .iter()
780 .find(|attr| {
781 attr.name == "_FillValue" && matches!(attr.dataspace.num_elements(), Ok(1))
782 })
783 .map(|attr| FillValueMessage {
784 defined: !attr.raw_data.is_empty(),
785 fill_time: FillTime::IfSet,
786 value: Some(attr.raw_data.clone()),
787 });
788 let fill_value = match fill_value {
789 Some(existing) if existing.value.is_some() => Some(existing),
790 _ => attr_fill_value,
791 };
792
793 Ok(Dataset {
794 context: context.context.clone(),
795 name,
796 data_address: address,
797 dataspace,
798 datatype: dt,
799 layout,
800 fill_value,
801 filters: filter_pipeline,
802 external_files,
803 attributes,
804 chunk_cache: context.context.chunk_cache.clone(),
805 chunk_entry_cache: Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(32).unwrap()))),
806 full_chunk_entries: Arc::new(OnceLock::new()),
807 full_dataset_bytes: Arc::new(OnceLock::new()),
808 external_slots: Arc::new(OnceLock::new()),
809 filter_registry: context.context.filter_registry.clone(),
810 })
811 }
812
813 pub fn name(&self) -> &str {
815 &self.name
816 }
817
818 pub fn address(&self) -> u64 {
821 self.data_address
822 }
823
824 pub fn shape(&self) -> &[u64] {
826 &self.dataspace.dims
827 }
828
829 pub fn dtype(&self) -> &Datatype {
831 &self.datatype
832 }
833
834 pub fn ndim(&self) -> usize {
836 self.dataspace.dims.len()
837 }
838
839 fn offset_size(&self) -> u8 {
840 self.context.superblock.offset_size
841 }
842
843 fn length_size(&self) -> u8 {
844 self.context.superblock.length_size
845 }
846
847 pub fn max_dims(&self) -> Option<&[u64]> {
849 self.dataspace.max_dims.as_deref()
850 }
851
852 pub fn chunks(&self) -> Option<Vec<u32>> {
854 match &self.layout {
855 DataLayout::Chunked { dims, .. } => Some(dims.clone()),
856 _ => None,
857 }
858 }
859
860 pub fn iter_chunks(&self) -> Result<DatasetChunkIterator> {
862 let DataLayout::Chunked {
863 address,
864 dims,
865 chunk_indexing,
866 ..
867 } = &self.layout
868 else {
869 return Err(Error::InvalidData(format!(
870 "dataset '{}' is not chunked",
871 self.name
872 )));
873 };
874
875 if Cursor::is_undefined_offset(*address, self.offset_size()) {
876 return Ok(DatasetChunkIterator {
877 dataset: self.clone(),
878 entries: Vec::new(),
879 index_address: *address,
880 chunk_shape: dims.iter().map(|&d| d as u64).collect(),
881 elem_size: self.raw_element_size()?,
882 next: 0,
883 });
884 }
885
886 let ndim = self.ndim();
887 let shape = &self.dataspace.dims;
888 let elem_size = self.raw_element_size()?;
889 let chunk_shape: Vec<u64> = dims.iter().map(|&d| d as u64).collect();
890 validate_chunk_shape(shape, &chunk_shape)?;
891 let entries = self.collect_chunk_entries(
892 *address,
893 dims,
894 chunk_indexing.as_ref(),
895 ChunkEntrySelection {
896 shape,
897 ndim,
898 elem_size,
899 chunk_bounds: None,
900 },
901 )?;
902
903 Ok(DatasetChunkIterator {
904 dataset: self.clone(),
905 entries,
906 index_address: *address,
907 chunk_shape,
908 elem_size,
909 next: 0,
910 })
911 }
912
913 pub fn chunk_cache_stats(&self) -> ChunkCacheStats {
915 self.chunk_cache.stats()
916 }
917
918 pub fn fill_value(&self) -> Option<&FillValueMessage> {
920 self.fill_value.as_ref()
921 }
922
923 pub fn attributes(&self) -> Vec<Attribute> {
925 self.attributes
926 .iter()
927 .map(|a| attribute_from_message_storage(a, self.context.as_ref()))
928 .collect()
929 }
930
931 pub fn attribute(&self, name: &str) -> Result<Attribute> {
933 self.attributes
934 .iter()
935 .find(|a| a.name == name)
936 .map(|a| attribute_from_message_storage(a, self.context.as_ref()))
937 .ok_or_else(|| Error::AttributeNotFound(name.to_string()))
938 }
939
940 pub fn read_string(&self) -> Result<String> {
944 let mut strings = self.read_strings()?;
945 match strings.len() {
946 1 => Ok(strings.swap_remove(0)),
947 0 => Err(Error::InvalidData(format!(
948 "dataset '{}' contains no string elements",
949 self.name
950 ))),
951 count => Err(Error::InvalidData(format!(
952 "dataset '{}' contains {count} string elements; use read_strings()",
953 self.name
954 ))),
955 }
956 }
957
958 pub fn read_strings(&self) -> Result<Vec<String>> {
960 match &self.datatype {
961 Datatype::String {
962 size: StringSize::Fixed(len),
963 encoding,
964 padding,
965 } => {
966 let raw = self.read_raw_bytes()?;
967 let elem_size = *len as usize;
968 let count = checked_usize(self.num_elements()?, "dataset string element count")?;
969 let expected_bytes =
970 checked_mul_usize(count, elem_size, "dataset string byte size")?;
971 if raw.len() < expected_bytes {
972 return Err(Error::InvalidData(format!(
973 "dataset '{}' string data too short: need {} bytes, have {}",
974 self.name,
975 expected_bytes,
976 raw.len()
977 )));
978 }
979
980 let mut strings = Vec::with_capacity(count);
981 for i in 0..count {
982 let start = i * elem_size;
983 let end = start + elem_size;
984 strings.push(decode_string(&raw[start..end], *padding, *encoding)?);
985 }
986 Ok(strings)
987 }
988 Datatype::String {
989 size: StringSize::Variable,
990 encoding,
991 padding,
992 } => {
993 let raw = self.read_raw_bytes()?;
994 let count = checked_usize(self.num_elements()?, "dataset string element count")?;
995 let ref_size = 4 + self.offset_size() as usize + 4;
996 let expected_bytes =
997 checked_mul_usize(count, ref_size, "dataset string reference byte size")?;
998 if raw.len() < expected_bytes {
999 return Err(Error::InvalidData(format!(
1000 "dataset '{}' vlen string data too short: need {} bytes, have {}",
1001 self.name,
1002 expected_bytes,
1003 raw.len()
1004 )));
1005 }
1006
1007 let mut strings = Vec::with_capacity(count);
1008 for i in 0..count {
1009 let offset = i * ref_size;
1010 strings.push(read_one_vlen_string_storage(
1011 &raw,
1012 offset,
1013 self.context.storage.as_ref(),
1014 self.offset_size(),
1015 self.length_size(),
1016 *padding,
1017 *encoding,
1018 )?);
1019 }
1020 Ok(strings)
1021 }
1022 Datatype::VarLen {
1023 base,
1024 kind: VarLenKind::String,
1025 encoding,
1026 padding,
1027 } => {
1028 if !matches!(base.as_ref(), Datatype::FixedPoint { size: 1, .. }) {
1029 return Err(Error::TypeMismatch {
1030 expected: "String dataset".into(),
1031 actual: format!("{:?}", self.datatype),
1032 });
1033 }
1034
1035 let raw = self.read_raw_bytes()?;
1036 let count = checked_usize(self.num_elements()?, "dataset string element count")?;
1037 let ref_size = 4 + self.offset_size() as usize + 4;
1038 let expected_bytes =
1039 checked_mul_usize(count, ref_size, "dataset string reference byte size")?;
1040 if raw.len() < expected_bytes {
1041 return Err(Error::InvalidData(format!(
1042 "dataset '{}' vlen byte string data too short: need {} bytes, have {}",
1043 self.name,
1044 expected_bytes,
1045 raw.len()
1046 )));
1047 }
1048
1049 let mut strings = Vec::with_capacity(count);
1050 for i in 0..count {
1051 let offset = i * ref_size;
1052 let ref_bytes = &raw[offset..offset + ref_size];
1053 let value = resolve_vlen_bytes_storage(
1054 ref_bytes,
1055 self.context.storage.as_ref(),
1056 self.offset_size(),
1057 self.length_size(),
1058 )
1059 .unwrap_or_default();
1060 strings.push(decode_string(&value, *padding, *encoding)?);
1061 }
1062 Ok(strings)
1063 }
1064 _ => Err(Error::TypeMismatch {
1065 expected: "String dataset".into(),
1066 actual: format!("{:?}", self.datatype),
1067 }),
1068 }
1069 }
1070
1071 pub fn num_elements(&self) -> Result<u64> {
1073 Ok(self.dataspace.num_elements()?)
1074 }
1075
1076 pub fn read_array<T: H5Type>(&self) -> Result<ArrayD<T>> {
1078 let result = match &self.layout {
1079 DataLayout::Compact { data } => self.read_compact::<T>(data),
1080 DataLayout::Contiguous { address, size } => self.read_contiguous::<T>(*address, *size),
1081 DataLayout::Chunked {
1082 address,
1083 dims,
1084 element_size,
1085 chunk_indexing,
1086 } => self.read_chunked::<T>(*address, dims, *element_size, chunk_indexing.as_ref()),
1087 };
1088 result.map_err(|e| e.with_context(&self.name))
1089 }
1090
1091 pub fn read_into<T: H5Type>(&self, dst: &mut [T]) -> Result<()> {
1093 let result = (|| {
1094 let element_count = checked_usize(self.num_elements()?, "dataset element count")?;
1095 if dst.len() != element_count {
1096 return Err(Error::InvalidData(format!(
1097 "destination has {} elements, dataset requires {}",
1098 dst.len(),
1099 element_count
1100 )));
1101 }
1102
1103 let elem_size = self.raw_element_size()?;
1104 if T::native_copy_compatible(&self.datatype) && std::mem::size_of::<T>() == elem_size {
1105 let dst_bytes = unsafe {
1106 std::slice::from_raw_parts_mut(
1107 dst.as_mut_ptr() as *mut u8,
1108 checked_mul_usize(dst.len(), elem_size, "destination byte length")?,
1109 )
1110 };
1111 return self.read_raw_bytes_into_inner(dst_bytes);
1112 }
1113
1114 let array = self.read_array::<T>()?;
1115 let values = array.as_slice_memory_order().ok_or_else(|| {
1116 Error::InvalidData("decoded array is not contiguous in memory order".into())
1117 })?;
1118 dst.clone_from_slice(values);
1119 Ok(())
1120 })();
1121
1122 result.map_err(|e| e.with_context(&self.name))
1123 }
1124
1125 #[cfg(feature = "rayon")]
1129 pub fn read_array_parallel<T: H5Type>(&self) -> Result<ArrayD<T>> {
1130 match &self.layout {
1131 DataLayout::Chunked {
1132 address,
1133 dims,
1134 element_size,
1135 chunk_indexing,
1136 } => self.read_chunked_parallel::<T>(
1137 *address,
1138 dims,
1139 *element_size,
1140 chunk_indexing.as_ref(),
1141 ),
1142 _ => self.read_array::<T>(),
1143 }
1144 }
1145
1146 #[cfg(feature = "rayon")]
1150 pub fn read_array_in_pool<T: H5Type>(&self, pool: &rayon::ThreadPool) -> Result<ArrayD<T>> {
1151 match &self.layout {
1152 DataLayout::Chunked {
1153 address,
1154 dims,
1155 element_size,
1156 chunk_indexing,
1157 } => pool.install(|| {
1158 self.read_chunked_parallel::<T>(
1159 *address,
1160 dims,
1161 *element_size,
1162 chunk_indexing.as_ref(),
1163 )
1164 }),
1165 _ => self.read_array::<T>(),
1166 }
1167 }
1168
1169 #[cfg(feature = "rayon")]
1174 pub fn read_slice_parallel<T: H5Type>(&self, selection: &SliceInfo) -> Result<ArrayD<T>> {
1175 let resolved = normalize_selection(selection, &self.dataspace.dims)?;
1176
1177 match &self.layout {
1178 DataLayout::Chunked {
1179 address,
1180 dims,
1181 element_size,
1182 chunk_indexing,
1183 } => self.read_chunked_slice_parallel::<T>(
1184 *address,
1185 dims,
1186 *element_size,
1187 chunk_indexing.as_ref(),
1188 selection,
1189 &resolved,
1190 ),
1191 _ => self.read_slice::<T>(selection),
1192 }
1193 }
1194
1195 pub fn read_slice<T: H5Type>(&self, selection: &SliceInfo) -> Result<ArrayD<T>> {
1197 let resolved = normalize_selection(selection, &self.dataspace.dims)?;
1198
1199 match &self.layout {
1200 DataLayout::Contiguous { address, size } => {
1201 self.read_contiguous_slice::<T>(*address, *size, &resolved)
1202 }
1203 DataLayout::Compact { data } => self.read_compact_slice::<T>(data, selection),
1204 DataLayout::Chunked {
1205 address,
1206 dims,
1207 element_size,
1208 chunk_indexing,
1209 } => self.read_chunked_slice::<T>(
1210 *address,
1211 dims,
1212 *element_size,
1213 chunk_indexing.as_ref(),
1214 selection,
1215 &resolved,
1216 ),
1217 }
1218 }
1219
1220 fn read_compact<T: H5Type>(&self, data: &[u8]) -> Result<ArrayD<T>> {
1221 self.validate_allocated_raw_data_len("compact", data.len())?;
1222 self.decode_raw_data::<T>(data)
1223 }
1224
1225 pub fn read_raw_bytes(&self) -> Result<Vec<u8>> {
1231 let result: Result<Vec<u8>> = (|| {
1232 let total_bytes = self.raw_byte_len()?;
1233 let mut output = vec![0u8; total_bytes];
1234 self.read_raw_bytes_into_inner(&mut output)?;
1235 Ok(output)
1236 })();
1237
1238 result.map_err(|e| e.with_context(&self.name))
1239 }
1240
1241 pub fn raw_byte_len(&self) -> Result<usize> {
1243 let elem_size = self.raw_element_size()?;
1244 let total_elements = checked_usize(self.num_elements()?, "dataset element count")?;
1245 checked_mul_usize(total_elements, elem_size, "dataset size in bytes")
1246 }
1247
1248 pub fn read_raw_bytes_into(&self, dst: &mut [u8]) -> Result<()> {
1253 let result: Result<()> = (|| {
1254 let total_bytes = self.raw_byte_len()?;
1255 if dst.len() != total_bytes {
1256 return Err(Error::InvalidData(format!(
1257 "destination has {} bytes, dataset requires {}",
1258 dst.len(),
1259 total_bytes
1260 )));
1261 }
1262 self.read_raw_bytes_into_inner(dst)
1263 })();
1264
1265 result.map_err(|e| e.with_context(&self.name))
1266 }
1267
1268 pub fn read_native_bytes(&self) -> Result<Vec<u8>> {
1270 let result: Result<Vec<u8>> = (|| {
1271 let total_bytes = self.raw_byte_len()?;
1272 let mut output = vec![0u8; total_bytes];
1273 self.read_raw_bytes_into_inner(&mut output)?;
1274 self.convert_to_native_endian(&mut output)?;
1275 Ok(output)
1276 })();
1277
1278 result.map_err(|e| e.with_context(&self.name))
1279 }
1280
1281 pub fn read_native_bytes_into(&self, dst: &mut [u8]) -> Result<()> {
1283 let result: Result<()> = (|| {
1284 let total_bytes = self.raw_byte_len()?;
1285 if dst.len() != total_bytes {
1286 return Err(Error::InvalidData(format!(
1287 "destination has {} bytes, dataset requires {}",
1288 dst.len(),
1289 total_bytes
1290 )));
1291 }
1292 self.read_raw_bytes_into_inner(dst)?;
1293 self.convert_to_native_endian(dst)
1294 })();
1295
1296 result.map_err(|e| e.with_context(&self.name))
1297 }
1298
1299 fn read_raw_bytes_into_inner(&self, dst: &mut [u8]) -> Result<()> {
1300 match &self.layout {
1301 DataLayout::Compact { data } => {
1302 self.validate_allocated_raw_data_len("compact", data.len())?;
1303 dst.copy_from_slice(data);
1304 Ok(())
1305 }
1306 DataLayout::Contiguous { address, size } => {
1307 self.read_contiguous_bytes_into(*address, *size, dst)
1308 }
1309 DataLayout::Chunked {
1310 address,
1311 dims,
1312 element_size: _,
1313 chunk_indexing,
1314 } => self.read_chunked_bytes_into(*address, dims, chunk_indexing.as_ref(), dst),
1315 }
1316 }
1317
1318 pub fn vlen_reference_size(&self) -> usize {
1320 4 + self.offset_size() as usize + 4
1321 }
1322
1323 pub fn raw_element_size(&self) -> Result<usize> {
1328 raw_element_size_for_datatype(&self.datatype, self.vlen_reference_size())
1329 }
1330
1331 pub fn resolve_vlen_reference_bytes(
1337 &self,
1338 reference: &[u8],
1339 base_element_size: usize,
1340 ) -> Result<Vec<u8>> {
1341 if reference.len() < self.vlen_reference_size() {
1342 return Err(Error::InvalidData(format!(
1343 "dataset '{}' vlen reference too short: need {} bytes, have {}",
1344 self.name,
1345 self.vlen_reference_size(),
1346 reference.len()
1347 )));
1348 }
1349
1350 let mut cursor = Cursor::new(reference);
1351 let seq_len = cursor.read_u32_le()? as usize;
1352 let heap_addr = cursor.read_offset(self.offset_size())?;
1353 let obj_index = cursor.read_u32_le()? as u16;
1354
1355 if Cursor::is_undefined_offset(heap_addr, self.offset_size()) || obj_index == 0 {
1356 return Ok(Vec::new());
1357 }
1358
1359 let expected_bytes =
1360 checked_mul_usize(seq_len, base_element_size, "vlen sequence byte size")?;
1361 let collection = crate::global_heap::GlobalHeapCollection::parse_at_storage(
1362 self.context.storage.as_ref(),
1363 heap_addr,
1364 self.offset_size(),
1365 self.length_size(),
1366 )?;
1367 let object = collection.get_object(obj_index).ok_or_else(|| {
1368 Error::InvalidData(format!(
1369 "dataset '{}' references missing vlen heap object {}",
1370 self.name, obj_index
1371 ))
1372 })?;
1373 if object.data.len() < expected_bytes {
1374 return Err(Error::InvalidData(format!(
1375 "dataset '{}' vlen heap object too short: need {} bytes, have {}",
1376 self.name,
1377 expected_bytes,
1378 object.data.len()
1379 )));
1380 }
1381
1382 Ok(object.data[..expected_bytes].to_vec())
1383 }
1384
1385 fn read_contiguous<T: H5Type>(&self, address: u64, size: u64) -> Result<ArrayD<T>> {
1386 if self.external_files.is_some() {
1387 let elem_size = self.raw_element_size()?;
1388 let total_elements = checked_usize(self.num_elements()?, "dataset element count")?;
1389 let total_bytes =
1390 checked_mul_usize(total_elements, elem_size, "dataset size in bytes")?;
1391 let raw = self.read_external_range(0, total_bytes)?;
1392 return self.decode_raw_data::<T>(&raw);
1393 }
1394
1395 if Cursor::is_undefined_offset(address, self.offset_size()) || size == 0 {
1396 return self.make_fill_array::<T>();
1398 }
1399
1400 let sz = checked_usize(size, "contiguous dataset size")?;
1401 self.validate_allocated_raw_data_len("contiguous", sz)?;
1402 let raw = self.context.read_range(address, sz)?;
1403 self.decode_raw_data::<T>(raw.as_ref())
1404 }
1405
1406 fn read_contiguous_bytes_into(&self, address: u64, size: u64, dst: &mut [u8]) -> Result<()> {
1407 if self.external_files.is_some() {
1408 return self.read_external_range_into(0, dst);
1409 }
1410
1411 if Cursor::is_undefined_offset(address, self.offset_size()) || size == 0 {
1412 self.fill_output_buffer(dst);
1413 return Ok(());
1414 }
1415
1416 let sz = checked_usize(size, "contiguous dataset size")?;
1417 self.validate_allocated_raw_data_len("contiguous", sz)?;
1418 if dst.is_empty() {
1419 return Ok(());
1420 }
1421 let raw = self.context.read_range(address, sz)?;
1422 dst.copy_from_slice(raw.as_ref());
1423 Ok(())
1424 }
1425
1426 fn read_contiguous_logical_range(
1427 &self,
1428 address: u64,
1429 logical_offset: usize,
1430 len: usize,
1431 ) -> Result<Vec<u8>> {
1432 if self.external_files.is_some() {
1433 return self.read_external_range(logical_offset, len);
1434 }
1435
1436 let file_offset = checked_add_u64(
1437 address,
1438 u64::try_from(logical_offset).map_err(|_| {
1439 Error::InvalidData("contiguous logical offset exceeds u64 capacity".to_string())
1440 })?,
1441 "contiguous read file offset",
1442 )?;
1443 Ok(self.context.read_range(file_offset, len)?.to_vec())
1444 }
1445
1446 fn read_external_range(&self, logical_offset: usize, len: usize) -> Result<Vec<u8>> {
1447 let mut output = vec![0u8; len];
1448 self.read_external_range_into(logical_offset, &mut output)?;
1449 Ok(output)
1450 }
1451
1452 fn read_external_range_into(&self, logical_offset: usize, dst: &mut [u8]) -> Result<()> {
1453 self.fill_output_buffer(dst);
1454 if dst.is_empty() {
1455 return Ok(());
1456 }
1457
1458 let request_start = u64::try_from(logical_offset).map_err(|_| {
1459 Error::InvalidData("external dataset offset exceeds u64 capacity".to_string())
1460 })?;
1461 let request_len = u64::try_from(dst.len()).map_err(|_| {
1462 Error::InvalidData("external dataset length exceeds u64 capacity".to_string())
1463 })?;
1464 let request_end = request_start
1465 .checked_add(request_len)
1466 .ok_or_else(|| Error::InvalidData("external dataset range overflows".into()))?;
1467
1468 for slot in self.external_raw_slots()?.iter() {
1469 let slot_end = slot.logical_offset.saturating_add(slot.size);
1470 let overlap_start = request_start.max(slot.logical_offset);
1471 let overlap_end = request_end.min(slot_end);
1472 if overlap_start >= overlap_end {
1473 continue;
1474 }
1475
1476 let read_offset = slot
1477 .file_offset
1478 .checked_add(overlap_start - slot.logical_offset)
1479 .ok_or_else(|| Error::InvalidData("external file read offset overflows".into()))?;
1480 let read_len = checked_usize(overlap_end - overlap_start, "external read length")?;
1481 let dst_start = checked_usize(overlap_start - request_start, "external read dst")?;
1482 let dst_end = checked_add_usize(dst_start, read_len, "external read dst end")?;
1483 let bytes = slot.storage.read_range(read_offset, read_len)?;
1484 dst[dst_start..dst_end].copy_from_slice(bytes.as_ref());
1485 }
1486
1487 Ok(())
1488 }
1489
1490 fn external_raw_slots(&self) -> Result<Arc<Vec<ResolvedExternalRawSlot>>> {
1491 if let Some(slots) = self.external_slots.get() {
1492 return Ok(slots.clone());
1493 }
1494
1495 let slots = Arc::new(self.load_external_raw_slots()?);
1496 let _ = self.external_slots.set(slots.clone());
1497 Ok(self
1498 .external_slots
1499 .get()
1500 .expect("external slot cache must exist after initialization")
1501 .clone())
1502 }
1503
1504 fn load_external_raw_slots(&self) -> Result<Vec<ResolvedExternalRawSlot>> {
1505 let Some(external_files) = self.external_files.as_ref() else {
1506 return Ok(Vec::new());
1507 };
1508
1509 let heap = LocalHeap::parse_at_storage(
1510 self.context.storage.as_ref(),
1511 external_files.heap_address,
1512 self.offset_size(),
1513 self.length_size(),
1514 )?;
1515
1516 let mut logical_offset = 0u64;
1517 let mut slots = Vec::with_capacity(external_files.slots.len());
1518 for slot in &external_files.slots {
1519 let filename =
1520 heap.get_string_storage(slot.name_offset, self.context.storage.as_ref())?;
1521 let storage = self
1522 .context
1523 .resolve_external_file(&filename)?
1524 .ok_or_else(|| {
1525 Error::Other(format!(
1526 "external raw data file '{filename}' could not be resolved"
1527 ))
1528 })?;
1529 let size = if Cursor::is_undefined_offset(slot.size, self.length_size()) {
1530 u64::MAX.saturating_sub(logical_offset)
1531 } else {
1532 slot.size
1533 };
1534
1535 slots.push(ResolvedExternalRawSlot {
1536 logical_offset,
1537 storage,
1538 file_offset: slot.offset,
1539 size,
1540 });
1541
1542 if Cursor::is_undefined_offset(slot.size, self.length_size()) {
1543 break;
1544 }
1545 logical_offset = logical_offset.checked_add(slot.size).ok_or_else(|| {
1546 Error::InvalidData("external raw data logical offset overflows".into())
1547 })?;
1548 }
1549
1550 Ok(slots)
1551 }
1552
1553 fn read_chunked<T: H5Type>(
1554 &self,
1555 index_address: u64,
1556 chunk_dims: &[u32],
1557 _element_size: u32,
1558 chunk_indexing: Option<&ChunkIndexing>,
1559 ) -> Result<ArrayD<T>> {
1560 if Cursor::is_undefined_offset(index_address, self.offset_size()) {
1561 return self.make_fill_array::<T>();
1562 }
1563
1564 let ndim = self.ndim();
1565 let shape = &self.dataspace.dims;
1566 let elem_size = self.raw_element_size()?;
1567 let total_elements = checked_usize(self.num_elements()?, "dataset element count")?;
1568 let total_bytes = checked_mul_usize(total_elements, elem_size, "dataset size in bytes")?;
1569
1570 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1571 if let Some(cached_bytes) = self.full_dataset_bytes.get() {
1572 return self.decode_raw_data::<T>(cached_bytes);
1573 }
1574 }
1575
1576 let chunk_shape: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
1577 validate_chunk_shape(shape, &chunk_shape)?;
1578 let dataset_strides = row_major_strides(shape, "dataset stride")?;
1579 let chunk_strides = row_major_strides(&chunk_shape, "chunk stride")?;
1580
1581 let mut entries = self.collect_chunk_entries(
1582 index_address,
1583 chunk_dims,
1584 chunk_indexing,
1585 ChunkEntrySelection {
1586 shape,
1587 ndim,
1588 elem_size,
1589 chunk_bounds: None,
1590 },
1591 )?;
1592
1593 let full_chunk_coverage = match full_dataset_chunk_bounds(shape, &chunk_shape)? {
1594 Some((first_chunk, last_chunk)) => validate_chunk_grid_coverage(
1595 &mut entries,
1596 shape,
1597 &chunk_shape,
1598 &first_chunk,
1599 &last_chunk,
1600 )?,
1601 None if entries.is_empty() => true,
1602 None => {
1603 return Err(Error::InvalidData(
1604 "chunk index contains entries for an empty dataset".into(),
1605 ))
1606 }
1607 };
1608 if full_chunk_coverage {
1609 if T::native_copy_compatible(&self.datatype) && std::mem::size_of::<T>() == elem_size {
1610 let mut result_values: Vec<MaybeUninit<T>> =
1611 std::iter::repeat_with(MaybeUninit::<T>::uninit)
1612 .take(total_elements)
1613 .collect();
1614 let result_ptr = result_values.as_mut_ptr() as *mut u8;
1615 let result_len = checked_mul_usize(
1616 result_values.len(),
1617 std::mem::size_of::<T>(),
1618 "typed dataset size in bytes",
1619 )?;
1620
1621 for entry in &entries {
1622 let chunk_data =
1623 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
1624 unsafe {
1625 copy_chunk_to_flat_with_strides_ptr(
1626 &chunk_data,
1627 FlatBufferPtr {
1628 ptr: result_ptr,
1629 len: result_len,
1630 },
1631 ChunkCopyLayout {
1632 chunk_offsets: &entry.offsets,
1633 chunk_shape: &chunk_shape,
1634 dataset_shape: shape,
1635 dataset_strides: &dataset_strides,
1636 chunk_strides: &chunk_strides,
1637 elem_size,
1638 },
1639 )?;
1640 }
1641 }
1642
1643 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1644 let mut cached_bytes = vec![0u8; total_bytes];
1645 unsafe {
1646 std::ptr::copy_nonoverlapping(
1647 result_ptr,
1648 cached_bytes.as_mut_ptr(),
1649 total_bytes,
1650 );
1651 }
1652 let _ = self.full_dataset_bytes.set(Arc::new(cached_bytes));
1653 }
1654
1655 let mut result_shape = Vec::with_capacity(shape.len());
1656 for &dim in shape {
1657 result_shape.push(checked_usize(dim, "dataset dimension")?);
1658 }
1659 let result_values = assume_init_vec(result_values);
1660 return ArrayD::from_shape_vec(IxDyn(&result_shape), result_values)
1661 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")));
1662 }
1663
1664 let mut flat_data = vec![MaybeUninit::<u8>::uninit(); total_bytes];
1665 let flat_ptr = flat_data.as_mut_ptr() as *mut u8;
1666 let flat_len = flat_data.len();
1667
1668 for entry in &entries {
1669 let chunk_data =
1670 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
1671 unsafe {
1672 copy_chunk_to_flat_with_strides_ptr(
1673 &chunk_data,
1674 FlatBufferPtr {
1675 ptr: flat_ptr,
1676 len: flat_len,
1677 },
1678 ChunkCopyLayout {
1679 chunk_offsets: &entry.offsets,
1680 chunk_shape: &chunk_shape,
1681 dataset_shape: shape,
1682 dataset_strides: &dataset_strides,
1683 chunk_strides: &chunk_strides,
1684 elem_size,
1685 },
1686 )?;
1687 }
1688 }
1689
1690 let flat_data = assume_init_u8_vec(flat_data);
1691 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1692 let _ = self.full_dataset_bytes.set(Arc::new(flat_data.clone()));
1693 }
1694 return self.decode_raw_data::<T>(&flat_data);
1695 }
1696
1697 let mut flat_data = self.make_output_buffer(total_bytes);
1698 for entry in &entries {
1699 let chunk_data =
1700 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
1701 copy_chunk_to_flat_with_strides(
1702 &chunk_data,
1703 &mut flat_data,
1704 ChunkCopyLayout {
1705 chunk_offsets: &entry.offsets,
1706 chunk_shape: &chunk_shape,
1707 dataset_shape: shape,
1708 dataset_strides: &dataset_strides,
1709 chunk_strides: &chunk_strides,
1710 elem_size,
1711 },
1712 )?;
1713 }
1714
1715 self.decode_raw_data::<T>(&flat_data)
1716 }
1717
1718 fn read_chunked_bytes_into(
1719 &self,
1720 index_address: u64,
1721 chunk_dims: &[u32],
1722 chunk_indexing: Option<&ChunkIndexing>,
1723 dst: &mut [u8],
1724 ) -> Result<()> {
1725 if Cursor::is_undefined_offset(index_address, self.offset_size()) {
1726 self.fill_output_buffer(dst);
1727 return Ok(());
1728 }
1729
1730 let ndim = self.ndim();
1731 let shape = &self.dataspace.dims;
1732 let elem_size = self.raw_element_size()?;
1733
1734 if dst.len() <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1735 if let Some(cached_bytes) = self.full_dataset_bytes.get() {
1736 if cached_bytes.len() == dst.len() {
1737 dst.copy_from_slice(cached_bytes.as_slice());
1738 return Ok(());
1739 }
1740 }
1741 }
1742
1743 let chunk_shape: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
1744 validate_chunk_shape(shape, &chunk_shape)?;
1745 let dataset_strides = row_major_strides(shape, "dataset stride")?;
1746 let chunk_strides = row_major_strides(&chunk_shape, "chunk stride")?;
1747
1748 let mut entries = self.collect_chunk_entries(
1749 index_address,
1750 chunk_dims,
1751 chunk_indexing,
1752 ChunkEntrySelection {
1753 shape,
1754 ndim,
1755 elem_size,
1756 chunk_bounds: None,
1757 },
1758 )?;
1759
1760 let full_chunk_coverage = match full_dataset_chunk_bounds(shape, &chunk_shape)? {
1761 Some((first_chunk, last_chunk)) => validate_chunk_grid_coverage(
1762 &mut entries,
1763 shape,
1764 &chunk_shape,
1765 &first_chunk,
1766 &last_chunk,
1767 )?,
1768 None if entries.is_empty() => true,
1769 None => {
1770 return Err(Error::InvalidData(
1771 "chunk index contains entries for an empty dataset".into(),
1772 ))
1773 }
1774 };
1775
1776 self.fill_output_buffer(dst);
1777 for entry in &entries {
1778 let chunk_data =
1779 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
1780 copy_chunk_to_flat_with_strides(
1781 &chunk_data,
1782 dst,
1783 ChunkCopyLayout {
1784 chunk_offsets: &entry.offsets,
1785 chunk_shape: &chunk_shape,
1786 dataset_shape: shape,
1787 dataset_strides: &dataset_strides,
1788 chunk_strides: &chunk_strides,
1789 elem_size,
1790 },
1791 )?;
1792 }
1793
1794 if full_chunk_coverage && dst.len() <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1795 let _ = self.full_dataset_bytes.set(Arc::new(dst.to_vec()));
1796 }
1797
1798 Ok(())
1799 }
1800
1801 #[cfg(feature = "rayon")]
1802 fn read_chunked_parallel<T: H5Type>(
1803 &self,
1804 index_address: u64,
1805 chunk_dims: &[u32],
1806 _element_size: u32,
1807 chunk_indexing: Option<&ChunkIndexing>,
1808 ) -> Result<ArrayD<T>> {
1809 if Cursor::is_undefined_offset(index_address, self.offset_size()) {
1810 return self.make_fill_array::<T>();
1811 }
1812
1813 let ndim = self.ndim();
1814 let shape = &self.dataspace.dims;
1815 let elem_size = self.raw_element_size()?;
1816 let total_elements = checked_usize(self.num_elements()?, "dataset element count")?;
1817 let total_bytes = checked_mul_usize(total_elements, elem_size, "dataset size in bytes")?;
1818
1819 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1820 if let Some(cached_bytes) = self.full_dataset_bytes.get() {
1821 return self.decode_raw_data::<T>(cached_bytes);
1822 }
1823 }
1824
1825 let chunk_shape: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
1826 validate_chunk_shape(shape, &chunk_shape)?;
1827 let dataset_strides = row_major_strides(shape, "dataset stride")?;
1828 let chunk_strides = row_major_strides(&chunk_shape, "chunk stride")?;
1829
1830 let mut entries = self.collect_chunk_entries(
1831 index_address,
1832 chunk_dims,
1833 chunk_indexing,
1834 ChunkEntrySelection {
1835 shape,
1836 ndim,
1837 elem_size,
1838 chunk_bounds: None,
1839 },
1840 )?;
1841
1842 let full_chunk_coverage = match full_dataset_chunk_bounds(shape, &chunk_shape)? {
1843 Some((first_chunk, last_chunk)) => validate_chunk_grid_coverage(
1844 &mut entries,
1845 shape,
1846 &chunk_shape,
1847 &first_chunk,
1848 &last_chunk,
1849 )?,
1850 None if entries.is_empty() => true,
1851 None => {
1852 return Err(Error::InvalidData(
1853 "chunk index contains entries for an empty dataset".into(),
1854 ))
1855 }
1856 };
1857 if full_chunk_coverage {
1858 if T::native_copy_compatible(&self.datatype) && std::mem::size_of::<T>() == elem_size {
1859 let mut result_values: Vec<MaybeUninit<T>> =
1860 std::iter::repeat_with(MaybeUninit::<T>::uninit)
1861 .take(total_elements)
1862 .collect();
1863 let flat = FlatBufferPtr {
1864 ptr: result_values.as_mut_ptr() as *mut u8,
1865 len: checked_mul_usize(
1866 result_values.len(),
1867 std::mem::size_of::<T>(),
1868 "typed dataset size in bytes",
1869 )?,
1870 };
1871
1872 entries
1873 .par_iter()
1874 .map(|entry| {
1875 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)
1876 .and_then(|data| unsafe {
1877 flat.copy_chunk(
1878 &data,
1879 ChunkCopyLayout {
1880 chunk_offsets: &entry.offsets,
1881 chunk_shape: &chunk_shape,
1882 dataset_shape: shape,
1883 dataset_strides: &dataset_strides,
1884 chunk_strides: &chunk_strides,
1885 elem_size,
1886 },
1887 )
1888 })
1889 })
1890 .collect::<std::result::Result<Vec<_>, Error>>()?;
1891
1892 let mut result_shape = Vec::with_capacity(shape.len());
1893 for &dim in shape {
1894 result_shape.push(checked_usize(dim, "dataset dimension")?);
1895 }
1896 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1897 let mut cached_bytes = vec![0u8; total_bytes];
1898 unsafe {
1899 std::ptr::copy_nonoverlapping(
1900 flat.ptr,
1901 cached_bytes.as_mut_ptr(),
1902 total_bytes,
1903 );
1904 }
1905 let _ = self.full_dataset_bytes.set(Arc::new(cached_bytes));
1906 }
1907 let result_values = assume_init_vec(result_values);
1908 return ArrayD::from_shape_vec(IxDyn(&result_shape), result_values)
1909 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")));
1910 }
1911
1912 let mut flat_data = vec![MaybeUninit::<u8>::uninit(); total_bytes];
1913 let flat = FlatBufferPtr {
1914 ptr: flat_data.as_mut_ptr() as *mut u8,
1915 len: flat_data.len(),
1916 };
1917
1918 entries
1919 .par_iter()
1920 .map(|entry| {
1921 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)
1922 .and_then(|data| unsafe {
1923 flat.copy_chunk(
1924 &data,
1925 ChunkCopyLayout {
1926 chunk_offsets: &entry.offsets,
1927 chunk_shape: &chunk_shape,
1928 dataset_shape: shape,
1929 dataset_strides: &dataset_strides,
1930 chunk_strides: &chunk_strides,
1931 elem_size,
1932 },
1933 )
1934 })
1935 })
1936 .collect::<std::result::Result<Vec<_>, Error>>()?;
1937
1938 let flat_data = assume_init_u8_vec(flat_data);
1939 if total_bytes <= HOT_FULL_DATASET_CACHE_MAX_BYTES {
1940 let _ = self.full_dataset_bytes.set(Arc::new(flat_data.clone()));
1941 }
1942 return self.decode_raw_data::<T>(&flat_data);
1943 }
1944
1945 let mut flat_data = self.make_output_buffer(total_bytes);
1946 let flat = FlatBufferPtr {
1947 ptr: flat_data.as_mut_ptr(),
1948 len: flat_data.len(),
1949 };
1950
1951 entries
1952 .par_iter()
1953 .map(|entry| {
1954 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)
1955 .and_then(|data| unsafe {
1956 flat.copy_chunk(
1957 &data,
1958 ChunkCopyLayout {
1959 chunk_offsets: &entry.offsets,
1960 chunk_shape: &chunk_shape,
1961 dataset_shape: shape,
1962 dataset_strides: &dataset_strides,
1963 chunk_strides: &chunk_strides,
1964 elem_size,
1965 },
1966 )
1967 })
1968 })
1969 .collect::<std::result::Result<Vec<_>, Error>>()?;
1970
1971 self.decode_raw_data::<T>(&flat_data)
1972 }
1973
1974 fn collect_chunk_entries(
1978 &self,
1979 index_address: u64,
1980 chunk_dims: &[u32],
1981 chunk_indexing: Option<&ChunkIndexing>,
1982 selection: ChunkEntrySelection<'_>,
1983 ) -> Result<Vec<chunk_index::ChunkEntry>> {
1984 if selection.chunk_bounds.is_none() {
1985 if let Some(cached) = self.full_chunk_entries.get() {
1986 return Ok((**cached).clone());
1987 }
1988 }
1989
1990 let cache_key =
1991 selection
1992 .chunk_bounds
1993 .map(|(first_chunk, last_chunk)| ChunkEntryCacheKey {
1994 index_address,
1995 first_chunk: SmallVec::from_slice(first_chunk),
1996 last_chunk: SmallVec::from_slice(last_chunk),
1997 });
1998
1999 if let Some(ref key) = cache_key {
2000 let mut cache = self.chunk_entry_cache.lock();
2001 if let Some(cached) = cache.get(key) {
2002 return Ok((**cached).clone());
2003 }
2004 }
2005
2006 let entries = match chunk_indexing {
2007 None => {
2008 self.collect_btree_v1_entries(
2010 index_address,
2011 selection.ndim,
2012 chunk_dims,
2013 selection.chunk_bounds,
2014 )
2015 }
2016 Some(ChunkIndexing::SingleChunk {
2017 filtered_size,
2018 filters,
2019 }) => Ok(vec![chunk_index::single_chunk_entry(
2020 index_address,
2021 *filtered_size,
2022 *filters,
2023 selection.ndim,
2024 )]),
2025 Some(ChunkIndexing::BTreeV2) => chunk_index::collect_v2_chunk_entries_storage(
2026 self.context.storage.as_ref(),
2027 index_address,
2028 self.offset_size(),
2029 self.length_size(),
2030 selection.ndim as u32,
2031 chunk_dims,
2032 selection.chunk_bounds,
2033 ),
2034 Some(ChunkIndexing::Implicit) => chunk_index::collect_implicit_chunk_entries(
2035 index_address,
2036 selection.shape,
2037 chunk_dims,
2038 selection.elem_size,
2039 selection.chunk_bounds,
2040 ),
2041 Some(ChunkIndexing::FixedArray { .. }) => {
2042 crate::fixed_array::collect_fixed_array_chunk_entries_storage(
2043 self.context.storage.as_ref(),
2044 index_address,
2045 self.offset_size(),
2046 self.length_size(),
2047 selection.shape,
2048 chunk_dims,
2049 selection.chunk_bounds,
2050 )
2051 }
2052 Some(ChunkIndexing::ExtensibleArray { .. }) => {
2053 crate::extensible_array::collect_extensible_array_chunk_entries_storage(
2054 self.context.storage.as_ref(),
2055 index_address,
2056 self.offset_size(),
2057 self.length_size(),
2058 selection.shape,
2059 chunk_dims,
2060 selection.chunk_bounds,
2061 )
2062 }
2063 }?;
2064
2065 if let Some(key) = cache_key {
2066 let mut cache = self.chunk_entry_cache.lock();
2067 cache.put(key, Arc::new(entries.clone()));
2068 } else {
2069 let _ = self.full_chunk_entries.set(Arc::new(entries.clone()));
2070 }
2071
2072 Ok(entries)
2073 }
2074
2075 fn collect_btree_v1_entries(
2077 &self,
2078 btree_address: u64,
2079 ndim: usize,
2080 chunk_dims: &[u32],
2081 chunk_bounds: Option<(&[u64], &[u64])>,
2082 ) -> Result<Vec<chunk_index::ChunkEntry>> {
2083 let leaves = crate::btree_v1::collect_btree_v1_leaves_storage(
2084 self.context.storage.as_ref(),
2085 btree_address,
2086 self.offset_size(),
2087 self.length_size(),
2088 Some(ndim as u32),
2089 chunk_dims,
2090 chunk_bounds,
2091 )?;
2092
2093 let mut entries = Vec::with_capacity(leaves.len());
2094 for (key, chunk_addr) in &leaves {
2095 match key {
2096 crate::btree_v1::BTreeV1Key::RawData {
2097 chunk_size,
2098 filter_mask,
2099 offsets,
2100 } => {
2101 entries.push(chunk_index::ChunkEntry {
2102 address: *chunk_addr,
2103 size: *chunk_size as u64,
2104 filter_mask: *filter_mask,
2105 offsets: offsets[..ndim].to_vec(),
2106 });
2107 }
2108 _ => {
2109 return Err(Error::InvalidData(
2110 "expected raw data key in chunk B-tree".into(),
2111 ))
2112 }
2113 }
2114 }
2115 Ok(entries)
2116 }
2117
2118 fn load_chunk_data(
2119 &self,
2120 entry: &chunk_index::ChunkEntry,
2121 dataset_addr: u64,
2122 chunk_shape: &[u64],
2123 elem_size: usize,
2124 ) -> Result<Arc<Vec<u8>>> {
2125 let cache_key = ChunkKey {
2126 dataset_addr,
2127 chunk_offsets: smallvec::SmallVec::from_slice(&entry.offsets),
2128 };
2129 let expected_len = decoded_chunk_expected_len(chunk_shape, elem_size)?;
2130 let filter_output_limit =
2131 checked_add_usize(expected_len, 1, "decoded chunk filter output limit")?;
2132
2133 self.chunk_cache.get_or_insert_with(cache_key, || {
2134 let size = if entry.size > 0 {
2135 checked_usize(entry.size, "encoded chunk size")?
2136 } else {
2137 expected_len
2138 };
2139 let raw = self.context.read_range(entry.address, size)?;
2140
2141 if let Some(ref pipeline) = self.filters {
2142 filters::apply_pipeline_with_limit(
2143 raw.as_ref(),
2144 &pipeline.filters,
2145 entry.filter_mask,
2146 elem_size,
2147 Some(&self.filter_registry),
2148 Some(filter_output_limit),
2149 )
2150 } else {
2151 Ok(raw.to_vec())
2152 }
2153 })
2154 }
2155
2156 fn load_exact_chunk_data(
2157 &self,
2158 entry: &chunk_index::ChunkEntry,
2159 dataset_addr: u64,
2160 chunk_shape: &[u64],
2161 elem_size: usize,
2162 ) -> Result<Arc<Vec<u8>>> {
2163 let data = self.load_chunk_data(entry, dataset_addr, chunk_shape, elem_size)?;
2164 validate_decoded_chunk_len(entry, chunk_shape, elem_size, data.len())?;
2165 Ok(data)
2166 }
2167
2168 fn read_chunked_slice<T: H5Type>(
2173 &self,
2174 index_address: u64,
2175 chunk_dims: &[u32],
2176 _element_size: u32,
2177 chunk_indexing: Option<&ChunkIndexing>,
2178 _selection: &SliceInfo,
2179 resolved: &ResolvedSelection,
2180 ) -> Result<ArrayD<T>> {
2181 if resolved.result_elements == 0 {
2182 return self.make_fill_array_from_shape::<T>(0, &resolved.result_shape);
2183 }
2184
2185 if Cursor::is_undefined_offset(index_address, self.offset_size()) {
2186 return self
2187 .make_fill_array_from_shape::<T>(resolved.result_elements, &resolved.result_shape);
2188 }
2189
2190 let ndim = self.ndim();
2191 let shape = &self.dataspace.dims;
2192 let elem_size = self.raw_element_size()?;
2193
2194 if let Some(array) = self.try_read_single_chunk_unit_stride_2d::<T>(
2195 index_address,
2196 chunk_dims,
2197 chunk_indexing,
2198 shape,
2199 elem_size,
2200 resolved,
2201 )? {
2202 return Ok(array);
2203 }
2204
2205 let chunk_shape: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
2206 validate_chunk_shape(shape, &chunk_shape)?;
2207 let mut first_chunk = vec![0u64; ndim];
2208 let mut last_chunk = vec![0u64; ndim];
2209 for d in 0..ndim {
2210 let (first, last) = resolved.dims[d]
2211 .chunk_index_range(chunk_shape[d])
2212 .expect("zero-sized result handled above");
2213 first_chunk[d] = first;
2214 last_chunk[d] = last;
2215 }
2216
2217 let mut overlapping = self.collect_chunk_entries(
2219 index_address,
2220 chunk_dims,
2221 chunk_indexing,
2222 ChunkEntrySelection {
2223 shape,
2224 ndim,
2225 elem_size,
2226 chunk_bounds: Some((&first_chunk, &last_chunk)),
2227 },
2228 )?;
2229 let fully_covered_grid = validate_chunk_grid_coverage(
2230 &mut overlapping,
2231 shape,
2232 &chunk_shape,
2233 &first_chunk,
2234 &last_chunk,
2235 )?;
2236
2237 let result_total_bytes = checked_mul_usize(
2238 resolved.result_elements,
2239 elem_size,
2240 "slice result size in bytes",
2241 )?;
2242 let result_dims = resolved.result_dims_with_collapsed();
2244 let mut result_strides = vec![1usize; ndim];
2245 for d in (0..ndim.saturating_sub(1)).rev() {
2246 result_strides[d] =
2247 checked_mul_usize(result_strides[d + 1], result_dims[d + 1], "result stride")?;
2248 }
2249 let mut chunk_strides = vec![1usize; ndim];
2250 for d in (0..ndim.saturating_sub(1)).rev() {
2251 chunk_strides[d] = checked_mul_usize(
2252 chunk_strides[d + 1],
2253 chunk_shape[d + 1] as usize,
2254 "chunk stride",
2255 )?;
2256 }
2257 let use_unit_stride_fast_path = resolved.is_unit_stride();
2258 let fully_covered_unit_stride = use_unit_stride_fast_path && fully_covered_grid;
2259
2260 if fully_covered_unit_stride {
2261 if T::native_copy_compatible(&self.datatype) && std::mem::size_of::<T>() == elem_size {
2262 let mut result_values: Vec<MaybeUninit<T>> =
2263 std::iter::repeat_with(MaybeUninit::<T>::uninit)
2264 .take(resolved.result_elements)
2265 .collect();
2266 let result_ptr = result_values.as_mut_ptr() as *mut u8;
2267 let result_len = checked_mul_usize(
2268 result_values.len(),
2269 std::mem::size_of::<T>(),
2270 "typed slice result size in bytes",
2271 )?;
2272
2273 for entry in &overlapping {
2274 let chunk_data =
2275 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2276
2277 unsafe {
2278 copy_unit_stride_chunk_overlap_ptr(
2279 &chunk_data,
2280 FlatBufferPtr {
2281 ptr: result_ptr,
2282 len: result_len,
2283 },
2284 UnitStrideCopyLayout {
2285 chunk_offsets: &entry.offsets,
2286 chunk_shape: &chunk_shape,
2287 dataset_shape: shape,
2288 resolved,
2289 chunk_strides: &chunk_strides,
2290 result_strides: &result_strides,
2291 elem_size,
2292 },
2293 )?;
2294 }
2295 }
2296
2297 let result_values = assume_init_vec(result_values);
2298 return ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), result_values)
2299 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")));
2300 }
2301
2302 let mut result_buf = vec![MaybeUninit::<u8>::uninit(); result_total_bytes];
2303 let result_ptr = result_buf.as_mut_ptr() as *mut u8;
2304 let result_len = result_buf.len();
2305
2306 for entry in &overlapping {
2307 let chunk_data =
2308 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2309
2310 unsafe {
2311 copy_unit_stride_chunk_overlap_ptr(
2312 &chunk_data,
2313 FlatBufferPtr {
2314 ptr: result_ptr,
2315 len: result_len,
2316 },
2317 UnitStrideCopyLayout {
2318 chunk_offsets: &entry.offsets,
2319 chunk_shape: &chunk_shape,
2320 dataset_shape: shape,
2321 resolved,
2322 chunk_strides: &chunk_strides,
2323 result_strides: &result_strides,
2324 elem_size,
2325 },
2326 )?;
2327 }
2328 }
2329
2330 let result_buf = assume_init_u8_vec(result_buf);
2331 return self.decode_buffer_with_shape::<T>(
2332 &result_buf,
2333 resolved.result_elements,
2334 &resolved.result_shape,
2335 );
2336 }
2337
2338 let mut result_buf = self.make_output_buffer(result_total_bytes);
2339
2340 for entry in &overlapping {
2342 let chunk_data =
2343 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2344
2345 if use_unit_stride_fast_path {
2346 copy_unit_stride_chunk_overlap(
2347 &chunk_data,
2348 &mut result_buf,
2349 UnitStrideCopyLayout {
2350 chunk_offsets: &entry.offsets,
2351 chunk_shape: &chunk_shape,
2352 dataset_shape: shape,
2353 resolved,
2354 chunk_strides: &chunk_strides,
2355 result_strides: &result_strides,
2356 elem_size,
2357 },
2358 )?;
2359 continue;
2360 }
2361
2362 let mut dim_indices: Vec<Vec<(usize, usize)>> = Vec::with_capacity(ndim);
2365 for d in 0..ndim {
2366 let chunk_start = entry.offsets[d];
2367 let chunk_end = (chunk_start + chunk_shape[d]).min(shape[d]);
2368 let dim = &resolved.dims[d];
2369 let sel_start = dim.start;
2370 let sel_end = dim.end;
2371 let sel_step = dim.step;
2372 let mut indices = Vec::new();
2373
2374 let first_sel = if sel_start >= chunk_start {
2376 sel_start
2377 } else {
2378 let steps_to_skip = (chunk_start - sel_start).div_ceil(sel_step);
2379 sel_start + steps_to_skip * sel_step
2380 };
2381
2382 let mut sel_idx = first_sel;
2383 while sel_idx < sel_end && sel_idx < chunk_end {
2384 let chunk_local = checked_usize(sel_idx - chunk_start, "chunk-local index")?;
2385 let result_dim_idx =
2387 checked_usize((sel_idx - dim.start) / sel_step, "result index")?;
2388 indices.push((chunk_local, result_dim_idx));
2389 sel_idx += sel_step;
2390 }
2391
2392 dim_indices.push(indices);
2393 }
2394
2395 copy_selected_elements(
2397 &chunk_data,
2398 &mut result_buf,
2399 &dim_indices,
2400 &chunk_strides,
2401 &result_strides,
2402 elem_size,
2403 ndim,
2404 )?;
2405 }
2406
2407 self.decode_buffer_with_shape::<T>(
2408 &result_buf,
2409 resolved.result_elements,
2410 &resolved.result_shape,
2411 )
2412 }
2413
2414 fn try_read_single_chunk_unit_stride_2d<T: H5Type>(
2415 &self,
2416 index_address: u64,
2417 chunk_dims: &[u32],
2418 chunk_indexing: Option<&ChunkIndexing>,
2419 shape: &[u64],
2420 elem_size: usize,
2421 resolved: &ResolvedSelection,
2422 ) -> Result<Option<ArrayD<T>>> {
2423 if shape.len() != 2
2424 || chunk_dims.len() != 2
2425 || resolved.dims.len() != 2
2426 || resolved.result_shape.len() != 2
2427 || !resolved.is_unit_stride()
2428 || std::mem::size_of::<T>() != elem_size
2429 || !T::native_copy_compatible(&self.datatype)
2430 {
2431 return Ok(None);
2432 }
2433
2434 let chunk_shape = [u64::from(chunk_dims[0]), u64::from(chunk_dims[1])];
2435 validate_chunk_shape(shape, &chunk_shape)?;
2436
2437 let row_dim = &resolved.dims[0];
2438 let col_dim = &resolved.dims[1];
2439 let Some((first_row_chunk, last_row_chunk)) = row_dim.chunk_index_range(chunk_shape[0])
2440 else {
2441 return Ok(None);
2442 };
2443 let Some((first_col_chunk, last_col_chunk)) = col_dim.chunk_index_range(chunk_shape[1])
2444 else {
2445 return Ok(None);
2446 };
2447 if first_row_chunk != last_row_chunk || first_col_chunk != last_col_chunk {
2448 return Ok(None);
2449 }
2450
2451 let first_chunk = [first_row_chunk, first_col_chunk];
2452 let last_chunk = first_chunk;
2453 let expected_offsets = [
2454 checked_mul_u64(first_row_chunk, chunk_shape[0], "single chunk row offset")?,
2455 checked_mul_u64(
2456 first_col_chunk,
2457 chunk_shape[1],
2458 "single chunk column offset",
2459 )?,
2460 ];
2461
2462 let entries = self.collect_chunk_entries(
2463 index_address,
2464 chunk_dims,
2465 chunk_indexing,
2466 ChunkEntrySelection {
2467 shape,
2468 ndim: 2,
2469 elem_size,
2470 chunk_bounds: Some((&first_chunk, &last_chunk)),
2471 },
2472 )?;
2473
2474 if entries.is_empty() {
2475 return self
2476 .make_fill_array_from_shape::<T>(resolved.result_elements, &resolved.result_shape)
2477 .map(Some);
2478 }
2479
2480 if entries.len() != 1 || entries[0].offsets.as_slice() != expected_offsets.as_slice() {
2481 return Ok(None);
2482 }
2483
2484 let entry = &entries[0];
2485 let chunk_data =
2486 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2487
2488 let row_start = checked_usize(
2489 row_dim.start - expected_offsets[0],
2490 "single chunk slice row start",
2491 )?;
2492 let col_start = checked_usize(
2493 col_dim.start - expected_offsets[1],
2494 "single chunk slice column start",
2495 )?;
2496 let row_count = row_dim.count;
2497 let col_count = col_dim.count;
2498 let chunk_cols = checked_usize(chunk_shape[1], "single chunk column extent")?;
2499 let row_bytes = checked_mul_usize(col_count, elem_size, "single chunk slice row bytes")?;
2500
2501 let mut result_values: Vec<MaybeUninit<T>> =
2502 std::iter::repeat_with(MaybeUninit::<T>::uninit)
2503 .take(resolved.result_elements)
2504 .collect();
2505 let result_ptr = result_values.as_mut_ptr() as *mut u8;
2506 let result_len = checked_mul_usize(
2507 result_values.len(),
2508 std::mem::size_of::<T>(),
2509 "typed slice result size in bytes",
2510 )?;
2511
2512 for row in 0..row_count {
2513 let chunk_row = checked_add_usize(row_start, row, "single chunk slice source row")?;
2514 let src_elem = checked_add_usize(
2515 checked_mul_usize(chunk_row, chunk_cols, "single chunk slice source row")?,
2516 col_start,
2517 "single chunk slice source element",
2518 )?;
2519 let dst_elem = checked_mul_usize(row, col_count, "single chunk slice destination row")?;
2520
2521 let src_start =
2522 checked_mul_usize(src_elem, elem_size, "single chunk slice source byte offset")?;
2523 let dst_start = checked_mul_usize(
2524 dst_elem,
2525 elem_size,
2526 "single chunk slice destination byte offset",
2527 )?;
2528 let src_end =
2529 checked_add_usize(src_start, row_bytes, "single chunk slice source byte end")?;
2530 let dst_end = checked_add_usize(
2531 dst_start,
2532 row_bytes,
2533 "single chunk slice destination byte end",
2534 )?;
2535
2536 if src_end > chunk_data.len() || dst_end > result_len {
2537 return Err(Error::InvalidData(format!(
2538 "single chunk unit-stride slice out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
2539 src_start,
2540 src_end,
2541 chunk_data.len(),
2542 dst_start,
2543 dst_end,
2544 result_len
2545 )));
2546 }
2547
2548 unsafe {
2549 std::ptr::copy_nonoverlapping(
2550 chunk_data.as_ptr().add(src_start),
2551 result_ptr.add(dst_start),
2552 row_bytes,
2553 );
2554 }
2555 }
2556
2557 let result_values = assume_init_vec(result_values);
2558 ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), result_values)
2559 .map(Some)
2560 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")))
2561 }
2562
2563 #[cfg(feature = "rayon")]
2569 fn read_chunked_slice_parallel<T: H5Type>(
2570 &self,
2571 index_address: u64,
2572 chunk_dims: &[u32],
2573 _element_size: u32,
2574 chunk_indexing: Option<&ChunkIndexing>,
2575 _selection: &SliceInfo,
2576 resolved: &ResolvedSelection,
2577 ) -> Result<ArrayD<T>> {
2578 if resolved.result_elements == 0 {
2579 return self.make_fill_array_from_shape::<T>(0, &resolved.result_shape);
2580 }
2581
2582 if Cursor::is_undefined_offset(index_address, self.offset_size()) {
2583 return self
2584 .make_fill_array_from_shape::<T>(resolved.result_elements, &resolved.result_shape);
2585 }
2586
2587 let ndim = self.ndim();
2588 let shape = &self.dataspace.dims;
2589 let elem_size = self.raw_element_size()?;
2590 let chunk_shape: Vec<u64> = chunk_dims.iter().map(|&d| d as u64).collect();
2591 validate_chunk_shape(shape, &chunk_shape)?;
2592 let mut first_chunk = vec![0u64; ndim];
2593 let mut last_chunk = vec![0u64; ndim];
2594 for d in 0..ndim {
2595 let (first, last) = resolved.dims[d]
2596 .chunk_index_range(chunk_shape[d])
2597 .expect("zero-sized result handled above");
2598 first_chunk[d] = first;
2599 last_chunk[d] = last;
2600 }
2601
2602 let mut overlapping = self.collect_chunk_entries(
2604 index_address,
2605 chunk_dims,
2606 chunk_indexing,
2607 ChunkEntrySelection {
2608 shape,
2609 ndim,
2610 elem_size,
2611 chunk_bounds: Some((&first_chunk, &last_chunk)),
2612 },
2613 )?;
2614 let fully_covered_grid = validate_chunk_grid_coverage(
2615 &mut overlapping,
2616 shape,
2617 &chunk_shape,
2618 &first_chunk,
2619 &last_chunk,
2620 )?;
2621
2622 let result_total_bytes = checked_mul_usize(
2624 resolved.result_elements,
2625 elem_size,
2626 "slice result size in bytes",
2627 )?;
2628 let result_dims = resolved.result_dims_with_collapsed();
2630 let mut result_strides = vec![1usize; ndim];
2631 for d in (0..ndim.saturating_sub(1)).rev() {
2632 result_strides[d] =
2633 checked_mul_usize(result_strides[d + 1], result_dims[d + 1], "result stride")?;
2634 }
2635 let mut chunk_strides = vec![1usize; ndim];
2636 for d in (0..ndim.saturating_sub(1)).rev() {
2637 chunk_strides[d] = checked_mul_usize(
2638 chunk_strides[d + 1],
2639 chunk_shape[d + 1] as usize,
2640 "chunk stride",
2641 )?;
2642 }
2643 let use_unit_stride_fast_path = resolved.is_unit_stride();
2644 let fully_covered_unit_stride = use_unit_stride_fast_path && fully_covered_grid;
2645
2646 if fully_covered_unit_stride {
2647 if T::native_copy_compatible(&self.datatype) && std::mem::size_of::<T>() == elem_size {
2648 let mut result_values: Vec<MaybeUninit<T>> =
2649 std::iter::repeat_with(MaybeUninit::<T>::uninit)
2650 .take(resolved.result_elements)
2651 .collect();
2652 let flat = FlatBufferPtr {
2653 ptr: result_values.as_mut_ptr() as *mut u8,
2654 len: checked_mul_usize(
2655 result_values.len(),
2656 std::mem::size_of::<T>(),
2657 "typed slice result size in bytes",
2658 )?,
2659 };
2660
2661 overlapping
2662 .par_iter()
2663 .map(|entry| {
2664 let chunk_data = self.load_exact_chunk_data(
2665 entry,
2666 index_address,
2667 &chunk_shape,
2668 elem_size,
2669 )?;
2670
2671 unsafe {
2672 flat.copy_unit_stride_chunk_overlap(
2673 &chunk_data,
2674 UnitStrideCopyLayout {
2675 chunk_offsets: &entry.offsets,
2676 chunk_shape: &chunk_shape,
2677 dataset_shape: shape,
2678 resolved,
2679 chunk_strides: &chunk_strides,
2680 result_strides: &result_strides,
2681 elem_size,
2682 },
2683 )?;
2684 }
2685
2686 Ok(())
2687 })
2688 .collect::<std::result::Result<Vec<_>, Error>>()?;
2689
2690 let result_values = assume_init_vec(result_values);
2691 return ArrayD::from_shape_vec(IxDyn(&resolved.result_shape), result_values)
2692 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")));
2693 }
2694
2695 let mut result_buf = vec![MaybeUninit::<u8>::uninit(); result_total_bytes];
2696 let flat = FlatBufferPtr {
2697 ptr: result_buf.as_mut_ptr() as *mut u8,
2698 len: result_buf.len(),
2699 };
2700
2701 overlapping
2702 .par_iter()
2703 .map(|entry| {
2704 let chunk_data =
2705 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2706
2707 unsafe {
2708 flat.copy_unit_stride_chunk_overlap(
2709 &chunk_data,
2710 UnitStrideCopyLayout {
2711 chunk_offsets: &entry.offsets,
2712 chunk_shape: &chunk_shape,
2713 dataset_shape: shape,
2714 resolved,
2715 chunk_strides: &chunk_strides,
2716 result_strides: &result_strides,
2717 elem_size,
2718 },
2719 )?;
2720 }
2721
2722 Ok(())
2723 })
2724 .collect::<std::result::Result<Vec<_>, Error>>()?;
2725
2726 let result_buf = assume_init_u8_vec(result_buf);
2727 return self.decode_buffer_with_shape::<T>(
2728 &result_buf,
2729 resolved.result_elements,
2730 &resolved.result_shape,
2731 );
2732 }
2733
2734 let mut result_buf = self.make_output_buffer(result_total_bytes);
2735
2736 let flat = FlatBufferPtr {
2737 ptr: result_buf.as_mut_ptr(),
2738 len: result_buf.len(),
2739 };
2740
2741 overlapping
2742 .par_iter()
2743 .map(|entry| {
2744 let chunk_data =
2745 self.load_exact_chunk_data(entry, index_address, &chunk_shape, elem_size)?;
2746
2747 if use_unit_stride_fast_path {
2748 unsafe {
2749 flat.copy_unit_stride_chunk_overlap(
2750 &chunk_data,
2751 UnitStrideCopyLayout {
2752 chunk_offsets: &entry.offsets,
2753 chunk_shape: &chunk_shape,
2754 dataset_shape: shape,
2755 resolved,
2756 chunk_strides: &chunk_strides,
2757 result_strides: &result_strides,
2758 elem_size,
2759 },
2760 )?;
2761 }
2762 return Ok(());
2763 }
2764
2765 let mut dim_indices: Vec<Vec<(usize, usize)>> = Vec::with_capacity(ndim);
2768 for d in 0..ndim {
2769 let chunk_start = entry.offsets[d];
2770 let chunk_end = (chunk_start + chunk_shape[d]).min(shape[d]);
2771 let dim = &resolved.dims[d];
2772 let sel_start = dim.start;
2773 let sel_end = dim.end;
2774 let sel_step = dim.step;
2775 let mut indices = Vec::new();
2776
2777 let first_sel = if sel_start >= chunk_start {
2778 sel_start
2779 } else {
2780 let steps_to_skip = (chunk_start - sel_start).div_ceil(sel_step);
2781 sel_start + steps_to_skip * sel_step
2782 };
2783
2784 let mut sel_idx = first_sel;
2785 while sel_idx < sel_end && sel_idx < chunk_end {
2786 let chunk_local =
2787 checked_usize(sel_idx - chunk_start, "chunk-local index")?;
2788 let result_dim_idx =
2789 checked_usize((sel_idx - dim.start) / sel_step, "result index")?;
2790 indices.push((chunk_local, result_dim_idx));
2791 sel_idx += sel_step;
2792 }
2793
2794 dim_indices.push(indices);
2795 }
2796
2797 unsafe {
2801 flat.copy_selected(
2802 &chunk_data,
2803 &dim_indices,
2804 &chunk_strides,
2805 &result_strides,
2806 elem_size,
2807 ndim,
2808 )?;
2809 }
2810
2811 Ok(())
2812 })
2813 .collect::<std::result::Result<Vec<_>, Error>>()?;
2814
2815 self.decode_buffer_with_shape::<T>(
2816 &result_buf,
2817 resolved.result_elements,
2818 &resolved.result_shape,
2819 )
2820 }
2821
2822 fn read_contiguous_slice<T: H5Type>(
2823 &self,
2824 address: u64,
2825 size: u64,
2826 resolved: &ResolvedSelection,
2827 ) -> Result<ArrayD<T>> {
2828 if resolved.result_elements == 0 {
2829 return self.make_fill_array_from_shape::<T>(0, &resolved.result_shape);
2830 }
2831
2832 if self.external_files.is_none()
2833 && (Cursor::is_undefined_offset(address, self.offset_size()) || size == 0)
2834 {
2835 return self
2836 .make_fill_array_from_shape::<T>(resolved.result_elements, &resolved.result_shape);
2837 }
2838 if self.external_files.is_none() {
2839 self.validate_allocated_raw_data_len(
2840 "contiguous",
2841 checked_usize(size, "contiguous dataset size")?,
2842 )?;
2843 }
2844
2845 let shape = &self.dataspace.dims;
2846 if selection_covers_full_dataset(resolved, shape) {
2847 return self.read_contiguous::<T>(address, size);
2848 }
2849
2850 let elem_size = self.raw_element_size()?;
2851 let result_total_bytes = checked_mul_usize(
2852 resolved.result_elements,
2853 elem_size,
2854 "contiguous slice result size in bytes",
2855 )?;
2856 let dataset_strides = row_major_strides(shape, "contiguous dataset stride")?;
2857 let result_dims = resolved.result_dims_with_collapsed();
2858 let result_strides = result_strides_for_dims(&result_dims)?;
2859 let result_buf = self.read_contiguous_slice_bytes_direct(
2860 address,
2861 size,
2862 resolved,
2863 ContiguousSliceDirectLayout {
2864 dataset_strides: &dataset_strides,
2865 result_strides: &result_strides,
2866 elem_size,
2867 result_total_bytes,
2868 },
2869 )?;
2870
2871 self.decode_buffer_with_shape::<T>(
2872 &result_buf,
2873 resolved.result_elements,
2874 &resolved.result_shape,
2875 )
2876 }
2877
2878 fn read_contiguous_slice_bytes_direct(
2879 &self,
2880 address: u64,
2881 size: u64,
2882 resolved: &ResolvedSelection,
2883 layout: ContiguousSliceDirectLayout<'_>,
2884 ) -> Result<Vec<u8>> {
2885 let shape = &self.dataspace.dims;
2886 let ndim = shape.len();
2887 if resolved.dims.len() != ndim
2888 || layout.dataset_strides.len() != ndim
2889 || layout.result_strides.len() != ndim
2890 {
2891 return Err(Error::InvalidData(format!(
2892 "contiguous slice layout rank does not match dataset rank {ndim}"
2893 )));
2894 }
2895
2896 let storage_len = if self.external_files.is_some() {
2897 checked_mul_usize(
2898 checked_usize(self.num_elements()?, "dataset element count")?,
2899 layout.elem_size,
2900 "external dataset size",
2901 )?
2902 } else {
2903 checked_usize(size, "contiguous dataset size")?
2904 };
2905 let tail_start = contiguous_slice_tail_start(shape, resolved);
2906 let block_elements = contiguous_slice_block_elements(resolved, tail_start)?;
2907 let block_bytes = checked_mul_usize(
2908 block_elements,
2909 layout.elem_size,
2910 "contiguous slice block size in bytes",
2911 )?;
2912 let mut result_buf = self.make_output_buffer(layout.result_total_bytes);
2913
2914 let prefix_blocks =
2915 resolved.dims[..tail_start]
2916 .iter()
2917 .try_fold(1usize, |acc, dim| -> Result<usize> {
2918 checked_mul_usize(acc, dim.count, "contiguous slice block count")
2919 })?;
2920 let mut counters = vec![0usize; tail_start];
2921
2922 for _ in 0..prefix_blocks {
2923 let mut source_elem = 0usize;
2924 let mut result_elem = 0usize;
2925
2926 for (d, &counter) in counters.iter().enumerate().take(tail_start) {
2927 let ordinal = u64::try_from(counter).map_err(|_| {
2928 Error::InvalidData("contiguous slice ordinal exceeds u64".to_string())
2929 })?;
2930 let coord = checked_add_u64(
2931 resolved.dims[d].start,
2932 checked_mul_u64(
2933 ordinal,
2934 resolved.dims[d].step,
2935 "contiguous slice coordinate",
2936 )?,
2937 "contiguous slice coordinate",
2938 )?;
2939 let coord = checked_usize(coord, "contiguous slice source index")?;
2940 let source_term =
2941 checked_mul_usize(coord, layout.dataset_strides[d], "contiguous slice source")?;
2942 let result_term = checked_mul_usize(
2943 counter,
2944 layout.result_strides[d],
2945 "contiguous slice result",
2946 )?;
2947 source_elem =
2948 checked_add_usize(source_elem, source_term, "contiguous slice source")?;
2949 result_elem =
2950 checked_add_usize(result_elem, result_term, "contiguous slice result")?;
2951 }
2952
2953 for (d, &dataset_stride) in layout
2954 .dataset_strides
2955 .iter()
2956 .enumerate()
2957 .take(ndim)
2958 .skip(tail_start)
2959 {
2960 let coord = checked_usize(resolved.dims[d].start, "contiguous slice source index")?;
2961 let source_term =
2962 checked_mul_usize(coord, dataset_stride, "contiguous slice source")?;
2963 source_elem =
2964 checked_add_usize(source_elem, source_term, "contiguous slice source")?;
2965 }
2966
2967 let source_start = checked_mul_usize(
2968 source_elem,
2969 layout.elem_size,
2970 "contiguous slice source byte offset",
2971 )?;
2972 let source_end = checked_add_usize(
2973 source_start,
2974 block_bytes,
2975 "contiguous slice source byte end",
2976 )?;
2977 if source_end > storage_len {
2978 return Err(Error::InvalidData(format!(
2979 "contiguous slice range {}..{} exceeds dataset storage size {}",
2980 source_start, source_end, storage_len
2981 )));
2982 }
2983
2984 let dst_start = checked_mul_usize(
2985 result_elem,
2986 layout.elem_size,
2987 "contiguous slice destination byte offset",
2988 )?;
2989 let dst_end = checked_add_usize(
2990 dst_start,
2991 block_bytes,
2992 "contiguous slice destination byte end",
2993 )?;
2994 if dst_end > result_buf.len() {
2995 return Err(Error::InvalidData(format!(
2996 "contiguous slice destination range {}..{} exceeds result size {}",
2997 dst_start,
2998 dst_end,
2999 result_buf.len()
3000 )));
3001 }
3002
3003 let block = self.read_contiguous_logical_range(address, source_start, block_bytes)?;
3004 if block.len() != block_bytes {
3005 return Err(Error::InvalidData(format!(
3006 "contiguous slice read returned {} bytes, expected {}",
3007 block.len(),
3008 block_bytes
3009 )));
3010 }
3011 result_buf[dst_start..dst_end].copy_from_slice(&block);
3012
3013 let mut carry = true;
3014 for d in (0..tail_start).rev() {
3015 if carry {
3016 counters[d] += 1;
3017 if counters[d] < resolved.dims[d].count {
3018 carry = false;
3019 } else {
3020 counters[d] = 0;
3021 }
3022 }
3023 }
3024 }
3025
3026 Ok(result_buf)
3027 }
3028
3029 fn read_compact_slice<T: H5Type>(
3030 &self,
3031 data: &[u8],
3032 selection: &SliceInfo,
3033 ) -> Result<ArrayD<T>> {
3034 let full = self.read_compact::<T>(data)?;
3035 slice_array(&full, selection, &self.dataspace.dims)
3036 }
3037
3038 fn decode_buffer_with_shape<T: H5Type>(
3039 &self,
3040 raw: &[u8],
3041 n: usize,
3042 shape: &[usize],
3043 ) -> Result<ArrayD<T>> {
3044 let elem_size = self.raw_element_size()?;
3045 let expected_bytes = checked_mul_usize(n, elem_size, "decoded buffer byte length")?;
3046 if raw.len() != expected_bytes {
3047 return Err(Error::InvalidData(format!(
3048 "decoded buffer has {} bytes, expected {} bytes",
3049 raw.len(),
3050 expected_bytes
3051 )));
3052 }
3053
3054 if let Some(elements) = T::decode_vec(raw, &self.datatype, n) {
3055 let elements = elements?;
3056 return ArrayD::from_shape_vec(IxDyn(shape), elements)
3057 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")));
3058 }
3059
3060 let mut elements = Vec::with_capacity(n);
3061 for i in 0..n {
3062 let start = checked_mul_usize(i, elem_size, "decoded element byte offset")?;
3063 let end = checked_mul_usize(i + 1, elem_size, "decoded element end offset")?;
3064 elements.push(T::from_bytes(&raw[start..end], &self.datatype)?);
3065 }
3066
3067 ArrayD::from_shape_vec(IxDyn(shape), elements)
3068 .map_err(|e| Error::InvalidData(format!("array shape error: {e}")))
3069 }
3070
3071 fn decode_raw_data<T: H5Type>(&self, raw: &[u8]) -> Result<ArrayD<T>> {
3072 let n = checked_usize(self.num_elements()?, "dataset element count")?;
3073 let mut shape = Vec::with_capacity(self.dataspace.dims.len());
3074 for &dim in &self.dataspace.dims {
3075 shape.push(checked_usize(dim, "dataset dimension")?);
3076 }
3077 self.decode_buffer_with_shape::<T>(raw, n, &shape)
3078 }
3079
3080 fn make_fill_array<T: H5Type>(&self) -> Result<ArrayD<T>> {
3081 let n = checked_usize(self.num_elements()?, "dataset element count")?;
3082 let mut shape = Vec::with_capacity(self.dataspace.dims.len());
3083 for &dim in &self.dataspace.dims {
3084 shape.push(checked_usize(dim, "dataset dimension")?);
3085 }
3086 self.make_fill_array_from_shape::<T>(n, &shape)
3087 }
3088
3089 fn make_fill_array_from_shape<T: H5Type>(
3090 &self,
3091 element_count: usize,
3092 shape: &[usize],
3093 ) -> Result<ArrayD<T>> {
3094 let elem_size = self.raw_element_size()?;
3095 let total_bytes = checked_mul_usize(element_count, elem_size, "fill result size in bytes")?;
3096 let fill = self.make_output_buffer(total_bytes);
3097 self.decode_buffer_with_shape::<T>(&fill, element_count, shape)
3098 }
3099
3100 fn make_output_buffer(&self, total_bytes: usize) -> Vec<u8> {
3101 let mut buf = vec![0u8; total_bytes];
3102 self.fill_output_buffer(&mut buf);
3103 buf
3104 }
3105
3106 fn fill_output_buffer(&self, buf: &mut [u8]) {
3107 buf.fill(0);
3108 if let Some(ref fv) = self.fill_value {
3109 if let Some(ref fill_bytes) = fv.value {
3110 if !fill_bytes.is_empty() {
3111 for chunk in buf.chunks_exact_mut(fill_bytes.len()) {
3112 chunk.copy_from_slice(fill_bytes);
3113 }
3114 }
3115 }
3116 }
3117 }
3118
3119 fn validate_allocated_raw_data_len(&self, storage_kind: &str, actual_len: usize) -> Result<()> {
3120 let expected_len = self.raw_byte_len()?;
3121 if actual_len != expected_len {
3122 return Err(Error::InvalidData(format!(
3123 "{storage_kind} raw data has {actual_len} bytes, expected {expected_len} bytes"
3124 )));
3125 }
3126 Ok(())
3127 }
3128
3129 fn convert_to_native_endian(&self, bytes: &mut [u8]) -> Result<()> {
3130 let count = checked_usize(self.num_elements()?, "dataset element count")?;
3131 convert_datatype_to_native_endian(&self.datatype, self.vlen_reference_size(), bytes, count)
3132 }
3133}
3134
3135fn native_byte_order() -> ByteOrder {
3136 if cfg!(target_endian = "little") {
3137 ByteOrder::LittleEndian
3138 } else {
3139 ByteOrder::BigEndian
3140 }
3141}
3142
3143fn convert_datatype_to_native_endian(
3144 dtype: &Datatype,
3145 vlen_reference_size: usize,
3146 bytes: &mut [u8],
3147 count: usize,
3148) -> Result<()> {
3149 match dtype {
3150 Datatype::FixedPoint {
3151 size, byte_order, ..
3152 }
3153 | Datatype::FloatingPoint { size, byte_order }
3154 | Datatype::Bitfield { size, byte_order } => {
3155 swap_elements_to_native(bytes, count, *size as usize, *byte_order)
3156 }
3157 Datatype::Enum { base, .. } => {
3158 convert_datatype_to_native_endian(base, vlen_reference_size, bytes, count)
3159 }
3160 Datatype::Array { base, dims } => {
3161 let array_count = dims.iter().try_fold(1usize, |acc, &dim| {
3162 checked_mul_usize(
3163 acc,
3164 checked_usize(dim, "array datatype dimension")?,
3165 "array datatype element count",
3166 )
3167 })?;
3168 let total_count =
3169 checked_mul_usize(count, array_count, "array datatype total element count")?;
3170 convert_datatype_to_native_endian(base, vlen_reference_size, bytes, total_count)
3171 }
3172 Datatype::Compound { size, fields } => {
3173 let record_size = *size as usize;
3174 let required = checked_mul_usize(count, record_size, "compound byte length")?;
3175 if bytes.len() < required {
3176 return Err(Error::InvalidData(format!(
3177 "compound native-endian conversion needs {required} bytes, got {}",
3178 bytes.len()
3179 )));
3180 }
3181
3182 for record in 0..count {
3183 let record_start =
3184 checked_mul_usize(record, record_size, "compound record byte offset")?;
3185 for field in fields {
3186 let field_offset = field.byte_offset as usize;
3187 let field_size =
3188 raw_element_size_for_datatype(&field.datatype, vlen_reference_size)?;
3189 let field_start = checked_add_usize(
3190 record_start,
3191 field_offset,
3192 "compound field byte offset",
3193 )?;
3194 let field_end =
3195 checked_add_usize(field_start, field_size, "compound field byte end")?;
3196 if field_end > bytes.len() || field_offset + field_size > record_size {
3197 return Err(Error::InvalidData(format!(
3198 "compound field '{}' range exceeds record size",
3199 field.name
3200 )));
3201 }
3202 convert_datatype_to_native_endian(
3203 &field.datatype,
3204 vlen_reference_size,
3205 &mut bytes[field_start..field_end],
3206 1,
3207 )?;
3208 }
3209 }
3210 Ok(())
3211 }
3212 Datatype::String { .. }
3213 | Datatype::VarLen { .. }
3214 | Datatype::Opaque { .. }
3215 | Datatype::Reference { .. } => Ok(()),
3216 }
3217}
3218
3219fn swap_elements_to_native(
3220 bytes: &mut [u8],
3221 count: usize,
3222 elem_size: usize,
3223 byte_order: ByteOrder,
3224) -> Result<()> {
3225 let required = checked_mul_usize(count, elem_size, "native-endian byte length")?;
3226 if bytes.len() < required {
3227 return Err(Error::InvalidData(format!(
3228 "native-endian conversion needs {required} bytes, got {}",
3229 bytes.len()
3230 )));
3231 }
3232
3233 if elem_size <= 1 || byte_order == native_byte_order() {
3234 return Ok(());
3235 }
3236
3237 for chunk in bytes[..required].chunks_exact_mut(elem_size) {
3238 chunk.reverse();
3239 }
3240 Ok(())
3241}
3242
3243fn attribute_from_message_storage(message: &AttributeMessage, context: &FileContext) -> Attribute {
3244 let decoded_strings = message
3245 .dataspace
3246 .num_elements()
3247 .ok()
3248 .and_then(|element_count| {
3249 decoded_vlen_strings_storage(
3250 &message.datatype,
3251 &message.raw_data,
3252 context.storage.as_ref(),
3253 context.superblock.offset_size,
3254 context.superblock.length_size,
3255 element_count,
3256 )
3257 });
3258 let raw_data = match &message.datatype {
3259 Datatype::VarLen {
3260 base,
3261 kind: VarLenKind::String,
3262 ..
3263 } if matches!(base.as_ref(), Datatype::FixedPoint { size: 1, .. })
3264 && matches!(message.dataspace.num_elements(), Ok(1)) =>
3265 {
3266 resolve_vlen_bytes_storage(
3267 &message.raw_data,
3268 context.storage.as_ref(),
3269 context.superblock.offset_size,
3270 context.superblock.length_size,
3271 )
3272 .unwrap_or_else(|| message.raw_data.clone())
3273 }
3274 _ => message.raw_data.clone(),
3275 };
3276
3277 Attribute {
3278 name: message.name.clone(),
3279 datatype: message.datatype.clone(),
3280 shape: match message.dataspace.dataspace_type {
3281 DataspaceType::Scalar => vec![],
3282 DataspaceType::Null => vec![0],
3283 DataspaceType::Simple => message.dataspace.dims.clone(),
3284 },
3285 raw_data,
3286 decoded_strings,
3287 }
3288}
3289
3290fn normalize_layout(layout: DataLayout, dataspace: &DataspaceMessage) -> DataLayout {
3291 match layout {
3292 DataLayout::Chunked {
3293 address,
3294 mut dims,
3295 mut element_size,
3296 chunk_indexing,
3297 } if dims.len() == dataspace.dims.len() + 1 => {
3298 if let Some(legacy_element_size) = dims.pop() {
3299 if element_size == 0 {
3300 element_size = legacy_element_size;
3301 }
3302 }
3303 DataLayout::Chunked {
3304 address,
3305 dims,
3306 element_size,
3307 chunk_indexing,
3308 }
3309 }
3310 other => other,
3311 }
3312}
3313
3314fn raw_element_size_for_datatype(dtype: &Datatype, vlen_reference_size: usize) -> Result<usize> {
3315 match dtype {
3316 Datatype::String {
3317 size: StringSize::Variable,
3318 ..
3319 }
3320 | Datatype::VarLen { .. } => Ok(vlen_reference_size),
3321 Datatype::Array { base, dims } => {
3322 let base_size = raw_element_size_for_datatype(base, vlen_reference_size)?;
3323 let count = dims.iter().try_fold(1usize, |acc, &dim| {
3324 let dim = checked_usize(dim, "array datatype dimension")?;
3325 checked_mul_usize(acc, dim, "array datatype element count")
3326 })?;
3327 checked_mul_usize(base_size, count, "array datatype byte size")
3328 }
3329 Datatype::Enum { base, .. } => raw_element_size_for_datatype(base, vlen_reference_size),
3330 Datatype::FixedPoint { size, .. }
3331 | Datatype::FloatingPoint { size, .. }
3332 | Datatype::Bitfield { size, .. }
3333 | Datatype::Reference { size, .. } => Ok(*size as usize),
3334 Datatype::String {
3335 size: StringSize::Fixed(len),
3336 ..
3337 } => Ok(*len as usize),
3338 Datatype::Compound { size, .. } | Datatype::Opaque { size, .. } => Ok(*size as usize),
3339 }
3340}
3341
3342#[cfg(test)]
3343fn copy_chunk_to_flat(
3345 chunk_data: &[u8],
3346 flat: &mut [u8],
3347 chunk_offsets: &[u64],
3348 chunk_shape: &[u64],
3349 dataset_shape: &[u64],
3350 elem_size: usize,
3351) -> Result<()> {
3352 let dataset_strides = row_major_strides(dataset_shape, "dataset stride")
3353 .expect("dataset strides should fit in usize");
3354 let chunk_strides =
3355 row_major_strides(chunk_shape, "chunk stride").expect("chunk strides should fit in usize");
3356 copy_chunk_to_flat_with_strides(
3357 chunk_data,
3358 flat,
3359 ChunkCopyLayout {
3360 chunk_offsets,
3361 chunk_shape,
3362 dataset_shape,
3363 dataset_strides: &dataset_strides,
3364 chunk_strides: &chunk_strides,
3365 elem_size,
3366 },
3367 )
3368}
3369
3370fn copy_chunk_to_flat_with_strides(
3371 chunk_data: &[u8],
3372 flat: &mut [u8],
3373 layout: ChunkCopyLayout<'_>,
3374) -> Result<()> {
3375 unsafe {
3376 copy_chunk_to_flat_with_strides_ptr(
3377 chunk_data,
3378 FlatBufferPtr {
3379 ptr: flat.as_mut_ptr(),
3380 len: flat.len(),
3381 },
3382 layout,
3383 )
3384 }
3385}
3386
3387#[inline(always)]
3388unsafe fn copy_chunk_to_flat_with_strides_ptr(
3389 chunk_data: &[u8],
3390 flat: FlatBufferPtr,
3391 layout: ChunkCopyLayout<'_>,
3392) -> Result<()> {
3393 let ndim = layout.dataset_shape.len();
3394 if layout.chunk_offsets.len() != ndim
3395 || layout.chunk_shape.len() != ndim
3396 || layout.dataset_strides.len() != ndim
3397 || layout.chunk_strides.len() != ndim
3398 {
3399 return Err(Error::InvalidData(format!(
3400 "chunk copy layout rank does not match dataset rank {ndim}"
3401 )));
3402 }
3403
3404 if ndim == 0 {
3405 if chunk_data.len() < layout.elem_size || flat.len < layout.elem_size {
3406 return Err(Error::InvalidData(format!(
3407 "scalar chunk copy requires {} bytes, got source {} and destination {}",
3408 layout.elem_size,
3409 chunk_data.len(),
3410 flat.len
3411 )));
3412 }
3413 std::ptr::copy_nonoverlapping(chunk_data.as_ptr(), flat.ptr, layout.elem_size);
3414 return Ok(());
3415 }
3416
3417 let mut actual_chunk_shape = Vec::with_capacity(ndim);
3419 for i in 0..ndim {
3420 if layout.chunk_offsets[i] >= layout.dataset_shape[i] {
3421 return Err(Error::InvalidData(format!(
3422 "chunk offset {} is outside dimension {} of size {}",
3423 layout.chunk_offsets[i], i, layout.dataset_shape[i]
3424 )));
3425 }
3426 let remaining = layout.dataset_shape[i] - layout.chunk_offsets[i];
3427 actual_chunk_shape.push(checked_usize(
3428 remaining.min(layout.chunk_shape[i]),
3429 "actual chunk extent",
3430 )?);
3431 }
3432
3433 let row_elems = *actual_chunk_shape.last().unwrap_or(&1);
3434 let row_bytes = checked_mul_usize(row_elems, layout.elem_size, "chunk row bytes")?;
3435 let mut dataset_origin = 0usize;
3436 for (d, offset) in layout.chunk_offsets.iter().enumerate() {
3437 let offset = checked_usize(*offset, "chunk offset")?;
3438 let term = checked_mul_usize(offset, layout.dataset_strides[d], "chunk origin")?;
3439 dataset_origin = checked_add_usize(dataset_origin, term, "chunk origin")?;
3440 }
3441
3442 if ndim == 1 {
3443 let dst_start = checked_mul_usize(dataset_origin, layout.elem_size, "chunk dst offset")?;
3444 let dst_end = checked_add_usize(dst_start, row_bytes, "chunk dst end")?;
3445 if row_bytes > chunk_data.len() || dst_end > flat.len {
3446 return Err(Error::InvalidData(format!(
3447 "chunk copy out of bounds: source row needs {} bytes from {} bytes, destination range {}..{} exceeds {} bytes",
3448 row_bytes,
3449 chunk_data.len(),
3450 dst_start,
3451 dst_end,
3452 flat.len
3453 )));
3454 }
3455 std::ptr::copy_nonoverlapping(chunk_data.as_ptr(), flat.ptr.add(dst_start), row_bytes);
3456 return Ok(());
3457 }
3458
3459 let outer_dims = &actual_chunk_shape[..ndim - 1];
3460 let total_rows = checked_product_usize(outer_dims, "chunk row count")?;
3461 let mut outer_idx = vec![0usize; ndim - 1];
3462
3463 for _ in 0..total_rows {
3464 let mut chunk_row = 0usize;
3465 let mut dataset_row = dataset_origin;
3466 for (d, outer) in outer_idx.iter().copied().enumerate() {
3467 let chunk_term = checked_mul_usize(outer, layout.chunk_strides[d], "chunk row")?;
3468 let dataset_term = checked_mul_usize(outer, layout.dataset_strides[d], "dataset row")?;
3469 chunk_row = checked_add_usize(chunk_row, chunk_term, "chunk row")?;
3470 dataset_row = checked_add_usize(dataset_row, dataset_term, "dataset row")?;
3471 }
3472
3473 let src_start = checked_mul_usize(chunk_row, layout.elem_size, "chunk src offset")?;
3474 let dst_start = checked_mul_usize(dataset_row, layout.elem_size, "chunk dst offset")?;
3475 let src_end = checked_add_usize(src_start, row_bytes, "chunk src end")?;
3476 let dst_end = checked_add_usize(dst_start, row_bytes, "chunk dst end")?;
3477 if src_end > chunk_data.len() || dst_end > flat.len {
3478 return Err(Error::InvalidData(format!(
3479 "chunk copy out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
3480 src_start,
3481 src_end,
3482 chunk_data.len(),
3483 dst_start,
3484 dst_end,
3485 flat.len
3486 )));
3487 }
3488 std::ptr::copy_nonoverlapping(
3489 chunk_data.as_ptr().add(src_start),
3490 flat.ptr.add(dst_start),
3491 row_bytes,
3492 );
3493
3494 let mut carry = true;
3495 for d in (0..outer_idx.len()).rev() {
3496 if carry {
3497 outer_idx[d] += 1;
3498 if outer_idx[d] < outer_dims[d] {
3499 carry = false;
3500 } else {
3501 outer_idx[d] = 0;
3502 }
3503 }
3504 }
3505 }
3506
3507 Ok(())
3508}
3509
3510fn checked_product_usize(values: &[usize], context: &str) -> Result<usize> {
3511 let mut product = 1usize;
3512 for &value in values {
3513 product = checked_mul_usize(product, value, context)?;
3514 }
3515 Ok(product)
3516}
3517
3518fn unit_stride_chunk_overlap_plan(
3519 chunk_offsets: &[u64],
3520 chunk_shape: &[u64],
3521 dataset_shape: &[u64],
3522 resolved: &ResolvedSelection,
3523) -> Result<(Vec<usize>, Vec<usize>, Vec<usize>)> {
3524 let ndim = dataset_shape.len();
3525 let mut overlap_counts = Vec::with_capacity(ndim);
3526 let mut chunk_local_start = Vec::with_capacity(ndim);
3527 let mut result_start = Vec::with_capacity(ndim);
3528
3529 for d in 0..ndim {
3530 let chunk_start = chunk_offsets[d];
3531 let chunk_end = (chunk_start + chunk_shape[d]).min(dataset_shape[d]);
3532 let dim = &resolved.dims[d];
3533 let overlap_start = chunk_start.max(dim.start);
3534 let overlap_end = chunk_end.min(dim.end);
3535 if overlap_start >= overlap_end {
3536 return Ok((Vec::new(), Vec::new(), Vec::new()));
3537 }
3538
3539 overlap_counts.push(checked_usize(
3540 overlap_end - overlap_start,
3541 "chunk overlap size",
3542 )?);
3543 chunk_local_start.push(checked_usize(
3544 overlap_start - chunk_start,
3545 "chunk overlap start",
3546 )?);
3547 result_start.push(checked_usize(
3548 overlap_start - dim.start,
3549 "slice result overlap start",
3550 )?);
3551 }
3552
3553 Ok((overlap_counts, chunk_local_start, result_start))
3554}
3555
3556#[inline(always)]
3557fn copy_unit_stride_chunk_overlap(
3558 chunk_data: &[u8],
3559 result_buf: &mut [u8],
3560 layout: UnitStrideCopyLayout<'_>,
3561) -> Result<()> {
3562 unsafe {
3563 copy_unit_stride_chunk_overlap_ptr(
3564 chunk_data,
3565 FlatBufferPtr {
3566 ptr: result_buf.as_mut_ptr(),
3567 len: result_buf.len(),
3568 },
3569 layout,
3570 )
3571 }
3572}
3573
3574#[inline(always)]
3585unsafe fn copy_unit_stride_chunk_overlap_ptr(
3586 chunk_data: &[u8],
3587 result: FlatBufferPtr,
3588 layout: UnitStrideCopyLayout<'_>,
3589) -> Result<()> {
3590 let ndim = layout.dataset_shape.len();
3591 if layout.chunk_offsets.len() != ndim
3592 || layout.chunk_shape.len() != ndim
3593 || layout.resolved.dims.len() != ndim
3594 || layout.chunk_strides.len() != ndim
3595 || layout.result_strides.len() != ndim
3596 {
3597 return Err(Error::InvalidData(format!(
3598 "unit-stride copy layout rank does not match dataset rank {ndim}"
3599 )));
3600 }
3601
3602 if ndim == 0 {
3603 if chunk_data.len() < layout.elem_size || result.len < layout.elem_size {
3604 return Err(Error::InvalidData(format!(
3605 "scalar slice copy requires {} bytes, got source {} and destination {}",
3606 layout.elem_size,
3607 chunk_data.len(),
3608 result.len
3609 )));
3610 }
3611 std::ptr::copy_nonoverlapping(chunk_data.as_ptr(), result.ptr, layout.elem_size);
3612 return Ok(());
3613 }
3614
3615 let (overlap_counts, chunk_local_start, result_start) = unit_stride_chunk_overlap_plan(
3616 layout.chunk_offsets,
3617 layout.chunk_shape,
3618 layout.dataset_shape,
3619 layout.resolved,
3620 )?;
3621 if overlap_counts.is_empty() {
3622 return Ok(());
3623 }
3624
3625 let row_elems = *overlap_counts.last().unwrap_or(&1);
3626 let row_bytes = checked_mul_usize(row_elems, layout.elem_size, "unit-stride slice row bytes")?;
3627
3628 let mut chunk_origin = 0usize;
3629 let mut result_origin = 0usize;
3630 for d in 0..ndim {
3631 let chunk_term = checked_mul_usize(
3632 chunk_local_start[d],
3633 layout.chunk_strides[d],
3634 "chunk overlap origin",
3635 )?;
3636 let result_term = checked_mul_usize(
3637 result_start[d],
3638 layout.result_strides[d],
3639 "slice result origin",
3640 )?;
3641 chunk_origin = checked_add_usize(chunk_origin, chunk_term, "chunk overlap origin")?;
3642 result_origin = checked_add_usize(result_origin, result_term, "slice result origin")?;
3643 }
3644
3645 if ndim == 1 {
3646 let src_start = checked_mul_usize(chunk_origin, layout.elem_size, "slice src offset")?;
3647 let dst_start = checked_mul_usize(result_origin, layout.elem_size, "slice dst offset")?;
3648 let src_end = checked_add_usize(src_start, row_bytes, "slice src end")?;
3649 let dst_end = checked_add_usize(dst_start, row_bytes, "slice dst end")?;
3650 if src_end > chunk_data.len() || dst_end > result.len {
3651 return Err(Error::InvalidData(format!(
3652 "unit-stride slice copy out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
3653 src_start,
3654 src_end,
3655 chunk_data.len(),
3656 dst_start,
3657 dst_end,
3658 result.len
3659 )));
3660 }
3661 std::ptr::copy_nonoverlapping(
3662 chunk_data.as_ptr().add(src_start),
3663 result.ptr.add(dst_start),
3664 row_bytes,
3665 );
3666 return Ok(());
3667 }
3668
3669 let outer_counts = &overlap_counts[..ndim - 1];
3670 let total_rows = checked_product_usize(outer_counts, "unit-stride slice row count")?;
3671 let mut outer_idx = vec![0usize; ndim - 1];
3672
3673 for _ in 0..total_rows {
3674 let mut chunk_row = chunk_origin;
3675 let mut result_row = result_origin;
3676 for (d, outer) in outer_idx.iter().copied().enumerate() {
3677 let chunk_term = checked_mul_usize(outer, layout.chunk_strides[d], "slice chunk row")?;
3678 let result_term =
3679 checked_mul_usize(outer, layout.result_strides[d], "slice result row")?;
3680 chunk_row = checked_add_usize(chunk_row, chunk_term, "slice chunk row")?;
3681 result_row = checked_add_usize(result_row, result_term, "slice result row")?;
3682 }
3683
3684 let src_start = checked_mul_usize(chunk_row, layout.elem_size, "slice src offset")?;
3685 let dst_start = checked_mul_usize(result_row, layout.elem_size, "slice dst offset")?;
3686 let src_end = checked_add_usize(src_start, row_bytes, "slice src end")?;
3687 let dst_end = checked_add_usize(dst_start, row_bytes, "slice dst end")?;
3688 if src_end > chunk_data.len() || dst_end > result.len {
3689 return Err(Error::InvalidData(format!(
3690 "unit-stride slice copy out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
3691 src_start,
3692 src_end,
3693 chunk_data.len(),
3694 dst_start,
3695 dst_end,
3696 result.len
3697 )));
3698 }
3699 std::ptr::copy_nonoverlapping(
3700 chunk_data.as_ptr().add(src_start),
3701 result.ptr.add(dst_start),
3702 row_bytes,
3703 );
3704
3705 let mut carry = true;
3706 for d in (0..outer_idx.len()).rev() {
3707 if carry {
3708 outer_idx[d] += 1;
3709 if outer_idx[d] < outer_counts[d] {
3710 carry = false;
3711 } else {
3712 outer_idx[d] = 0;
3713 }
3714 }
3715 }
3716 }
3717
3718 Ok(())
3719}
3720
3721#[allow(clippy::too_many_arguments)]
3722#[inline(always)]
3726fn copy_selected_elements(
3727 chunk_data: &[u8],
3728 result_buf: &mut [u8],
3729 dim_indices: &[Vec<(usize, usize)>],
3730 chunk_strides: &[usize],
3731 result_strides: &[usize],
3732 elem_size: usize,
3733 ndim: usize,
3734) -> Result<()> {
3735 if dim_indices.len() != ndim || chunk_strides.len() != ndim || result_strides.len() != ndim {
3736 return Err(Error::InvalidData(format!(
3737 "selected-element copy layout rank does not match rank {ndim}"
3738 )));
3739 }
3740
3741 if dim_indices.iter().any(|v| v.is_empty()) {
3743 return Ok(());
3744 }
3745
3746 let counts: Vec<usize> = dim_indices.iter().map(|v| v.len()).collect();
3748 let total = checked_product_usize(&counts, "selected-element copy count")?;
3749 let mut counters = vec![0usize; ndim];
3750
3751 for _ in 0..total {
3752 let mut chunk_flat = 0;
3753 let mut result_flat = 0;
3754 for d in 0..ndim {
3755 let (cl, ri) = dim_indices[d][counters[d]];
3756 let chunk_term = checked_mul_usize(cl, chunk_strides[d], "selected chunk offset")?;
3757 let result_term = checked_mul_usize(ri, result_strides[d], "selected result offset")?;
3758 chunk_flat = checked_add_usize(chunk_flat, chunk_term, "selected chunk offset")?;
3759 result_flat = checked_add_usize(result_flat, result_term, "selected result offset")?;
3760 }
3761
3762 let src_start = checked_mul_usize(chunk_flat, elem_size, "selected source byte offset")?;
3763 let dst_start =
3764 checked_mul_usize(result_flat, elem_size, "selected destination byte offset")?;
3765 let src_end = checked_add_usize(src_start, elem_size, "selected source byte end")?;
3766 let dst_end = checked_add_usize(dst_start, elem_size, "selected destination byte end")?;
3767
3768 if src_end > chunk_data.len() || dst_end > result_buf.len() {
3769 return Err(Error::InvalidData(format!(
3770 "selected-element copy out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
3771 src_start,
3772 src_end,
3773 chunk_data.len(),
3774 dst_start,
3775 dst_end,
3776 result_buf.len()
3777 )));
3778 }
3779 result_buf[dst_start..dst_end].copy_from_slice(&chunk_data[src_start..src_end]);
3780
3781 let mut carry = true;
3783 for d in (0..ndim).rev() {
3784 if carry {
3785 counters[d] += 1;
3786 if counters[d] < dim_indices[d].len() {
3787 carry = false;
3788 } else {
3789 counters[d] = 0;
3790 }
3791 }
3792 }
3793 }
3794
3795 Ok(())
3796}
3797
3798#[cfg(feature = "rayon")]
3808#[allow(clippy::too_many_arguments)]
3809#[inline(always)]
3810unsafe fn copy_selected_elements_ptr(
3811 chunk_data: &[u8],
3812 result_ptr: *mut u8,
3813 result_len: usize,
3814 dim_indices: &[Vec<(usize, usize)>],
3815 chunk_strides: &[usize],
3816 result_strides: &[usize],
3817 elem_size: usize,
3818 ndim: usize,
3819) -> Result<()> {
3820 if dim_indices.len() != ndim || chunk_strides.len() != ndim || result_strides.len() != ndim {
3821 return Err(Error::InvalidData(format!(
3822 "selected-element copy layout rank does not match rank {ndim}"
3823 )));
3824 }
3825
3826 if dim_indices.iter().any(|v| v.is_empty()) {
3827 return Ok(());
3828 }
3829
3830 let counts: Vec<usize> = dim_indices.iter().map(|v| v.len()).collect();
3831 let total = checked_product_usize(&counts, "selected-element copy count")?;
3832 let mut counters = vec![0usize; ndim];
3833
3834 for _ in 0..total {
3835 let mut chunk_flat = 0;
3836 let mut result_flat = 0;
3837 for d in 0..ndim {
3838 let (cl, ri) = dim_indices[d][counters[d]];
3839 let chunk_term = checked_mul_usize(cl, chunk_strides[d], "selected chunk offset")?;
3840 let result_term = checked_mul_usize(ri, result_strides[d], "selected result offset")?;
3841 chunk_flat = checked_add_usize(chunk_flat, chunk_term, "selected chunk offset")?;
3842 result_flat = checked_add_usize(result_flat, result_term, "selected result offset")?;
3843 }
3844
3845 let src_start = checked_mul_usize(chunk_flat, elem_size, "selected source byte offset")?;
3846 let dst_start =
3847 checked_mul_usize(result_flat, elem_size, "selected destination byte offset")?;
3848 let src_end = checked_add_usize(src_start, elem_size, "selected source byte end")?;
3849 let dst_end = checked_add_usize(dst_start, elem_size, "selected destination byte end")?;
3850
3851 if src_end > chunk_data.len() || dst_end > result_len {
3852 return Err(Error::InvalidData(format!(
3853 "selected-element copy out of bounds: source range {}..{} of {} bytes, destination range {}..{} of {} bytes",
3854 src_start,
3855 src_end,
3856 chunk_data.len(),
3857 dst_start,
3858 dst_end,
3859 result_len
3860 )));
3861 }
3862 std::ptr::copy_nonoverlapping(
3863 chunk_data.as_ptr().add(src_start),
3864 result_ptr.add(dst_start),
3865 elem_size,
3866 );
3867
3868 let mut carry = true;
3869 for d in (0..ndim).rev() {
3870 if carry {
3871 counters[d] += 1;
3872 if counters[d] < dim_indices[d].len() {
3873 carry = false;
3874 } else {
3875 counters[d] = 0;
3876 }
3877 }
3878 }
3879 }
3880
3881 Ok(())
3882}
3883
3884fn slice_array<T: H5Type + Clone>(
3886 array: &ArrayD<T>,
3887 selection: &SliceInfo,
3888 shape: &[u64],
3889) -> Result<ArrayD<T>> {
3890 let mut result_shape = Vec::new();
3892
3893 for (i, sel) in selection.selections.iter().enumerate() {
3894 let dim_size = shape[i];
3895 match sel {
3896 SliceInfoElem::Index(idx) => {
3897 if *idx >= dim_size {
3898 return Err(Error::SliceOutOfBounds {
3899 dim: i,
3900 index: *idx,
3901 size: dim_size,
3902 });
3903 }
3904 }
3906 SliceInfoElem::Slice { start, end, step } => {
3907 let dim_size = checked_usize(dim_size, "slice dimension size")?;
3908 let actual_end = if *end == u64::MAX {
3909 dim_size
3910 } else {
3911 checked_usize(*end, "slice end")?.min(dim_size)
3912 };
3913 let actual_start = checked_usize(*start, "slice start")?;
3914 let actual_step = checked_usize(*step, "slice step")?;
3915 if actual_step == 0 {
3916 return Err(Error::InvalidData("slice step cannot be 0".into()));
3917 }
3918 if actual_start > dim_size {
3919 return Err(Error::SliceOutOfBounds {
3920 dim: i,
3921 index: *start,
3922 size: shape[i],
3923 });
3924 }
3925 let n = (actual_end - actual_start).div_ceil(actual_step);
3926 result_shape.push(n);
3927 }
3928 }
3929 }
3930
3931 let ndim = shape.len();
3933 let total = checked_product_usize(&result_shape, "slice result element count")?;
3934 let mut elements = Vec::with_capacity(total);
3935
3936 let mut result_idx = vec![0usize; result_shape.len()];
3938
3939 for _ in 0..total {
3940 let mut src_idx = Vec::with_capacity(ndim);
3942 let mut ri = 0;
3943 for sel in selection.selections.iter() {
3944 match sel {
3945 SliceInfoElem::Index(idx) => {
3946 src_idx.push(checked_usize(*idx, "slice source index")?);
3947 }
3948 SliceInfoElem::Slice { start, step, .. } => {
3949 let start = checked_usize(*start, "slice start")?;
3950 let step = checked_usize(*step, "slice step")?;
3951 let offset =
3952 checked_mul_usize(result_idx[ri], step, "slice source index offset")?;
3953 src_idx.push(checked_add_usize(start, offset, "slice source index")?);
3954 ri += 1;
3955 }
3956 }
3957 }
3958
3959 elements.push(array[IxDyn(&src_idx)].clone());
3960
3961 if !result_shape.is_empty() {
3963 let mut carry = true;
3964 for d in (0..result_shape.len()).rev() {
3965 if carry {
3966 result_idx[d] += 1;
3967 if result_idx[d] < result_shape[d] {
3968 carry = false;
3969 } else {
3970 result_idx[d] = 0;
3971 }
3972 }
3973 }
3974 }
3975 }
3976
3977 ArrayD::from_shape_vec(IxDyn(&result_shape), elements)
3978 .map_err(|e| Error::InvalidData(format!("slice shape error: {e}")))
3979}
3980
3981#[cfg(test)]
3982mod tests {
3983 use super::*;
3984 use crate::storage::BytesStorage;
3985 use crate::superblock::Superblock;
3986 use std::collections::HashMap;
3987
3988 fn test_context_with_offset_size(bytes: Vec<u8>, offset_size: u8) -> Arc<FileContext> {
3989 let storage: DynStorage = Arc::new(BytesStorage::new(bytes));
3990 Arc::new(FileContext {
3991 storage,
3992 superblock: Superblock {
3993 version: 2,
3994 offset_size,
3995 length_size: 8,
3996 group_leaf_node_k: 0,
3997 group_internal_node_k: 0,
3998 indexed_storage_k: 0,
3999 consistency_flags: 0,
4000 base_address: 0,
4001 free_space_address: u64::MAX,
4002 eof_address: 0,
4003 driver_info_address: u64::MAX,
4004 root_symbol_table_entry: None,
4005 root_object_header_address: Some(0),
4006 extension_address: None,
4007 },
4008 chunk_cache: Arc::new(ChunkCache::new(1024, 8)),
4009 header_cache: Arc::new(Mutex::new(HashMap::new())),
4010 dataset_path_cache: Arc::new(Mutex::new(HashMap::new())),
4011 filter_registry: Arc::new(FilterRegistry::default()),
4012 external_file_resolver: None,
4013 external_link_resolver: None,
4014 external_file_cache: Mutex::new(HashMap::new()),
4015 sohm_table: OnceLock::new(),
4016 full_file_cache: OnceLock::new(),
4017 })
4018 }
4019
4020 fn test_context(bytes: Vec<u8>) -> Arc<FileContext> {
4021 test_context_with_offset_size(bytes, 8)
4022 }
4023
4024 fn fixed_u16_dataset(layout: DataLayout, storage_bytes: Vec<u8>) -> Dataset {
4025 let context = test_context(storage_bytes);
4026 Dataset {
4027 context: context.clone(),
4028 name: "short".to_string(),
4029 data_address: 0,
4030 dataspace: DataspaceMessage {
4031 rank: 1,
4032 dims: vec![3],
4033 max_dims: None,
4034 dataspace_type: DataspaceType::Simple,
4035 },
4036 datatype: Datatype::FixedPoint {
4037 size: 2,
4038 signed: false,
4039 byte_order: ByteOrder::LittleEndian,
4040 },
4041 layout,
4042 fill_value: None,
4043 filters: None,
4044 external_files: None,
4045 attributes: Vec::new(),
4046 chunk_cache: context.chunk_cache.clone(),
4047 chunk_entry_cache: Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(32).unwrap()))),
4048 full_chunk_entries: Arc::new(OnceLock::new()),
4049 full_dataset_bytes: Arc::new(OnceLock::new()),
4050 external_slots: Arc::new(OnceLock::new()),
4051 filter_registry: context.filter_registry.clone(),
4052 }
4053 }
4054
4055 fn vlen_string_dataset(
4056 layout: DataLayout,
4057 storage_bytes: Vec<u8>,
4058 offset_size: u8,
4059 fill_value: Option<FillValueMessage>,
4060 ) -> Dataset {
4061 let context = test_context_with_offset_size(storage_bytes, offset_size);
4062 Dataset {
4063 context: context.clone(),
4064 name: "vlen".to_string(),
4065 data_address: 0,
4066 dataspace: DataspaceMessage {
4067 rank: 1,
4068 dims: vec![2],
4069 max_dims: None,
4070 dataspace_type: DataspaceType::Simple,
4071 },
4072 datatype: Datatype::String {
4073 size: StringSize::Variable,
4074 encoding: crate::messages::datatype::StringEncoding::Ascii,
4075 padding: crate::messages::datatype::StringPadding::NullTerminate,
4076 },
4077 layout,
4078 fill_value,
4079 filters: None,
4080 external_files: None,
4081 attributes: Vec::new(),
4082 chunk_cache: context.chunk_cache.clone(),
4083 chunk_entry_cache: Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(32).unwrap()))),
4084 full_chunk_entries: Arc::new(OnceLock::new()),
4085 full_dataset_bytes: Arc::new(OnceLock::new()),
4086 external_slots: Arc::new(OnceLock::new()),
4087 filter_registry: context.filter_registry.clone(),
4088 }
4089 }
4090
4091 #[derive(Clone, Debug, PartialEq, Eq)]
4092 struct RawElement(Vec<u8>);
4093
4094 impl H5Type for RawElement {
4095 fn hdf5_type() -> Datatype {
4096 Datatype::Opaque {
4097 size: 0,
4098 tag: "raw".to_string(),
4099 }
4100 }
4101
4102 fn from_bytes(bytes: &[u8], _dtype: &Datatype) -> Result<Self> {
4103 Ok(Self(bytes.to_vec()))
4104 }
4105
4106 fn element_size(dtype: &Datatype) -> usize {
4107 crate::datatype_api::dtype_element_size(dtype).unwrap_or(0)
4108 }
4109 }
4110
4111 #[test]
4112 fn slice_info_all() {
4113 let s = SliceInfo::all(3);
4114 assert_eq!(s.selections.len(), 3);
4115 }
4116
4117 #[test]
4118 fn raw_element_size_uses_file_vlen_reference_width() {
4119 let dtype = Datatype::VarLen {
4120 base: Box::new(Datatype::FixedPoint {
4121 size: 1,
4122 signed: false,
4123 byte_order: crate::error::ByteOrder::LittleEndian,
4124 }),
4125 kind: VarLenKind::Sequence,
4126 encoding: crate::messages::datatype::StringEncoding::Ascii,
4127 padding: crate::messages::datatype::StringPadding::NullTerminate,
4128 };
4129
4130 assert_eq!(raw_element_size_for_datatype(&dtype, 12).unwrap(), 12);
4131 assert_eq!(
4132 raw_element_size_for_datatype(
4133 &Datatype::Array {
4134 base: Box::new(dtype),
4135 dims: vec![2, 3],
4136 },
4137 12,
4138 )
4139 .unwrap(),
4140 72
4141 );
4142 }
4143
4144 #[test]
4145 fn chunked_vlen_slice_uses_file_reference_width() {
4146 for (offset_size, ref_size) in [(2u8, 10usize), (4u8, 12usize)] {
4147 let chunk_bytes: Vec<u8> = (0..(2 * ref_size)).map(|i| (i + 1) as u8).collect();
4148 let dataset = vlen_string_dataset(
4149 DataLayout::Chunked {
4150 address: 0,
4151 dims: vec![2],
4152 element_size: 16,
4153 chunk_indexing: Some(ChunkIndexing::Implicit),
4154 },
4155 chunk_bytes.clone(),
4156 offset_size,
4157 None,
4158 );
4159
4160 let selection = SliceInfo {
4161 selections: vec![SliceInfoElem::Slice {
4162 start: 0,
4163 end: 2,
4164 step: 1,
4165 }],
4166 };
4167 let array = dataset.read_slice::<RawElement>(&selection).unwrap();
4168 let values: Vec<_> = array.iter().cloned().collect();
4169
4170 assert_eq!(values[0].0, chunk_bytes[..ref_size]);
4171 assert_eq!(values[1].0, chunk_bytes[ref_size..2 * ref_size]);
4172 }
4173 }
4174
4175 #[cfg(feature = "rayon")]
4176 #[test]
4177 fn parallel_chunked_vlen_slice_uses_file_reference_width() {
4178 for (offset_size, ref_size) in [(2u8, 10usize), (4u8, 12usize)] {
4179 let chunk_bytes: Vec<u8> = (0..(2 * ref_size)).map(|i| (i + 1) as u8).collect();
4180 let dataset = vlen_string_dataset(
4181 DataLayout::Chunked {
4182 address: 0,
4183 dims: vec![2],
4184 element_size: 16,
4185 chunk_indexing: Some(ChunkIndexing::Implicit),
4186 },
4187 chunk_bytes.clone(),
4188 offset_size,
4189 None,
4190 );
4191
4192 let selection = SliceInfo {
4193 selections: vec![SliceInfoElem::Slice {
4194 start: 0,
4195 end: 2,
4196 step: 1,
4197 }],
4198 };
4199 let array = dataset
4200 .read_slice_parallel::<RawElement>(&selection)
4201 .unwrap();
4202 let values: Vec<_> = array.iter().cloned().collect();
4203
4204 assert_eq!(values[0].0, chunk_bytes[..ref_size]);
4205 assert_eq!(values[1].0, chunk_bytes[ref_size..2 * ref_size]);
4206 }
4207 }
4208
4209 #[test]
4210 fn undefined_chunked_vlen_slice_fill_uses_file_reference_width() {
4211 for (offset_size, ref_size, undefined_address) in
4212 [(2u8, 10usize, 0xFFFF), (4u8, 12usize, 0xFFFF_FFFF)]
4213 {
4214 let fill_bytes = vec![0xA5; ref_size];
4215 let dataset = vlen_string_dataset(
4216 DataLayout::Chunked {
4217 address: undefined_address,
4218 dims: vec![2],
4219 element_size: 16,
4220 chunk_indexing: Some(ChunkIndexing::Implicit),
4221 },
4222 Vec::new(),
4223 offset_size,
4224 Some(FillValueMessage {
4225 defined: true,
4226 fill_time: FillTime::IfSet,
4227 value: Some(fill_bytes.clone()),
4228 }),
4229 );
4230
4231 let selection = SliceInfo {
4232 selections: vec![SliceInfoElem::Slice {
4233 start: 0,
4234 end: 2,
4235 step: 1,
4236 }],
4237 };
4238 let array = dataset.read_slice::<RawElement>(&selection).unwrap();
4239 let values: Vec<_> = array.iter().cloned().collect();
4240
4241 assert_eq!(values[0].0, fill_bytes);
4242 assert_eq!(values[1].0, fill_bytes);
4243 }
4244 }
4245
4246 #[test]
4247 fn array_raw_element_size_rejects_overflow() {
4248 let dtype = Datatype::Array {
4249 base: Box::new(Datatype::FixedPoint {
4250 size: 8,
4251 signed: false,
4252 byte_order: crate::error::ByteOrder::LittleEndian,
4253 }),
4254 dims: vec![u64::MAX, 2],
4255 };
4256
4257 let err = raw_element_size_for_datatype(&dtype, 12).unwrap_err();
4258 assert!(err.to_string().contains("array datatype"));
4259 }
4260
4261 #[test]
4262 fn dataset_num_elements_rejects_overflow() {
4263 let mut dataset = fixed_u16_dataset(DataLayout::Compact { data: vec![] }, Vec::new());
4264 dataset.dataspace.dims = vec![u64::MAX, 2];
4265
4266 let err = dataset.num_elements().unwrap_err();
4267 assert!(err.to_string().contains("element count"));
4268 }
4269
4270 #[test]
4271 fn compact_raw_data_requires_exact_logical_length() {
4272 let dataset = fixed_u16_dataset(
4273 DataLayout::Compact {
4274 data: vec![1, 0, 2, 0, 3],
4275 },
4276 Vec::new(),
4277 );
4278
4279 let err = dataset.read_array::<u16>().unwrap_err();
4280 assert!(
4281 matches!(err, Error::Context { .. })
4282 && err
4283 .to_string()
4284 .contains("compact raw data has 5 bytes, expected 6 bytes"),
4285 "expected compact raw length error, got: {err}"
4286 );
4287 }
4288
4289 #[test]
4290 fn contiguous_raw_data_requires_exact_logical_length() {
4291 let dataset = fixed_u16_dataset(
4292 DataLayout::Contiguous {
4293 address: 0,
4294 size: 5,
4295 },
4296 vec![1, 0, 2, 0, 3],
4297 );
4298
4299 let err = dataset.read_raw_bytes().unwrap_err();
4300 assert!(
4301 matches!(err, Error::Context { .. })
4302 && err
4303 .to_string()
4304 .contains("contiguous raw data has 5 bytes, expected 6 bytes"),
4305 "expected contiguous raw length error, got: {err}"
4306 );
4307 }
4308
4309 #[test]
4310 fn copy_chunk_1d() {
4311 let chunk_data = vec![1u8, 2, 3, 4]; let mut flat = vec![0u8; 8];
4313 let chunk_offsets = vec![2u64]; let chunk_shape = vec![4u64];
4315 let dataset_shape = vec![8u64];
4316
4317 copy_chunk_to_flat(
4318 &chunk_data,
4319 &mut flat,
4320 &chunk_offsets,
4321 &chunk_shape,
4322 &dataset_shape,
4323 1,
4324 )
4325 .unwrap();
4326 assert_eq!(flat, vec![0, 0, 1, 2, 3, 4, 0, 0]);
4327 }
4328
4329 #[test]
4330 fn copy_chunk_2d_rowwise() {
4331 let chunk_data = vec![1u8, 2, 3, 4, 5, 6];
4332 let mut flat = vec![0u8; 16];
4333 let chunk_offsets = vec![1u64, 1u64];
4334 let chunk_shape = vec![2u64, 3u64];
4335 let dataset_shape = vec![4u64, 4u64];
4336
4337 copy_chunk_to_flat(
4338 &chunk_data,
4339 &mut flat,
4340 &chunk_offsets,
4341 &chunk_shape,
4342 &dataset_shape,
4343 1,
4344 )
4345 .unwrap();
4346
4347 assert_eq!(flat, vec![0, 0, 0, 0, 0, 1, 2, 3, 0, 4, 5, 6, 0, 0, 0, 0,]);
4348 }
4349
4350 #[test]
4351 fn copy_unit_stride_chunk_overlap_2d_partial() {
4352 let chunk_data: Vec<u8> = (1..=16).collect();
4353 let mut result = vec![0u8; 6];
4354 let chunk_offsets = vec![0u64, 0u64];
4355 let chunk_shape = vec![4u64, 4u64];
4356 let dataset_shape = vec![4u64, 4u64];
4357 let resolved = ResolvedSelection {
4358 dims: vec![
4359 ResolvedSelectionDim {
4360 start: 1,
4361 end: 3,
4362 step: 1,
4363 count: 2,
4364 },
4365 ResolvedSelectionDim {
4366 start: 1,
4367 end: 4,
4368 step: 1,
4369 count: 3,
4370 },
4371 ],
4372 result_shape: vec![2, 3],
4373 result_elements: 6,
4374 };
4375 let chunk_strides = vec![4usize, 1usize];
4376 let result_strides = vec![3usize, 1usize];
4377
4378 copy_unit_stride_chunk_overlap(
4379 &chunk_data,
4380 &mut result,
4381 UnitStrideCopyLayout {
4382 chunk_offsets: &chunk_offsets,
4383 chunk_shape: &chunk_shape,
4384 dataset_shape: &dataset_shape,
4385 resolved: &resolved,
4386 chunk_strides: &chunk_strides,
4387 result_strides: &result_strides,
4388 elem_size: 1,
4389 },
4390 )
4391 .unwrap();
4392
4393 assert_eq!(result, vec![6, 7, 8, 10, 11, 12]);
4394 }
4395
4396 fn chunk_entry(offsets: &[u64], address: u64) -> chunk_index::ChunkEntry {
4397 chunk_index::ChunkEntry {
4398 address,
4399 size: 0,
4400 filter_mask: 0,
4401 offsets: offsets.to_vec(),
4402 }
4403 }
4404
4405 #[test]
4406 fn chunk_grid_coverage_detects_missing_chunk() {
4407 let mut entries = vec![
4408 chunk_entry(&[0, 0], 0x1000),
4409 chunk_entry(&[0, 2], 0x2000),
4410 chunk_entry(&[2, 0], 0x3000),
4411 ];
4412
4413 let complete =
4414 validate_chunk_grid_coverage(&mut entries, &[4, 4], &[2, 2], &[0, 0], &[1, 1]).unwrap();
4415
4416 assert!(!complete);
4417 }
4418
4419 #[test]
4420 fn chunk_grid_coverage_rejects_duplicate_offsets() {
4421 let mut entries = vec![
4422 chunk_entry(&[0, 0], 0x1000),
4423 chunk_entry(&[0, 0], 0x2000),
4424 chunk_entry(&[0, 2], 0x3000),
4425 chunk_entry(&[2, 0], 0x4000),
4426 ];
4427
4428 let err = validate_chunk_grid_coverage(&mut entries, &[4, 4], &[2, 2], &[0, 0], &[1, 1])
4429 .unwrap_err();
4430
4431 assert!(matches!(err, Error::InvalidData(_)));
4432 }
4433
4434 #[test]
4435 fn decoded_chunk_len_requires_exact_size() {
4436 let entry = chunk_entry(&[0, 0], 0x1000);
4437
4438 validate_decoded_chunk_len(&entry, &[2, 3], 4, 24).unwrap();
4439 let err = validate_decoded_chunk_len(&entry, &[2, 3], 4, 23).unwrap_err();
4440
4441 assert!(matches!(err, Error::InvalidData(_)));
4442 }
4443
4444 #[test]
4445 fn copy_chunk_errors_on_short_row() {
4446 let chunk_data = vec![1u8, 2, 3, 4, 5];
4447 let mut flat = vec![0u8; 16];
4448 let chunk_offsets = vec![1u64, 1u64];
4449 let chunk_shape = vec![2u64, 3u64];
4450 let dataset_shape = vec![4u64, 4u64];
4451
4452 let err = copy_chunk_to_flat(
4453 &chunk_data,
4454 &mut flat,
4455 &chunk_offsets,
4456 &chunk_shape,
4457 &dataset_shape,
4458 1,
4459 )
4460 .unwrap_err();
4461
4462 assert!(matches!(err, Error::InvalidData(_)));
4463 }
4464
4465 #[test]
4466 fn copy_unit_stride_chunk_overlap_errors_on_short_row() {
4467 let chunk_data: Vec<u8> = (1..=7).collect();
4468 let mut result = vec![0u8; 6];
4469 let chunk_offsets = vec![0u64, 0u64];
4470 let chunk_shape = vec![4u64, 4u64];
4471 let dataset_shape = vec![4u64, 4u64];
4472 let resolved = ResolvedSelection {
4473 dims: vec![
4474 ResolvedSelectionDim {
4475 start: 1,
4476 end: 3,
4477 step: 1,
4478 count: 2,
4479 },
4480 ResolvedSelectionDim {
4481 start: 1,
4482 end: 4,
4483 step: 1,
4484 count: 3,
4485 },
4486 ],
4487 result_shape: vec![2, 3],
4488 result_elements: 6,
4489 };
4490 let chunk_strides = vec![4usize, 1usize];
4491 let result_strides = vec![3usize, 1usize];
4492
4493 let err = copy_unit_stride_chunk_overlap(
4494 &chunk_data,
4495 &mut result,
4496 UnitStrideCopyLayout {
4497 chunk_offsets: &chunk_offsets,
4498 chunk_shape: &chunk_shape,
4499 dataset_shape: &dataset_shape,
4500 resolved: &resolved,
4501 chunk_strides: &chunk_strides,
4502 result_strides: &result_strides,
4503 elem_size: 1,
4504 },
4505 )
4506 .unwrap_err();
4507
4508 assert!(matches!(err, Error::InvalidData(_)));
4509 }
4510}