Skip to main content

rustyhdf5_format/
vl_data.rs

1//! Variable-length data reading (VL strings & VL sequences).
2//!
3//! VL data elements in HDF5 store their values in the global heap.
4//! The raw data for each element contains a global heap ID:
5//! `sequence_length(4 LE) + collection_address(offset_size LE) + object_index(4 LE)`.
6
7#[cfg(not(feature = "std"))]
8use alloc::{string::String, vec::Vec};
9
10use crate::error::FormatError;
11use crate::global_heap::GlobalHeapCollection;
12
13/// A parsed variable-length element reference (global heap ID).
14#[derive(Debug, Clone)]
15pub struct VlElement {
16    /// Length of the VL data.
17    pub length: u32,
18    /// Address of the global heap collection containing the data.
19    pub collection_address: u64,
20    /// Index of the object within the collection.
21    pub object_index: u32,
22}
23
24fn ensure_len(data: &[u8], offset: usize, needed: usize) -> Result<(), FormatError> {
25    match offset.checked_add(needed) {
26        Some(end) if end <= data.len() => Ok(()),
27        _ => Err(FormatError::UnexpectedEof {
28            expected: offset.saturating_add(needed),
29            available: data.len(),
30        }),
31    }
32}
33
34fn read_offset(data: &[u8], pos: usize, offset_size: u8) -> Result<u64, FormatError> {
35    let s = offset_size as usize;
36    ensure_len(data, pos, s)?;
37    let slice = &data[pos..pos + s];
38    Ok(match offset_size {
39        2 => u16::from_le_bytes([slice[0], slice[1]]) as u64,
40        4 => u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as u64,
41        8 => u64::from_le_bytes([
42            slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
43        ]),
44        _ => return Err(FormatError::InvalidOffsetSize(offset_size)),
45    })
46}
47
48/// Parse VL global heap references from raw attribute/dataset data.
49pub fn parse_vl_references(
50    raw_data: &[u8],
51    num_elements: u64,
52    offset_size: u8,
53) -> Result<Vec<VlElement>, FormatError> {
54    let elem_size = 4 + offset_size as usize + 4; // length + address + index
55    let total = num_elements as usize * elem_size;
56    if raw_data.len() < total {
57        return Err(FormatError::UnexpectedEof {
58            expected: total,
59            available: raw_data.len(),
60        });
61    }
62
63    let mut elements = Vec::with_capacity(num_elements as usize);
64    let mut pos = 0;
65
66    for _ in 0..num_elements {
67        let length = u32::from_le_bytes([
68            raw_data[pos],
69            raw_data[pos + 1],
70            raw_data[pos + 2],
71            raw_data[pos + 3],
72        ]);
73        pos += 4;
74
75        let collection_address = read_offset(raw_data, pos, offset_size)?;
76        pos += offset_size as usize;
77
78        let object_index = u32::from_le_bytes([
79            raw_data[pos],
80            raw_data[pos + 1],
81            raw_data[pos + 2],
82            raw_data[pos + 3],
83        ]);
84        pos += 4;
85
86        elements.push(VlElement {
87            length,
88            collection_address,
89            object_index,
90        });
91    }
92
93    Ok(elements)
94}
95
96/// Check if an address represents an undefined/null address.
97fn is_undefined_address(addr: u64, offset_size: u8) -> bool {
98    match offset_size {
99        2 => addr == 0xFFFF,
100        4 => addr == 0xFFFF_FFFF,
101        8 => addr == 0xFFFF_FFFF_FFFF_FFFF,
102        _ => false,
103    }
104}
105
106/// Resolve VL strings from raw data by looking up each element in the global heap.
107pub fn read_vl_strings(
108    file_data: &[u8],
109    raw_data: &[u8],
110    num_elements: u64,
111    offset_size: u8,
112    length_size: u8,
113) -> Result<Vec<String>, FormatError> {
114    let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
115    let mut result = Vec::with_capacity(refs.len());
116
117    for vl in &refs {
118        if vl.length == 0 && is_undefined_address(vl.collection_address, offset_size) {
119            result.push(String::new());
120            continue;
121        }
122        if vl.length == 0 && vl.collection_address == 0 {
123            result.push(String::new());
124            continue;
125        }
126
127        let coll = GlobalHeapCollection::parse(
128            file_data,
129            vl.collection_address as usize,
130            length_size,
131        )?;
132        let obj = coll
133            .get_object(vl.object_index as u16)
134            .ok_or(FormatError::GlobalHeapObjectNotFound {
135                collection_address: vl.collection_address,
136                index: vl.object_index as u16,
137            })?;
138
139        // The object data is the raw string bytes
140        let len = (vl.length as usize).min(obj.data.len());
141        let s = String::from_utf8_lossy(&obj.data[..len]).into_owned();
142        result.push(s);
143    }
144
145    Ok(result)
146}
147
148/// Resolve VL byte sequences from raw data.
149pub fn read_vl_bytes(
150    file_data: &[u8],
151    raw_data: &[u8],
152    num_elements: u64,
153    offset_size: u8,
154    length_size: u8,
155) -> Result<Vec<Vec<u8>>, FormatError> {
156    let refs = parse_vl_references(raw_data, num_elements, offset_size)?;
157    let mut result = Vec::with_capacity(refs.len());
158
159    for vl in &refs {
160        if vl.length == 0 && (is_undefined_address(vl.collection_address, offset_size) || vl.collection_address == 0) {
161            result.push(Vec::new());
162            continue;
163        }
164
165        let coll = GlobalHeapCollection::parse(
166            file_data,
167            vl.collection_address as usize,
168            length_size,
169        )?;
170        let obj = coll
171            .get_object(vl.object_index as u16)
172            .ok_or(FormatError::GlobalHeapObjectNotFound {
173                collection_address: vl.collection_address,
174                index: vl.object_index as u16,
175            })?;
176
177        let len = (vl.length as usize).min(obj.data.len());
178        result.push(obj.data[..len].to_vec());
179    }
180
181    Ok(result)
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    /// Build a global heap collection at given offset in a file buffer.
189    fn build_gcol_at(
190        file_data: &mut Vec<u8>,
191        offset: usize,
192        objects: &[(u16, &[u8])], // (index, data)
193    ) {
194        let length_size = 8usize;
195
196        // Ensure file_data is large enough
197        let header_size = 8 + length_size;
198        let mut obj_total = 0usize;
199        for (_, data) in objects {
200            let padded = (data.len() + 7) & !7;
201            obj_total += 8 + length_size + padded;
202        }
203        obj_total += 2; // free space marker
204        let collection_size = header_size + obj_total;
205        let needed = offset + collection_size;
206        if file_data.len() < needed {
207            file_data.resize(needed, 0);
208        }
209
210        let mut pos = offset;
211        // Signature
212        file_data[pos..pos + 4].copy_from_slice(b"GCOL");
213        file_data[pos + 4] = 1; // version
214        // reserved(3) already 0
215        pos += 8;
216        file_data[pos..pos + 8].copy_from_slice(&(collection_size as u64).to_le_bytes());
217        pos += 8;
218
219        for (index, data) in objects {
220            file_data[pos..pos + 2].copy_from_slice(&index.to_le_bytes());
221            file_data[pos + 2..pos + 4].copy_from_slice(&1u16.to_le_bytes()); // ref_count
222            // reserved(4) already 0
223            pos += 8;
224            file_data[pos..pos + 8].copy_from_slice(&(data.len() as u64).to_le_bytes());
225            pos += 8;
226            file_data[pos..pos + data.len()].copy_from_slice(data);
227            let padded = (data.len() + 7) & !7;
228            pos += padded;
229        }
230        // free space marker
231        file_data[pos..pos + 2].copy_from_slice(&0u16.to_le_bytes());
232    }
233
234    /// Build VL reference raw data for given strings at a collection address.
235    fn build_vl_refs(
236        strings: &[&str],
237        collection_address: u64,
238        start_index: u16,
239        offset_size: u8,
240    ) -> Vec<u8> {
241        let mut raw = Vec::new();
242        for (i, s) in strings.iter().enumerate() {
243            raw.extend_from_slice(&(s.len() as u32).to_le_bytes());
244            match offset_size {
245                4 => raw.extend_from_slice(&(collection_address as u32).to_le_bytes()),
246                8 => raw.extend_from_slice(&collection_address.to_le_bytes()),
247                _ => panic!("unsupported"),
248            }
249            raw.extend_from_slice(&(start_index as u32 + i as u32).to_le_bytes());
250        }
251        raw
252    }
253
254    #[test]
255    fn parse_vl_references_two_elements() {
256        let raw = build_vl_refs(&["hello", "world"], 0x1000, 1, 8);
257        let refs = parse_vl_references(&raw, 2, 8).unwrap();
258        assert_eq!(refs.len(), 2);
259        assert_eq!(refs[0].length, 5);
260        assert_eq!(refs[0].collection_address, 0x1000);
261        assert_eq!(refs[0].object_index, 1);
262        assert_eq!(refs[1].length, 5);
263        assert_eq!(refs[1].object_index, 2);
264    }
265
266    #[test]
267    fn read_vl_strings_from_heap() {
268        let gcol_offset = 256usize;
269        let mut file_data = vec![0u8; 512];
270        build_gcol_at(&mut file_data, gcol_offset, &[
271            (1, b"Alice"),
272            (2, b"Bob"),
273        ]);
274
275        let raw = build_vl_refs(&["Alice", "Bob"], gcol_offset as u64, 1, 8);
276        let strings = read_vl_strings(&file_data, &raw, 2, 8, 8).unwrap();
277        assert_eq!(strings, vec!["Alice", "Bob"]);
278    }
279
280    #[test]
281    fn null_vl_element_empty_string() {
282        // length=0, address=undefined
283        let mut raw = Vec::new();
284        raw.extend_from_slice(&0u32.to_le_bytes()); // length=0
285        raw.extend_from_slice(&u64::MAX.to_le_bytes()); // undefined address
286        raw.extend_from_slice(&0u32.to_le_bytes()); // index
287
288        let file_data = vec![0u8; 16];
289        let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
290        assert_eq!(strings, vec![""]);
291    }
292
293    #[test]
294    fn null_vl_element_zero_address() {
295        let mut raw = Vec::new();
296        raw.extend_from_slice(&0u32.to_le_bytes());
297        raw.extend_from_slice(&0u64.to_le_bytes());
298        raw.extend_from_slice(&0u32.to_le_bytes());
299
300        let file_data = vec![0u8; 16];
301        let strings = read_vl_strings(&file_data, &raw, 1, 8, 8).unwrap();
302        assert_eq!(strings, vec![""]);
303    }
304
305    #[test]
306    fn read_vl_bytes_from_heap() {
307        let gcol_offset = 128usize;
308        let mut file_data = vec![0u8; 512];
309        build_gcol_at(&mut file_data, gcol_offset, &[
310            (1, &[0xDE, 0xAD]),
311            (2, &[0xBE, 0xEF, 0xCA]),
312        ]);
313
314        let raw = build_vl_refs(&["ab", "abc"], gcol_offset as u64, 1, 8);
315        // Fix lengths to match actual byte lengths
316        let mut raw_fixed = Vec::new();
317        raw_fixed.extend_from_slice(&2u32.to_le_bytes());
318        raw_fixed.extend_from_slice(&(gcol_offset as u64).to_le_bytes());
319        raw_fixed.extend_from_slice(&1u32.to_le_bytes());
320        raw_fixed.extend_from_slice(&3u32.to_le_bytes());
321        raw_fixed.extend_from_slice(&(gcol_offset as u64).to_le_bytes());
322        raw_fixed.extend_from_slice(&2u32.to_le_bytes());
323
324        let bytes = read_vl_bytes(&file_data, &raw_fixed, 2, 8, 8).unwrap();
325        assert_eq!(bytes, vec![vec![0xDE, 0xAD], vec![0xBE, 0xEF, 0xCA]]);
326    }
327
328    #[test]
329    fn parse_vl_references_truncated_error() {
330        let raw = vec![0u8; 10]; // too short for 1 element with offset_size=8
331        let err = parse_vl_references(&raw, 1, 8).unwrap_err();
332        assert!(matches!(err, FormatError::UnexpectedEof { .. }));
333    }
334}