Skip to main content

rustyhdf5_format/
data_read.rs

1//! Raw data reading and typed conversion for HDF5 datasets.
2
3#[cfg(not(feature = "std"))]
4use alloc::{collections::BTreeMap, string::String, vec, vec::Vec};
5
6#[cfg(feature = "std")]
7use std::collections::BTreeMap;
8
9use crate::chunk_cache::ChunkCache;
10use crate::chunked_read::{read_chunked_data, read_chunked_data_cached};
11use crate::data_layout::DataLayout;
12use crate::dataspace::Dataspace;
13use crate::datatype::{Datatype, DatatypeByteOrder};
14use crate::error::FormatError;
15use crate::filter_pipeline::FilterPipeline;
16
17/// Zero-copy read of contiguous raw data, returning a borrowed slice.
18///
19/// For contiguous layouts, returns a direct `&[u8]` slice into `file_data`.
20/// For compact or chunked layouts, returns `Ok(None)` — the caller should
21/// fall back to `read_raw_data` for those.
22pub fn read_raw_data_zerocopy<'a>(
23    file_data: &'a [u8],
24    layout: &DataLayout,
25    dataspace: &Dataspace,
26    datatype: &Datatype,
27) -> Result<Option<&'a [u8]>, FormatError> {
28    let num_elements = dataspace.num_elements() as usize;
29    let elem_size = datatype.type_size() as usize;
30    let expected_size = num_elements * elem_size;
31
32    match layout {
33        DataLayout::Contiguous { address, size } => {
34            let addr = address.ok_or(FormatError::NoDataAllocated)?;
35            let addr = addr as usize;
36            let sz = *size as usize;
37            if sz != expected_size {
38                return Err(FormatError::DataSizeMismatch {
39                    expected: expected_size,
40                    actual: sz,
41                });
42            }
43            if addr + sz > file_data.len() {
44                return Err(FormatError::UnexpectedEof {
45                    expected: addr + sz,
46                    available: file_data.len(),
47                });
48            }
49            Ok(Some(&file_data[addr..addr + sz]))
50        }
51        _ => Ok(None),
52    }
53}
54
55/// Read raw bytes for a dataset given its layout and the file data buffer.
56///
57/// For compact layouts, returns the inline data.
58/// For contiguous layouts, reads from the address in the file buffer.
59/// For chunked layouts, traverses the B-tree and assembles chunks.
60pub fn read_raw_data(
61    file_data: &[u8],
62    layout: &DataLayout,
63    dataspace: &Dataspace,
64    datatype: &Datatype,
65) -> Result<Vec<u8>, FormatError> {
66    read_raw_data_full(file_data, layout, dataspace, datatype, None, 8, 8)
67}
68
69/// Read raw bytes with full parameters including filter pipeline and sizes.
70pub fn read_raw_data_full(
71    file_data: &[u8],
72    layout: &DataLayout,
73    dataspace: &Dataspace,
74    datatype: &Datatype,
75    pipeline: Option<&FilterPipeline>,
76    offset_size: u8,
77    length_size: u8,
78) -> Result<Vec<u8>, FormatError> {
79    let num_elements = dataspace.num_elements() as usize;
80    let elem_size = datatype.type_size() as usize;
81    let expected_size = num_elements * elem_size;
82
83    match layout {
84        DataLayout::Compact { data } => {
85            if data.len() != expected_size {
86                return Err(FormatError::DataSizeMismatch {
87                    expected: expected_size,
88                    actual: data.len(),
89                });
90            }
91            Ok(data.clone())
92        }
93        DataLayout::Contiguous { address, size } => {
94            let addr = address.ok_or(FormatError::NoDataAllocated)?;
95            let addr = addr as usize;
96            let sz = *size as usize;
97            if sz != expected_size {
98                return Err(FormatError::DataSizeMismatch {
99                    expected: expected_size,
100                    actual: sz,
101                });
102            }
103            if addr + sz > file_data.len() {
104                return Err(FormatError::UnexpectedEof {
105                    expected: addr + sz,
106                    available: file_data.len(),
107                });
108            }
109            Ok(file_data[addr..addr + sz].to_vec())
110        }
111        DataLayout::Chunked { .. } => {
112            read_chunked_data(file_data, layout, dataspace, datatype, pipeline, offset_size, length_size)
113        }
114        DataLayout::Virtual { .. } => Err(FormatError::UnsupportedVersion(0)),
115    }
116}
117
118/// Read raw bytes with chunk cache support.
119///
120/// For chunked layouts the `cache` is used to avoid repeated B-tree
121/// traversals and to cache decompressed chunk data.  For compact and
122/// contiguous layouts this behaves identically to [`read_raw_data_full`].
123pub fn read_raw_data_cached(
124    file_data: &[u8],
125    layout: &DataLayout,
126    dataspace: &Dataspace,
127    datatype: &Datatype,
128    pipeline: Option<&FilterPipeline>,
129    offset_size: u8,
130    length_size: u8,
131    cache: &ChunkCache,
132) -> Result<Vec<u8>, FormatError> {
133    match layout {
134        DataLayout::Chunked { .. } => {
135            read_chunked_data_cached(
136                file_data, layout, dataspace, datatype, pipeline,
137                offset_size, length_size, cache,
138            )
139        }
140        _ => read_raw_data_full(
141            file_data, layout, dataspace, datatype, pipeline,
142            offset_size, length_size,
143        ),
144    }
145}
146
147fn datatype_name(dt: &Datatype) -> &'static str {
148    match dt {
149        Datatype::FixedPoint { .. } => "FixedPoint",
150        Datatype::FloatingPoint { .. } => "FloatingPoint",
151        Datatype::String { .. } => "String",
152        Datatype::Time { .. } => "Time",
153        Datatype::BitField { .. } => "BitField",
154        Datatype::Opaque { .. } => "Opaque",
155        Datatype::Compound { .. } => "Compound",
156        Datatype::Reference { .. } => "Reference",
157        Datatype::Enumeration { .. } => "Enumeration",
158        Datatype::VariableLength { .. } => "VariableLength",
159        Datatype::Array { .. } => "Array",
160    }
161}
162
163fn ensure_numeric(dt: &Datatype, expected: &'static str) -> Result<(), FormatError> {
164    match dt {
165        Datatype::FixedPoint { .. } | Datatype::FloatingPoint { .. } => Ok(()),
166        _ => Err(FormatError::TypeMismatch {
167            expected,
168            actual: datatype_name(dt),
169        }),
170    }
171}
172
173fn get_byte_order(dt: &Datatype) -> DatatypeByteOrder {
174    match dt {
175        Datatype::FixedPoint { byte_order, .. } => byte_order.clone(),
176        Datatype::FloatingPoint { byte_order, .. } => byte_order.clone(),
177        _ => DatatypeByteOrder::LittleEndian,
178    }
179}
180
181fn get_size(dt: &Datatype) -> usize {
182    dt.type_size() as usize
183}
184
185/// Convert raw bytes to `f64` values.
186pub fn read_as_f64(raw: &[u8], datatype: &Datatype) -> Result<Vec<f64>, FormatError> {
187    ensure_numeric(datatype, "FloatingPoint or FixedPoint")?;
188    let elem_size = get_size(datatype);
189    if elem_size == 0 || !raw.len().is_multiple_of(elem_size) {
190        return Err(FormatError::DataSizeMismatch {
191            expected: 0,
192            actual: raw.len(),
193        });
194    }
195    let count = raw.len() / elem_size;
196
197    // Fast path: native-endian f64 can use bulk copy (zero conversion overhead)
198    #[cfg(target_endian = "little")]
199    if matches!(datatype, Datatype::FloatingPoint { size: 8, byte_order: DatatypeByteOrder::LittleEndian, .. })
200    {
201        let mut result = vec![0.0f64; count];
202        // Safety equivalent via from_le_bytes — but we use safe transmute-free copy
203        for (i, val) in result.iter_mut().enumerate() {
204            let off = i * 8;
205            *val = f64::from_le_bytes([
206                raw[off], raw[off+1], raw[off+2], raw[off+3],
207                raw[off+4], raw[off+5], raw[off+6], raw[off+7],
208            ]);
209        }
210        return Ok(result);
211    }
212
213    let order = get_byte_order(datatype);
214    let mut result = Vec::with_capacity(count);
215
216    for i in 0..count {
217        let chunk = &raw[i * elem_size..(i + 1) * elem_size];
218        let val = convert_to_f64(chunk, datatype, &order)?;
219        result.push(val);
220    }
221    Ok(result)
222}
223
224fn convert_to_f64(
225    bytes: &[u8],
226    dt: &Datatype,
227    order: &DatatypeByteOrder,
228) -> Result<f64, FormatError> {
229    match dt {
230        Datatype::FloatingPoint { size, .. } => match size {
231            4 => {
232                let v = read_f32_bytes(bytes, order);
233                Ok(v as f64)
234            }
235            8 => Ok(read_f64_bytes(bytes, order)),
236            _ => Err(FormatError::DataSizeMismatch {
237                expected: 8,
238                actual: *size as usize,
239            }),
240        },
241        Datatype::FixedPoint { size, signed, .. } => {
242            if *signed {
243                let v = read_signed_int(bytes, *size as usize, order);
244                Ok(v as f64)
245            } else {
246                let v = read_unsigned_int(bytes, *size as usize, order);
247                Ok(v as f64)
248            }
249        }
250        _ => Err(FormatError::TypeMismatch {
251            expected: "numeric",
252            actual: datatype_name(dt),
253        }),
254    }
255}
256
257/// Convert raw bytes to `i64` values.
258pub fn read_as_i64(raw: &[u8], datatype: &Datatype) -> Result<Vec<i64>, FormatError> {
259    ensure_numeric(datatype, "FixedPoint (signed)")?;
260    let elem_size = get_size(datatype);
261    if elem_size == 0 || !raw.len().is_multiple_of(elem_size) {
262        return Err(FormatError::DataSizeMismatch {
263            expected: 0,
264            actual: raw.len(),
265        });
266    }
267    let count = raw.len() / elem_size;
268    let order = get_byte_order(datatype);
269    let mut result = Vec::with_capacity(count);
270    for i in 0..count {
271        let chunk = &raw[i * elem_size..(i + 1) * elem_size];
272        let v = read_signed_int(chunk, elem_size, &order);
273        result.push(v);
274    }
275    Ok(result)
276}
277
278/// Convert raw bytes to `u64` values.
279pub fn read_as_u64(raw: &[u8], datatype: &Datatype) -> Result<Vec<u64>, FormatError> {
280    ensure_numeric(datatype, "FixedPoint (unsigned)")?;
281    let elem_size = get_size(datatype);
282    if elem_size == 0 || !raw.len().is_multiple_of(elem_size) {
283        return Err(FormatError::DataSizeMismatch {
284            expected: 0,
285            actual: raw.len(),
286        });
287    }
288    let count = raw.len() / elem_size;
289    let order = get_byte_order(datatype);
290    let mut result = Vec::with_capacity(count);
291    for i in 0..count {
292        let chunk = &raw[i * elem_size..(i + 1) * elem_size];
293        let v = read_unsigned_int(chunk, elem_size, &order);
294        result.push(v);
295    }
296    Ok(result)
297}
298
299/// Convert raw bytes to `f32` values.
300pub fn read_as_f32(raw: &[u8], datatype: &Datatype) -> Result<Vec<f32>, FormatError> {
301    ensure_numeric(datatype, "FloatingPoint")?;
302    let elem_size = get_size(datatype);
303    if elem_size == 0 || !raw.len().is_multiple_of(elem_size) {
304        return Err(FormatError::DataSizeMismatch {
305            expected: 0,
306            actual: raw.len(),
307        });
308    }
309    let count = raw.len() / elem_size;
310    let order = get_byte_order(datatype);
311    let mut result = Vec::with_capacity(count);
312    for i in 0..count {
313        let chunk = &raw[i * elem_size..(i + 1) * elem_size];
314        match datatype {
315            Datatype::FloatingPoint { size: 4, .. } => {
316                result.push(read_f32_bytes(chunk, &order));
317            }
318            Datatype::FloatingPoint { size: 8, .. } => {
319                result.push(read_f64_bytes(chunk, &order) as f32);
320            }
321            Datatype::FixedPoint { signed: true, size, .. } => {
322                result.push(read_signed_int(chunk, *size as usize, &order) as f32);
323            }
324            Datatype::FixedPoint { signed: false, size, .. } => {
325                result.push(read_unsigned_int(chunk, *size as usize, &order) as f32);
326            }
327            _ => {
328                return Err(FormatError::TypeMismatch {
329                    expected: "numeric",
330                    actual: datatype_name(datatype),
331                });
332            }
333        }
334    }
335    Ok(result)
336}
337
338/// Convert raw bytes to `i32` values.
339pub fn read_as_i32(raw: &[u8], datatype: &Datatype) -> Result<Vec<i32>, FormatError> {
340    ensure_numeric(datatype, "FixedPoint")?;
341    let elem_size = get_size(datatype);
342    if elem_size == 0 || !raw.len().is_multiple_of(elem_size) {
343        return Err(FormatError::DataSizeMismatch {
344            expected: 0,
345            actual: raw.len(),
346        });
347    }
348    let count = raw.len() / elem_size;
349    let order = get_byte_order(datatype);
350    let mut result = Vec::with_capacity(count);
351    for i in 0..count {
352        let chunk = &raw[i * elem_size..(i + 1) * elem_size];
353        let v = read_signed_int(chunk, elem_size, &order);
354        result.push(v as i32);
355    }
356    Ok(result)
357}
358
359/// Read fixed-length strings from raw bytes.
360pub fn read_as_strings(raw: &[u8], datatype: &Datatype) -> Result<Vec<String>, FormatError> {
361    match datatype {
362        Datatype::String { size, padding, .. } => {
363            let elem_size = *size as usize;
364            if elem_size == 0 {
365                return Ok(Vec::new());
366            }
367            if !raw.len().is_multiple_of(elem_size) {
368                return Err(FormatError::DataSizeMismatch {
369                    expected: 0,
370                    actual: raw.len(),
371                });
372            }
373            let count = raw.len() / elem_size;
374            let mut result = Vec::with_capacity(count);
375            for i in 0..count {
376                let chunk = &raw[i * elem_size..(i + 1) * elem_size];
377                let s = match padding {
378                    crate::datatype::StringPadding::NullTerminate => {
379                        let end = chunk.iter().position(|&b| b == 0).unwrap_or(chunk.len());
380                        String::from_utf8_lossy(&chunk[..end]).into_owned()
381                    }
382                    crate::datatype::StringPadding::NullPad => {
383                        let end = chunk.iter().rposition(|&b| b != 0).map_or(0, |p| p + 1);
384                        String::from_utf8_lossy(&chunk[..end]).into_owned()
385                    }
386                    crate::datatype::StringPadding::SpacePad => {
387                        let end = chunk
388                            .iter()
389                            .rposition(|&b| b != b' ')
390                            .map_or(0, |p| p + 1);
391                        String::from_utf8_lossy(&chunk[..end]).into_owned()
392                    }
393                };
394                result.push(s);
395            }
396            Ok(result)
397        }
398        _ => Err(FormatError::TypeMismatch {
399            expected: "String",
400            actual: datatype_name(datatype),
401        }),
402    }
403}
404
405// --- Compound type reading ---
406
407/// A single field extracted from compound data, containing the raw bytes for that field
408/// across all elements.
409#[derive(Debug, Clone)]
410pub struct CompoundFieldData {
411    /// Field name.
412    pub name: String,
413    /// Datatype of this field.
414    pub datatype: Datatype,
415    /// Raw bytes for this field across all elements (len = num_elements * field_type_size).
416    pub raw_data: Vec<u8>,
417}
418
419/// Read compound dataset and return all fields as separate data vectors.
420///
421/// Each returned `CompoundFieldData` contains the raw bytes for that field
422/// across all elements, suitable for further typed conversion with `read_as_f64`, etc.
423pub fn read_compound_fields(raw: &[u8], datatype: &Datatype) -> Result<Vec<CompoundFieldData>, FormatError> {
424    match datatype {
425        Datatype::Compound { size, members } => {
426            let elem_size = *size as usize;
427            if elem_size == 0 {
428                return Ok(Vec::new());
429            }
430            if !raw.len().is_multiple_of(elem_size) {
431                return Err(FormatError::DataSizeMismatch {
432                    expected: 0,
433                    actual: raw.len(),
434                });
435            }
436            let count = raw.len() / elem_size;
437            let mut fields = Vec::with_capacity(members.len());
438            for m in members {
439                let field_size = m.datatype.type_size() as usize;
440                let offset = m.byte_offset as usize;
441                let mut field_raw = Vec::with_capacity(count * field_size);
442                for i in 0..count {
443                    let elem_start = i * elem_size + offset;
444                    field_raw.extend_from_slice(&raw[elem_start..elem_start + field_size]);
445                }
446                fields.push(CompoundFieldData {
447                    name: m.name.clone(),
448                    datatype: m.datatype.clone(),
449                    raw_data: field_raw,
450                });
451            }
452            Ok(fields)
453        }
454        _ => Err(FormatError::TypeMismatch {
455            expected: "Compound",
456            actual: datatype_name(datatype),
457        }),
458    }
459}
460
461/// Extract a single field by name from compound raw data.
462pub fn read_compound_field(raw: &[u8], datatype: &Datatype, field_name: &str) -> Result<CompoundFieldData, FormatError> {
463    let fields = read_compound_fields(raw, datatype)?;
464    fields.into_iter()
465        .find(|f| f.name == field_name)
466        .ok_or_else(|| FormatError::PathNotFound(field_name.into()))
467}
468
469// --- Enum type reading ---
470
471/// A single value from an enum dataset, containing both the integer value and string name.
472#[derive(Debug, Clone)]
473pub struct EnumValue {
474    /// The string name for this enum value.
475    pub name: String,
476    /// The raw integer value.
477    pub raw_value: Vec<u8>,
478}
479
480/// Read enum dataset values, mapping integer values to their string names.
481///
482/// Returns one `EnumValue` per element. Unknown values get name `UNKNOWN(hex)`.
483pub fn read_enum_values(raw: &[u8], datatype: &Datatype) -> Result<Vec<EnumValue>, FormatError> {
484    match datatype {
485        Datatype::Enumeration { size, members, .. } => {
486            let elem_size = *size as usize;
487            if elem_size == 0 {
488                return Ok(Vec::new());
489            }
490            if !raw.len().is_multiple_of(elem_size) {
491                return Err(FormatError::DataSizeMismatch {
492                    expected: 0,
493                    actual: raw.len(),
494                });
495            }
496            let count = raw.len() / elem_size;
497            // Build lookup map: raw bytes -> name
498            let mut lookup = BTreeMap::new();
499            for m in members {
500                lookup.insert(m.value.clone(), m.name.clone());
501            }
502            let mut result = Vec::with_capacity(count);
503            for i in 0..count {
504                let val_bytes = raw[i * elem_size..(i + 1) * elem_size].to_vec();
505                let name = lookup.get(&val_bytes).cloned().unwrap_or_else(|| {
506                    let hex: Vec<String> = val_bytes.iter().map(|b| {
507                        let mut s = String::new();
508                        core::fmt::Write::write_fmt(&mut s, format_args!("{b:02x}")).ok();
509                        s
510                    }).collect();
511                    let mut result = String::from("UNKNOWN(0x");
512                    for h in &hex { result.push_str(h); }
513                    result.push(')');
514                    result
515                });
516                result.push(EnumValue { name, raw_value: val_bytes });
517            }
518            Ok(result)
519        }
520        _ => Err(FormatError::TypeMismatch {
521            expected: "Enumeration",
522            actual: datatype_name(datatype),
523        }),
524    }
525}
526
527/// Read enum dataset and return just the string names.
528pub fn read_enum_names(raw: &[u8], datatype: &Datatype) -> Result<Vec<String>, FormatError> {
529    let values = read_enum_values(raw, datatype)?;
530    Ok(values.into_iter().map(|v| v.name).collect())
531}
532
533// --- Reference type reading ---
534
535/// A resolved object reference: the file address of the referenced object header.
536#[derive(Debug, Clone, PartialEq, Eq)]
537pub struct ObjectReference {
538    /// The file address of the referenced object header.
539    /// A value of `u64::MAX` (all 0xFF bytes) indicates a null reference.
540    pub address: u64,
541}
542
543impl ObjectReference {
544    /// Returns `true` if this is a null (unset) reference.
545    pub fn is_null(&self) -> bool {
546        self.address == u64::MAX
547    }
548}
549
550/// A region reference: raw bytes that encode a dataset selection.
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct RegionReference {
553    /// The raw region reference bytes. A region reference is typically 12 bytes
554    /// (object address + dataspace selection) but the exact layout depends on
555    /// the file's offset size and the selection type.
556    pub raw: Vec<u8>,
557}
558
559/// Read object references from raw bytes.
560///
561/// Object references are stored as `offset_size`-byte file addresses pointing
562/// to the object header of the referenced object. A reference consisting of
563/// all `0xFF` bytes is a null (unset) reference.
564///
565/// # Arguments
566/// * `raw` — raw bytes read from the dataset
567/// * `datatype` — must be `Datatype::Reference` with `ReferenceType::Object`
568/// * `offset_size` — the file's offset size (from superblock), typically 8
569pub fn read_object_references(
570    raw: &[u8],
571    datatype: &Datatype,
572    offset_size: u8,
573) -> Result<Vec<ObjectReference>, FormatError> {
574    match datatype {
575        Datatype::Reference {
576            ref_type: crate::datatype::ReferenceType::Object,
577            size,
578        } => {
579            let elem_size = *size as usize;
580            if elem_size == 0 {
581                return Ok(Vec::new());
582            }
583            if !raw.len().is_multiple_of(elem_size) {
584                return Err(FormatError::DataSizeMismatch {
585                    expected: 0,
586                    actual: raw.len(),
587                });
588            }
589            let count = raw.len() / elem_size;
590            let mut result = Vec::with_capacity(count);
591            let read_size = (offset_size as usize).min(elem_size);
592            for i in 0..count {
593                let chunk = &raw[i * elem_size..(i + 1) * elem_size];
594                let address = read_ref_address(chunk, read_size);
595                result.push(ObjectReference { address });
596            }
597            Ok(result)
598        }
599        _ => Err(FormatError::TypeMismatch {
600            expected: "Reference(Object)",
601            actual: datatype_name(datatype),
602        }),
603    }
604}
605
606/// Read region references from raw bytes.
607///
608/// Region references encode a dataset selection (hyperslab, point list, etc.)
609/// along with the address of the target dataset. This function returns the
610/// raw bytes for each reference without decoding the selection, since the
611/// full region reference format is complex and depends on the selection type.
612///
613/// # Arguments
614/// * `raw` — raw bytes read from the dataset
615/// * `datatype` — must be `Datatype::Reference` with `ReferenceType::DatasetRegion`
616pub fn read_region_references(
617    raw: &[u8],
618    datatype: &Datatype,
619) -> Result<Vec<RegionReference>, FormatError> {
620    match datatype {
621        Datatype::Reference {
622            ref_type: crate::datatype::ReferenceType::DatasetRegion,
623            size,
624        } => {
625            let elem_size = *size as usize;
626            if elem_size == 0 {
627                return Ok(Vec::new());
628            }
629            if !raw.len().is_multiple_of(elem_size) {
630                return Err(FormatError::DataSizeMismatch {
631                    expected: 0,
632                    actual: raw.len(),
633                });
634            }
635            let count = raw.len() / elem_size;
636            let mut result = Vec::with_capacity(count);
637            for i in 0..count {
638                let chunk = &raw[i * elem_size..(i + 1) * elem_size];
639                result.push(RegionReference {
640                    raw: chunk.to_vec(),
641                });
642            }
643            Ok(result)
644        }
645        _ => Err(FormatError::TypeMismatch {
646            expected: "Reference(DatasetRegion)",
647            actual: datatype_name(datatype),
648        }),
649    }
650}
651
652/// Read a file address from reference bytes (little-endian).
653fn read_ref_address(bytes: &[u8], size: usize) -> u64 {
654    let mut buf = [0xFFu8; 8];
655    let len = size.min(bytes.len()).min(8);
656    buf[..len].copy_from_slice(&bytes[..len]);
657    // If we read fewer than 8 bytes, check if ALL read bytes are 0xFF (null ref)
658    if len < 8 && bytes[..len].iter().all(|&b| b == 0xFF) {
659        return u64::MAX;
660    }
661    // Zero-extend upper bytes for non-null refs
662    if len < 8 && !bytes[..len].iter().all(|&b| b == 0xFF) {
663        for b in buf[len..].iter_mut() {
664            *b = 0;
665        }
666    }
667    u64::from_le_bytes(buf)
668}
669
670// --- Array type reading ---
671
672/// Read array-typed dataset elements, returning the raw base-type data.
673///
674/// For an array type with dimensions [D1, D2, ...] and base type T,
675/// each dataset element contains D1*D2*... values of type T.
676/// This function returns the raw bytes as a flat buffer that can be
677/// converted with `read_as_f64`, `read_as_i32`, etc. using the base type.
678pub fn read_array_flat(raw: &[u8], datatype: &Datatype) -> Result<(Vec<u8>, Datatype, Vec<u32>), FormatError> {
679    match datatype {
680        Datatype::Array { base_type, dimensions } => {
681            Ok((raw.to_vec(), *base_type.clone(), dimensions.clone()))
682        }
683        _ => Err(FormatError::TypeMismatch {
684            expected: "Array",
685            actual: datatype_name(datatype),
686        }),
687    }
688}
689
690// --- Low-level byte conversion helpers ---
691
692fn reorder_bytes(bytes: &[u8], order: &DatatypeByteOrder) -> [u8; 8] {
693    let mut buf = [0u8; 8];
694    let len = bytes.len().min(8);
695    match order {
696        DatatypeByteOrder::LittleEndian | DatatypeByteOrder::Vax => {
697            buf[..len].copy_from_slice(&bytes[..len]);
698        }
699        DatatypeByteOrder::BigEndian => {
700            // Reverse bytes into LE order
701            for i in 0..len {
702                buf[i] = bytes[len - 1 - i];
703            }
704        }
705    }
706    buf
707}
708
709fn read_f64_bytes(bytes: &[u8], order: &DatatypeByteOrder) -> f64 {
710    let buf = reorder_bytes(bytes, order);
711    f64::from_le_bytes(buf)
712}
713
714fn read_f32_bytes(bytes: &[u8], order: &DatatypeByteOrder) -> f32 {
715    let mut buf = [0u8; 4];
716    let len = bytes.len().min(4);
717    match order {
718        DatatypeByteOrder::LittleEndian | DatatypeByteOrder::Vax => {
719            buf[..len].copy_from_slice(&bytes[..len]);
720        }
721        DatatypeByteOrder::BigEndian => {
722            for i in 0..len {
723                buf[i] = bytes[len - 1 - i];
724            }
725        }
726    }
727    f32::from_le_bytes(buf)
728}
729
730fn read_unsigned_int(bytes: &[u8], size: usize, order: &DatatypeByteOrder) -> u64 {
731    let buf = reorder_bytes(bytes, order);
732    match size {
733        1 => buf[0] as u64,
734        2 => u16::from_le_bytes([buf[0], buf[1]]) as u64,
735        4 => u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as u64,
736        8 => u64::from_le_bytes(buf),
737        _ => {
738            // Generic: read as LE
739            let mut val = 0u64;
740            for (i, &byte) in buf.iter().enumerate().take(size.min(8)) {
741                val |= (byte as u64) << (i * 8);
742            }
743            val
744        }
745    }
746}
747
748fn read_signed_int(bytes: &[u8], size: usize, order: &DatatypeByteOrder) -> i64 {
749    let buf = reorder_bytes(bytes, order);
750    match size {
751        1 => buf[0] as i8 as i64,
752        2 => i16::from_le_bytes([buf[0], buf[1]]) as i64,
753        4 => i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as i64,
754        8 => i64::from_le_bytes(buf),
755        _ => {
756            let u = read_unsigned_int(bytes, size, order);
757            // Sign extend
758            let shift = 64 - (size * 8);
759            ((u as i64) << shift) >> shift
760        }
761    }
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767    use crate::dataspace::{Dataspace, DataspaceType};
768    use crate::datatype::{CharacterSet, StringPadding};
769
770    fn make_f64_le_type() -> Datatype {
771        Datatype::FloatingPoint {
772            size: 8,
773            byte_order: DatatypeByteOrder::LittleEndian,
774            bit_offset: 0,
775            bit_precision: 64,
776            exponent_location: 52,
777            exponent_size: 11,
778            mantissa_location: 0,
779            mantissa_size: 52,
780            exponent_bias: 1023,
781        }
782    }
783
784    fn make_f32_be_type() -> Datatype {
785        Datatype::FloatingPoint {
786            size: 4,
787            byte_order: DatatypeByteOrder::BigEndian,
788            bit_offset: 0,
789            bit_precision: 32,
790            exponent_location: 23,
791            exponent_size: 8,
792            mantissa_location: 0,
793            mantissa_size: 23,
794            exponent_bias: 127,
795        }
796    }
797
798    fn make_i32_le_type() -> Datatype {
799        Datatype::FixedPoint {
800            size: 4,
801            byte_order: DatatypeByteOrder::LittleEndian,
802            signed: true,
803            bit_offset: 0,
804            bit_precision: 32,
805        }
806    }
807
808    fn make_i16_le_type() -> Datatype {
809        Datatype::FixedPoint {
810            size: 2,
811            byte_order: DatatypeByteOrder::LittleEndian,
812            signed: true,
813            bit_offset: 0,
814            bit_precision: 16,
815        }
816    }
817
818    fn make_u8_type() -> Datatype {
819        Datatype::FixedPoint {
820            size: 1,
821            byte_order: DatatypeByteOrder::LittleEndian,
822            signed: false,
823            bit_offset: 0,
824            bit_precision: 8,
825        }
826    }
827
828    fn make_simple_dataspace(dims: &[u64]) -> Dataspace {
829        Dataspace {
830            space_type: DataspaceType::Simple,
831            rank: dims.len() as u8,
832            dimensions: dims.to_vec(),
833            max_dimensions: None,
834        }
835    }
836
837    #[test]
838    fn read_f64_compact() {
839        let dt = make_f64_le_type();
840        let ds = make_simple_dataspace(&[3]);
841        let mut data = Vec::new();
842        data.extend_from_slice(&1.0f64.to_le_bytes());
843        data.extend_from_slice(&2.0f64.to_le_bytes());
844        data.extend_from_slice(&3.0f64.to_le_bytes());
845        let layout = DataLayout::Compact { data: data.clone() };
846        let raw = read_raw_data(&[], &layout, &ds, &dt).unwrap();
847        assert_eq!(raw, data);
848        let values = read_as_f64(&raw, &dt).unwrap();
849        assert_eq!(values, vec![1.0, 2.0, 3.0]);
850    }
851
852    #[test]
853    fn read_i32_contiguous() {
854        let dt = make_i32_le_type();
855        let ds = make_simple_dataspace(&[4]);
856        let mut file_data = vec![0u8; 1024];
857        let offset = 256usize;
858        let vals: Vec<i32> = vec![10, -20, 30, -40];
859        for (i, v) in vals.iter().enumerate() {
860            let bytes = v.to_le_bytes();
861            file_data[offset + i * 4..offset + i * 4 + 4].copy_from_slice(&bytes);
862        }
863        let layout = DataLayout::Contiguous {
864            address: Some(offset as u64),
865            size: 16,
866        };
867        let raw = read_raw_data(&file_data, &layout, &ds, &dt).unwrap();
868        let result = read_as_i32(&raw, &dt).unwrap();
869        assert_eq!(result, vec![10, -20, 30, -40]);
870    }
871
872    #[test]
873    fn read_u8_data() {
874        let dt = make_u8_type();
875        let ds = make_simple_dataspace(&[5]);
876        let data = vec![10u8, 20, 30, 40, 50];
877        let layout = DataLayout::Compact { data: data.clone() };
878        let raw = read_raw_data(&[], &layout, &ds, &dt).unwrap();
879        let result = read_as_u64(&raw, &dt).unwrap();
880        assert_eq!(result, vec![10, 20, 30, 40, 50]);
881    }
882
883    #[test]
884    fn read_f32_be() {
885        let dt = make_f32_be_type();
886        let ds = make_simple_dataspace(&[2]);
887        let mut data = Vec::new();
888        // Store as big-endian
889        data.extend_from_slice(&1.5f32.to_be_bytes());
890        data.extend_from_slice(&2.5f32.to_be_bytes());
891        let layout = DataLayout::Compact { data: data.clone() };
892        let raw = read_raw_data(&[], &layout, &ds, &dt).unwrap();
893        let result = read_as_f32(&raw, &dt).unwrap();
894        assert_eq!(result, vec![1.5, 2.5]);
895    }
896
897    #[test]
898    fn read_i16_le() {
899        let dt = make_i16_le_type();
900        let ds = make_simple_dataspace(&[3]);
901        let mut data = Vec::new();
902        data.extend_from_slice(&(-100i16).to_le_bytes());
903        data.extend_from_slice(&200i16.to_le_bytes());
904        data.extend_from_slice(&(-300i16).to_le_bytes());
905        let layout = DataLayout::Compact { data: data.clone() };
906        let raw = read_raw_data(&[], &layout, &ds, &dt).unwrap();
907        let result = read_as_i64(&raw, &dt).unwrap();
908        assert_eq!(result, vec![-100, 200, -300]);
909    }
910
911    #[test]
912    fn read_strings_compact() {
913        let dt = Datatype::String {
914            size: 5,
915            padding: StringPadding::NullPad,
916            charset: CharacterSet::Ascii,
917        };
918        let ds = make_simple_dataspace(&[2]);
919        let mut data = Vec::new();
920        data.extend_from_slice(b"hello");
921        data.extend_from_slice(b"hi\0\0\0");
922        let layout = DataLayout::Compact { data: data.clone() };
923        let raw = read_raw_data(&[], &layout, &ds, &dt).unwrap();
924        let result = read_as_strings(&raw, &dt).unwrap();
925        assert_eq!(result, vec!["hello", "hi"]);
926    }
927
928    #[test]
929    fn type_mismatch_f64_on_string() {
930        let dt = Datatype::String {
931            size: 4,
932            padding: StringPadding::NullTerminate,
933            charset: CharacterSet::Ascii,
934        };
935        let raw = vec![0u8; 8];
936        let err = read_as_f64(&raw, &dt).unwrap_err();
937        assert!(matches!(err, FormatError::TypeMismatch { .. }));
938    }
939
940    #[test]
941    fn size_mismatch_compact() {
942        let dt = make_f64_le_type();
943        let ds = make_simple_dataspace(&[3]);
944        let data = vec![0u8; 16]; // wrong: should be 24
945        let layout = DataLayout::Compact { data };
946        let err = read_raw_data(&[], &layout, &ds, &dt).unwrap_err();
947        assert!(matches!(err, FormatError::DataSizeMismatch { .. }));
948    }
949
950    #[test]
951    fn no_data_allocated() {
952        let dt = make_f64_le_type();
953        let ds = make_simple_dataspace(&[3]);
954        let layout = DataLayout::Contiguous {
955            address: None,
956            size: 24,
957        };
958        let err = read_raw_data(&[], &layout, &ds, &dt).unwrap_err();
959        assert!(matches!(err, FormatError::NoDataAllocated));
960    }
961
962    #[test]
963    fn string_type_mismatch_on_read_as_strings() {
964        let dt = make_i32_le_type();
965        let raw = vec![0u8; 8];
966        let err = read_as_strings(&raw, &dt).unwrap_err();
967        assert!(matches!(err, FormatError::TypeMismatch { .. }));
968    }
969
970    #[test]
971    fn read_f64_from_i32() {
972        // read_as_f64 should work on FixedPoint types too
973        let dt = make_i32_le_type();
974        let mut raw = Vec::new();
975        raw.extend_from_slice(&42i32.to_le_bytes());
976        raw.extend_from_slice(&(-7i32).to_le_bytes());
977        let result = read_as_f64(&raw, &dt).unwrap();
978        assert_eq!(result, vec![42.0, -7.0]);
979    }
980
981    #[test]
982    fn read_strings_space_padded() {
983        let dt = Datatype::String {
984            size: 8,
985            padding: StringPadding::SpacePad,
986            charset: CharacterSet::Ascii,
987        };
988        let raw = b"hello   world   ";
989        let result = read_as_strings(raw, &dt).unwrap();
990        assert_eq!(result, vec!["hello", "world"]);
991    }
992
993    #[test]
994    fn read_strings_null_terminated() {
995        let dt = Datatype::String {
996            size: 6,
997            padding: StringPadding::NullTerminate,
998            charset: CharacterSet::Ascii,
999        };
1000        let raw = b"abc\0\0\0de\0\0\0\0";
1001        let result = read_as_strings(raw, &dt).unwrap();
1002        assert_eq!(result, vec!["abc", "de"]);
1003    }
1004
1005    #[test]
1006    fn read_compound_two_fields() {
1007        use crate::datatype::CompoundMember;
1008        // Compound: { x: f64, id: i32 } => size = 12
1009        let dt = Datatype::Compound {
1010            size: 12,
1011            members: vec![
1012                CompoundMember {
1013                    name: "x".to_string(),
1014                    byte_offset: 0,
1015                    datatype: make_f64_le_type(),
1016                },
1017                CompoundMember {
1018                    name: "id".to_string(),
1019                    byte_offset: 8,
1020                    datatype: make_i32_le_type(),
1021                },
1022            ],
1023        };
1024        // Two elements
1025        let mut raw = Vec::new();
1026        raw.extend_from_slice(&1.5f64.to_le_bytes());
1027        raw.extend_from_slice(&10i32.to_le_bytes());
1028        raw.extend_from_slice(&2.5f64.to_le_bytes());
1029        raw.extend_from_slice(&20i32.to_le_bytes());
1030
1031        let fields = read_compound_fields(&raw, &dt).unwrap();
1032        assert_eq!(fields.len(), 2);
1033        assert_eq!(fields[0].name, "x");
1034        let x_vals = read_as_f64(&fields[0].raw_data, &fields[0].datatype).unwrap();
1035        assert_eq!(x_vals, vec![1.5, 2.5]);
1036
1037        assert_eq!(fields[1].name, "id");
1038        let id_vals = read_as_i32(&fields[1].raw_data, &fields[1].datatype).unwrap();
1039        assert_eq!(id_vals, vec![10, 20]);
1040    }
1041
1042    #[test]
1043    fn read_compound_single_field_by_name() {
1044        use crate::datatype::CompoundMember;
1045        let dt = Datatype::Compound {
1046            size: 12,
1047            members: vec![
1048                CompoundMember {
1049                    name: "x".to_string(),
1050                    byte_offset: 0,
1051                    datatype: make_f64_le_type(),
1052                },
1053                CompoundMember {
1054                    name: "id".to_string(),
1055                    byte_offset: 8,
1056                    datatype: make_i32_le_type(),
1057                },
1058            ],
1059        };
1060        let mut raw = Vec::new();
1061        raw.extend_from_slice(&3.14f64.to_le_bytes());
1062        raw.extend_from_slice(&42i32.to_le_bytes());
1063
1064        let field = read_compound_field(&raw, &dt, "id").unwrap();
1065        let vals = read_as_i32(&field.raw_data, &field.datatype).unwrap();
1066        assert_eq!(vals, vec![42]);
1067
1068        // Non-existent field
1069        let err = read_compound_field(&raw, &dt, "missing").unwrap_err();
1070        assert!(matches!(err, FormatError::PathNotFound(_)));
1071    }
1072
1073    #[test]
1074    fn read_enum_values_basic() {
1075        use crate::datatype::EnumMember;
1076        let dt = Datatype::Enumeration {
1077            size: 4,
1078            base_type: Box::new(make_i32_le_type()),
1079            members: vec![
1080                EnumMember { name: "RED".to_string(), value: 0i32.to_le_bytes().to_vec() },
1081                EnumMember { name: "GREEN".to_string(), value: 1i32.to_le_bytes().to_vec() },
1082                EnumMember { name: "BLUE".to_string(), value: 2i32.to_le_bytes().to_vec() },
1083            ],
1084        };
1085        let mut raw = Vec::new();
1086        raw.extend_from_slice(&1i32.to_le_bytes()); // GREEN
1087        raw.extend_from_slice(&0i32.to_le_bytes()); // RED
1088        raw.extend_from_slice(&2i32.to_le_bytes()); // BLUE
1089        raw.extend_from_slice(&99i32.to_le_bytes()); // unknown
1090
1091        let names = read_enum_names(&raw, &dt).unwrap();
1092        assert_eq!(names[0], "GREEN");
1093        assert_eq!(names[1], "RED");
1094        assert_eq!(names[2], "BLUE");
1095        assert!(names[3].starts_with("UNKNOWN("));
1096    }
1097
1098    #[test]
1099    fn read_array_flat_basic() {
1100        // Array[3] of f64
1101        let dt = Datatype::Array {
1102            base_type: Box::new(make_f64_le_type()),
1103            dimensions: vec![3],
1104        };
1105        let mut raw = Vec::new();
1106        for v in &[1.0f64, 2.0, 3.0] {
1107            raw.extend_from_slice(&v.to_le_bytes());
1108        }
1109        let (data, base_dt, dims) = read_array_flat(&raw, &dt).unwrap();
1110        assert_eq!(dims, vec![3]);
1111        let vals = read_as_f64(&data, &base_dt).unwrap();
1112        assert_eq!(vals, vec![1.0, 2.0, 3.0]);
1113    }
1114
1115    #[test]
1116    fn read_object_references_basic() {
1117        use crate::datatype::ReferenceType;
1118        let dt = Datatype::Reference {
1119            size: 8,
1120            ref_type: ReferenceType::Object,
1121        };
1122        let mut raw = Vec::new();
1123        raw.extend_from_slice(&1024u64.to_le_bytes()); // valid ref
1124        raw.extend_from_slice(&u64::MAX.to_le_bytes()); // null ref
1125        raw.extend_from_slice(&2048u64.to_le_bytes()); // valid ref
1126
1127        let refs = read_object_references(&raw, &dt, 8).unwrap();
1128        assert_eq!(refs.len(), 3);
1129        assert_eq!(refs[0].address, 1024);
1130        assert!(!refs[0].is_null());
1131        assert!(refs[1].is_null());
1132        assert_eq!(refs[2].address, 2048);
1133    }
1134
1135    #[test]
1136    fn read_object_references_4byte_offset() {
1137        use crate::datatype::ReferenceType;
1138        let dt = Datatype::Reference {
1139            size: 4,
1140            ref_type: ReferenceType::Object,
1141        };
1142        let mut raw = Vec::new();
1143        raw.extend_from_slice(&512u32.to_le_bytes());
1144        raw.extend_from_slice(&u32::MAX.to_le_bytes()); // null ref
1145
1146        let refs = read_object_references(&raw, &dt, 4).unwrap();
1147        assert_eq!(refs.len(), 2);
1148        assert_eq!(refs[0].address, 512);
1149        assert!(refs[1].is_null());
1150    }
1151
1152    #[test]
1153    fn read_object_references_type_mismatch() {
1154        let dt = make_f64_le_type();
1155        let raw = vec![0u8; 8];
1156        let err = read_object_references(&raw, &dt, 8).unwrap_err();
1157        assert!(matches!(err, FormatError::TypeMismatch { .. }));
1158    }
1159
1160    #[test]
1161    fn read_region_references_basic() {
1162        use crate::datatype::ReferenceType;
1163        let dt = Datatype::Reference {
1164            size: 12,
1165            ref_type: ReferenceType::DatasetRegion,
1166        };
1167        let raw = vec![0xABu8; 24]; // two 12-byte region refs
1168
1169        let refs = read_region_references(&raw, &dt).unwrap();
1170        assert_eq!(refs.len(), 2);
1171        assert_eq!(refs[0].raw.len(), 12);
1172        assert_eq!(refs[1].raw.len(), 12);
1173    }
1174
1175    #[test]
1176    fn read_region_references_type_mismatch() {
1177        let dt = make_i32_le_type();
1178        let raw = vec![0u8; 12];
1179        let err = read_region_references(&raw, &dt).unwrap_err();
1180        assert!(matches!(err, FormatError::TypeMismatch { .. }));
1181    }
1182
1183    #[test]
1184    fn zerocopy_contiguous_returns_slice_into_file_data() {
1185        let dt = make_f64_le_type();
1186        let ds = make_simple_dataspace(&[3]);
1187        let mut file_data = vec![0u8; 1024];
1188        let offset = 256usize;
1189        let vals = [1.0f64, 2.0, 3.0];
1190        for (i, v) in vals.iter().enumerate() {
1191            file_data[offset + i * 8..offset + i * 8 + 8].copy_from_slice(&v.to_le_bytes());
1192        }
1193        let layout = DataLayout::Contiguous {
1194            address: Some(offset as u64),
1195            size: 24,
1196        };
1197        let result = read_raw_data_zerocopy(&file_data, &layout, &ds, &dt).unwrap();
1198        let slice = result.expect("contiguous should return Some");
1199        // Pointer identity: the slice must point into file_data, not a copy
1200        let file_range = file_data.as_ptr_range();
1201        assert!(file_range.contains(&slice.as_ptr()));
1202        assert_eq!(slice.len(), 24);
1203        // Verify the actual data
1204        let values = read_as_f64(slice, &dt).unwrap();
1205        assert_eq!(values, vec![1.0, 2.0, 3.0]);
1206    }
1207
1208    #[test]
1209    fn zerocopy_compact_returns_none() {
1210        let dt = make_f64_le_type();
1211        let ds = make_simple_dataspace(&[1]);
1212        let data = vec![0u8; 8];
1213        let layout = DataLayout::Compact { data };
1214        let result = read_raw_data_zerocopy(&[], &layout, &ds, &dt).unwrap();
1215        assert!(result.is_none());
1216    }
1217
1218    #[test]
1219    fn zerocopy_no_data_allocated() {
1220        let dt = make_f64_le_type();
1221        let ds = make_simple_dataspace(&[1]);
1222        let layout = DataLayout::Contiguous {
1223            address: None,
1224            size: 8,
1225        };
1226        let err = read_raw_data_zerocopy(&[], &layout, &ds, &dt).unwrap_err();
1227        assert!(matches!(err, FormatError::NoDataAllocated));
1228    }
1229
1230    #[test]
1231    fn zerocopy_size_mismatch() {
1232        let dt = make_f64_le_type();
1233        let ds = make_simple_dataspace(&[3]);
1234        let file_data = vec![0u8; 1024];
1235        let layout = DataLayout::Contiguous {
1236            address: Some(0),
1237            size: 16, // wrong: should be 24
1238        };
1239        let err = read_raw_data_zerocopy(&file_data, &layout, &ds, &dt).unwrap_err();
1240        assert!(matches!(err, FormatError::DataSizeMismatch { .. }));
1241    }
1242}