Skip to main content

rust_hdf5/format/messages/
link_info.rs

1//! Link info message (type 0x02) — metadata about link storage in a group.
2//!
3//! Binary layout (version 0):
4//!   Byte 0: version = 0
5//!   Byte 1: flags
6//!     bit 0: max creation order tracked
7//!     bit 1: creation order indexed
8//!   [if bit 0]: max_creation_order u64 LE
9//!   fractal_heap_address: sizeof_addr bytes (UNDEF if compact storage)
10//!   name_btree_address:   sizeof_addr bytes (UNDEF if compact storage)
11//!   [if bit 1]: creation_order_btree_address: sizeof_addr bytes
12
13use crate::format::bytes::read_le_addr as read_addr;
14use crate::format::{FormatContext, FormatError, FormatResult, UNDEF_ADDR};
15
16const VERSION: u8 = 0;
17const FLAG_MAX_CREATION_ORDER: u8 = 0x01;
18const FLAG_CREATION_ORDER_INDEXED: u8 = 0x02;
19
20/// Link info message payload.
21#[derive(Debug, Clone, PartialEq)]
22pub struct LinkInfoMessage {
23    /// Maximum creation order value (present if tracking creation order).
24    pub max_creation_order: Option<u64>,
25    /// Fractal heap address for link name storage.  `UNDEF_ADDR` for compact groups.
26    pub fractal_heap_address: u64,
27    /// Name-index B-tree v2 address.  `UNDEF_ADDR` for compact groups.
28    pub name_btree_address: u64,
29    /// Creation-order B-tree v2 address (present only when creation order is indexed).
30    pub creation_order_btree_address: Option<u64>,
31}
32
33impl LinkInfoMessage {
34    /// A compact link info (no fractal heap, no B-trees).
35    pub fn compact() -> Self {
36        Self {
37            max_creation_order: None,
38            fractal_heap_address: UNDEF_ADDR,
39            name_btree_address: UNDEF_ADDR,
40            creation_order_btree_address: None,
41        }
42    }
43
44    /// A compact link info that tracks creation order.
45    pub fn compact_with_creation_order() -> Self {
46        Self {
47            max_creation_order: Some(0),
48            fractal_heap_address: UNDEF_ADDR,
49            name_btree_address: UNDEF_ADDR,
50            creation_order_btree_address: None,
51        }
52    }
53
54    // ------------------------------------------------------------------ encode
55
56    pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
57        let sa = ctx.sizeof_addr as usize;
58
59        let mut flags: u8 = 0;
60        if self.max_creation_order.is_some() {
61            flags |= FLAG_MAX_CREATION_ORDER;
62        }
63        if self.creation_order_btree_address.is_some() {
64            flags |= FLAG_CREATION_ORDER_INDEXED;
65        }
66
67        let mut buf = Vec::with_capacity(2 + 8 + 3 * sa);
68        buf.push(VERSION);
69        buf.push(flags);
70
71        if let Some(max_co) = self.max_creation_order {
72            buf.extend_from_slice(&max_co.to_le_bytes());
73        }
74
75        buf.extend_from_slice(&self.fractal_heap_address.to_le_bytes()[..sa]);
76        buf.extend_from_slice(&self.name_btree_address.to_le_bytes()[..sa]);
77
78        if let Some(co_addr) = self.creation_order_btree_address {
79            buf.extend_from_slice(&co_addr.to_le_bytes()[..sa]);
80        }
81
82        buf
83    }
84
85    // ------------------------------------------------------------------ decode
86
87    pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
88        if buf.len() < 2 {
89            return Err(FormatError::BufferTooShort {
90                needed: 2,
91                available: buf.len(),
92            });
93        }
94
95        let version = buf[0];
96        if version != VERSION {
97            return Err(FormatError::InvalidVersion(version));
98        }
99
100        let flags = buf[1];
101        let has_max_co = (flags & FLAG_MAX_CREATION_ORDER) != 0;
102        let has_co_btree = (flags & FLAG_CREATION_ORDER_INDEXED) != 0;
103
104        let sa = ctx.sizeof_addr as usize;
105        let mut pos = 2;
106
107        let max_creation_order = if has_max_co {
108            check_len(buf, pos, 8)?;
109            let v = u64::from_le_bytes([
110                buf[pos],
111                buf[pos + 1],
112                buf[pos + 2],
113                buf[pos + 3],
114                buf[pos + 4],
115                buf[pos + 5],
116                buf[pos + 6],
117                buf[pos + 7],
118            ]);
119            pos += 8;
120            Some(v)
121        } else {
122            None
123        };
124
125        check_len(buf, pos, sa)?;
126        let fractal_heap_address = read_addr(&buf[pos..], sa);
127        pos += sa;
128
129        check_len(buf, pos, sa)?;
130        let name_btree_address = read_addr(&buf[pos..], sa);
131        pos += sa;
132
133        let creation_order_btree_address = if has_co_btree {
134            check_len(buf, pos, sa)?;
135            let v = read_addr(&buf[pos..], sa);
136            pos += sa;
137            Some(v)
138        } else {
139            None
140        };
141
142        Ok((
143            Self {
144                max_creation_order,
145                fractal_heap_address,
146                name_btree_address,
147                creation_order_btree_address,
148            },
149            pos,
150        ))
151    }
152}
153
154// ========================================================================= helpers
155
156fn check_len(buf: &[u8], pos: usize, need: usize) -> FormatResult<()> {
157    if buf.len() < pos + need {
158        Err(FormatError::BufferTooShort {
159            needed: pos + need,
160            available: buf.len(),
161        })
162    } else {
163        Ok(())
164    }
165}
166
167// ======================================================================= tests
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    fn ctx8() -> FormatContext {
174        FormatContext {
175            sizeof_addr: 8,
176            sizeof_size: 8,
177        }
178    }
179
180    fn ctx4() -> FormatContext {
181        FormatContext {
182            sizeof_addr: 4,
183            sizeof_size: 4,
184        }
185    }
186
187    #[test]
188    fn roundtrip_compact() {
189        let msg = LinkInfoMessage::compact();
190        let encoded = msg.encode(&ctx8());
191        // 2 header + 8 + 8 = 18
192        assert_eq!(encoded.len(), 18);
193        let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
194        assert_eq!(consumed, 18);
195        assert_eq!(decoded, msg);
196    }
197
198    #[test]
199    fn roundtrip_compact_ctx4() {
200        let msg = LinkInfoMessage::compact();
201        let encoded = msg.encode(&ctx4());
202        // 2 header + 4 + 4 = 10
203        assert_eq!(encoded.len(), 10);
204        let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx4()).unwrap();
205        assert_eq!(consumed, 10);
206        assert_eq!(decoded, msg);
207    }
208
209    #[test]
210    fn roundtrip_with_creation_order() {
211        let msg = LinkInfoMessage::compact_with_creation_order();
212        let encoded = msg.encode(&ctx8());
213        // 2 + 8(max_co) + 8 + 8 = 26
214        assert_eq!(encoded.len(), 26);
215        let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
216        assert_eq!(consumed, 26);
217        assert_eq!(decoded, msg);
218    }
219
220    #[test]
221    fn roundtrip_full() {
222        let msg = LinkInfoMessage {
223            max_creation_order: Some(42),
224            fractal_heap_address: 0x1000,
225            name_btree_address: 0x2000,
226            creation_order_btree_address: Some(0x3000),
227        };
228        let encoded = msg.encode(&ctx8());
229        // 2 + 8 + 8 + 8 + 8 = 34
230        assert_eq!(encoded.len(), 34);
231        let (decoded, consumed) = LinkInfoMessage::decode(&encoded, &ctx8()).unwrap();
232        assert_eq!(consumed, 34);
233        assert_eq!(decoded, msg);
234    }
235
236    #[test]
237    fn roundtrip_undef_addresses() {
238        let msg = LinkInfoMessage {
239            max_creation_order: None,
240            fractal_heap_address: UNDEF_ADDR,
241            name_btree_address: UNDEF_ADDR,
242            creation_order_btree_address: None,
243        };
244        let encoded = msg.encode(&ctx4());
245        let (decoded, _) = LinkInfoMessage::decode(&encoded, &ctx4()).unwrap();
246        assert_eq!(decoded.fractal_heap_address, UNDEF_ADDR);
247        assert_eq!(decoded.name_btree_address, UNDEF_ADDR);
248    }
249
250    #[test]
251    fn decode_bad_version() {
252        let buf = [1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0];
253        let err = LinkInfoMessage::decode(&buf, &ctx8()).unwrap_err();
254        match err {
255            FormatError::InvalidVersion(1) => {}
256            other => panic!("unexpected error: {:?}", other),
257        }
258    }
259
260    #[test]
261    fn decode_buffer_too_short() {
262        let buf = [0u8];
263        let err = LinkInfoMessage::decode(&buf, &ctx8()).unwrap_err();
264        match err {
265            FormatError::BufferTooShort { .. } => {}
266            other => panic!("unexpected error: {:?}", other),
267        }
268    }
269
270    #[test]
271    fn version_byte() {
272        let encoded = LinkInfoMessage::compact().encode(&ctx8());
273        assert_eq!(encoded[0], 0);
274    }
275}