Skip to main content

rustyhdf5_format/
link_info.rs

1//! HDF5 Link Info message parsing (message type 0x0002).
2
3use crate::error::FormatError;
4
5/// Parsed Link Info message from a v2 group object header.
6#[derive(Debug, Clone, PartialEq)]
7pub struct LinkInfoMessage {
8    /// Maximum creation order value (if tracking is enabled).
9    pub max_creation_order: Option<u64>,
10    /// Address of fractal heap for dense link storage. None means compact storage only.
11    pub fractal_heap_address: Option<u64>,
12    /// Address of B-tree v2 for name-ordered link index.
13    pub btree_name_index_address: Option<u64>,
14    /// Address of B-tree v2 for creation-order link index.
15    pub btree_creation_order_address: Option<u64>,
16}
17
18fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
19    let s = size as usize;
20    if pos + s > data.len() {
21        return Err(FormatError::UnexpectedEof {
22            expected: pos + s,
23            available: data.len(),
24        });
25    }
26    Ok(match size {
27        2 => u16::from_le_bytes([data[pos], data[pos + 1]]) as u64,
28        4 => u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as u64,
29        8 => u64::from_le_bytes([
30            data[pos],
31            data[pos + 1],
32            data[pos + 2],
33            data[pos + 3],
34            data[pos + 4],
35            data[pos + 5],
36            data[pos + 6],
37            data[pos + 7],
38        ]),
39        _ => return Err(FormatError::InvalidOffsetSize(size)),
40    })
41}
42
43fn is_undefined(val: u64, offset_size: u8) -> bool {
44    match offset_size {
45        2 => val == 0xFFFF,
46        4 => val == 0xFFFF_FFFF,
47        8 => val == 0xFFFF_FFFF_FFFF_FFFF,
48        _ => false,
49    }
50}
51
52fn ensure_len(data: &[u8], pos: usize, needed: usize) -> Result<(), FormatError> {
53    match pos.checked_add(needed) {
54        Some(end) if end <= data.len() => Ok(()),
55        _ => Err(FormatError::UnexpectedEof {
56            expected: pos.saturating_add(needed),
57            available: data.len(),
58        }),
59    }
60}
61
62impl LinkInfoMessage {
63    /// Parse a Link Info message from raw message data.
64    pub fn parse(data: &[u8], offset_size: u8) -> Result<LinkInfoMessage, FormatError> {
65        ensure_len(data, 0, 2)?;
66
67        let version = data[0];
68        if version != 0 {
69            return Err(FormatError::InvalidLinkInfoVersion(version));
70        }
71
72        let flags = data[1];
73        let has_max_creation_order = flags & 0x01 != 0;
74        let has_creation_order_index = flags & 0x02 != 0;
75
76        let mut pos = 2;
77
78        let max_creation_order = if has_max_creation_order {
79            ensure_len(data, pos, 8)?;
80            let v = u64::from_le_bytes([
81                data[pos],
82                data[pos + 1],
83                data[pos + 2],
84                data[pos + 3],
85                data[pos + 4],
86                data[pos + 5],
87                data[pos + 6],
88                data[pos + 7],
89            ]);
90            pos += 8;
91            Some(v)
92        } else {
93            None
94        };
95
96        let fh_addr = read_offset(data, pos, offset_size)?;
97        pos += offset_size as usize;
98        let fractal_heap_address = if is_undefined(fh_addr, offset_size) {
99            None
100        } else {
101            Some(fh_addr)
102        };
103
104        let btree_addr = read_offset(data, pos, offset_size)?;
105        pos += offset_size as usize;
106        let btree_name_index_address = if is_undefined(btree_addr, offset_size) {
107            None
108        } else {
109            Some(btree_addr)
110        };
111
112        let btree_creation_order_address = if has_creation_order_index {
113            let addr = read_offset(data, pos, offset_size)?;
114            if is_undefined(addr, offset_size) {
115                None
116            } else {
117                Some(addr)
118            }
119        } else {
120            None
121        };
122
123        Ok(LinkInfoMessage {
124            max_creation_order,
125            fractal_heap_address,
126            btree_name_index_address,
127            btree_creation_order_address,
128        })
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn compact_storage() {
138        // version=0, flags=0, fractal_heap=undef, btree=undef
139        let mut data = vec![0u8; 2 + 8 + 8];
140        data[0] = 0; // version
141        data[1] = 0; // flags
142        // fractal heap address = undefined
143        data[2..10].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_le_bytes());
144        // btree name index = undefined
145        data[10..18].copy_from_slice(&0xFFFF_FFFF_FFFF_FFFFu64.to_le_bytes());
146
147        let msg = LinkInfoMessage::parse(&data, 8).unwrap();
148        assert_eq!(msg.fractal_heap_address, None);
149        assert_eq!(msg.btree_name_index_address, None);
150        assert_eq!(msg.max_creation_order, None);
151        assert_eq!(msg.btree_creation_order_address, None);
152    }
153
154    #[test]
155    fn dense_storage_with_creation_order() {
156        // flags: bit 0 (max creation order) + bit 1 (creation order index)
157        let mut data = Vec::new();
158        data.push(0); // version
159        data.push(0x03); // flags
160        data.extend_from_slice(&42u64.to_le_bytes()); // max_creation_order
161        data.extend_from_slice(&0x1000u64.to_le_bytes()); // fractal heap
162        data.extend_from_slice(&0x2000u64.to_le_bytes()); // btree name
163        data.extend_from_slice(&0x3000u64.to_le_bytes()); // btree creation order
164
165        let msg = LinkInfoMessage::parse(&data, 8).unwrap();
166        assert_eq!(msg.max_creation_order, Some(42));
167        assert_eq!(msg.fractal_heap_address, Some(0x1000));
168        assert_eq!(msg.btree_name_index_address, Some(0x2000));
169        assert_eq!(msg.btree_creation_order_address, Some(0x3000));
170    }
171
172    #[test]
173    fn no_creation_order_tracking() {
174        let mut data = Vec::new();
175        data.push(0); // version
176        data.push(0x00); // flags: nothing
177        data.extend_from_slice(&0x500u64.to_le_bytes()); // fractal heap
178        data.extend_from_slice(&0x600u64.to_le_bytes()); // btree name
179
180        let msg = LinkInfoMessage::parse(&data, 8).unwrap();
181        assert_eq!(msg.max_creation_order, None);
182        assert_eq!(msg.fractal_heap_address, Some(0x500));
183        assert_eq!(msg.btree_name_index_address, Some(0x600));
184        assert_eq!(msg.btree_creation_order_address, None);
185    }
186
187    #[test]
188    fn invalid_version() {
189        let data = vec![1, 0, 0, 0];
190        let err = LinkInfoMessage::parse(&data, 8).unwrap_err();
191        assert_eq!(err, FormatError::InvalidLinkInfoVersion(1));
192    }
193
194    #[test]
195    fn four_byte_offsets() {
196        let mut data = Vec::new();
197        data.push(0); // version
198        data.push(0x00); // flags
199        data.extend_from_slice(&0x100u32.to_le_bytes()); // fractal heap
200        data.extend_from_slice(&0x200u32.to_le_bytes()); // btree name
201
202        let msg = LinkInfoMessage::parse(&data, 4).unwrap();
203        assert_eq!(msg.fractal_heap_address, Some(0x100));
204        assert_eq!(msg.btree_name_index_address, Some(0x200));
205    }
206}