Skip to main content

rustyhdf5_format/
attribute_info.rs

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