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