Skip to main content

rust_hdf5/format/messages/
dataspace.rs

1//! Dataspace message (type 0x01) — describes dataset dimensionality.
2//!
3//! Binary layout (version 2):
4//!   Byte 0: version = 2
5//!   Byte 1: dimensionality (ndims, 0–32)
6//!   Byte 2: flags (bit 0 = max dims present)
7//!   Byte 3: type (0 = scalar, 1 = simple, 2 = null)
8//!   Then ndims * sizeof_size bytes for current dimensions
9//!   Then (if flag bit 0) ndims * sizeof_size bytes for max dimensions
10
11use crate::format::{FormatContext, FormatError, FormatResult};
12
13const VERSION: u8 = 2;
14const FLAG_MAX_DIMS: u8 = 0x01;
15
16/// Dataspace type field values.
17const DS_TYPE_SCALAR: u8 = 0;
18const DS_TYPE_SIMPLE: u8 = 1;
19const _DS_TYPE_NULL: u8 = 2;
20
21/// Dataspace message payload.
22#[derive(Debug, Clone, PartialEq)]
23pub struct DataspaceMessage {
24    /// Current dimension sizes.
25    pub dims: Vec<u64>,
26    /// Optional maximum dimension sizes.  An entry of `u64::MAX` means unlimited.
27    pub max_dims: Option<Vec<u64>>,
28}
29
30impl DataspaceMessage {
31    // ------------------------------------------------------------------ factories
32
33    /// A scalar dataspace (rank 0, no max dims).
34    pub fn scalar() -> Self {
35        Self {
36            dims: Vec::new(),
37            max_dims: None,
38        }
39    }
40
41    /// A simple dataspace with fixed dimensions (max == current).
42    pub fn simple(dims: &[u64]) -> Self {
43        Self {
44            dims: dims.to_vec(),
45            max_dims: None,
46        }
47    }
48
49    /// A simple dataspace where every dimension is unlimited.
50    pub fn unlimited(current: &[u64]) -> Self {
51        Self {
52            dims: current.to_vec(),
53            max_dims: Some(vec![u64::MAX; current.len()]),
54        }
55    }
56
57    // ------------------------------------------------------------------ encode
58
59    pub fn encode(&self, ctx: &FormatContext) -> Vec<u8> {
60        let ndims = self.dims.len();
61        let ss = ctx.sizeof_size as usize;
62        let has_max = self.max_dims.is_some();
63        let flags: u8 = if has_max { FLAG_MAX_DIMS } else { 0 };
64
65        // Determine dataspace type
66        let ds_type = if ndims == 0 {
67            DS_TYPE_SCALAR
68        } else {
69            DS_TYPE_SIMPLE
70        };
71
72        let body_len = 4 + ndims * ss + if has_max { ndims * ss } else { 0 };
73        let mut buf = Vec::with_capacity(body_len);
74
75        buf.push(VERSION);
76        buf.push(ndims as u8);
77        buf.push(flags);
78        buf.push(ds_type); // type byte required for version 2
79
80        // current dimensions
81        for &d in &self.dims {
82            buf.extend_from_slice(&d.to_le_bytes()[..ss]);
83        }
84
85        // max dimensions
86        if let Some(ref maxes) = self.max_dims {
87            for &m in maxes {
88                buf.extend_from_slice(&m.to_le_bytes()[..ss]);
89            }
90        }
91
92        buf
93    }
94
95    // ------------------------------------------------------------------ decode
96
97    pub fn decode(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
98        if buf.len() < 4 {
99            return Err(FormatError::BufferTooShort {
100                needed: 4,
101                available: buf.len(),
102            });
103        }
104
105        let version = buf[0];
106        match version {
107            1 => Self::decode_v1(buf, ctx),
108            VERSION => Self::decode_v2(buf, ctx),
109            _ => Err(FormatError::InvalidVersion(version)),
110        }
111    }
112
113    /// Decode version 2 dataspace message.
114    fn decode_v2(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
115        let ndims = buf[1] as usize;
116        let flags = buf[2];
117        let _ds_type = buf[3]; // type byte: 0=scalar, 1=simple, 2=null
118        let has_max = (flags & FLAG_MAX_DIMS) != 0;
119        let ss = ctx.sizeof_size as usize;
120
121        let needed = 4 + ndims * ss + if has_max { ndims * ss } else { 0 };
122        if buf.len() < needed {
123            return Err(FormatError::BufferTooShort {
124                needed,
125                available: buf.len(),
126            });
127        }
128
129        let mut pos = 4;
130
131        let mut dims = Vec::with_capacity(ndims);
132        for _ in 0..ndims {
133            dims.push(read_size(&buf[pos..], ss));
134            pos += ss;
135        }
136
137        let max_dims = if has_max {
138            let mut v = Vec::with_capacity(ndims);
139            for _ in 0..ndims {
140                v.push(read_size(&buf[pos..], ss));
141                pos += ss;
142            }
143            Some(v)
144        } else {
145            None
146        };
147
148        Ok((Self { dims, max_dims }, pos))
149    }
150
151    /// Decode version 1 dataspace message.
152    ///
153    /// Version 1 layout:
154    /// ```text
155    /// Byte 0: version = 1
156    /// Byte 1: ndims
157    /// Byte 2: flags (bit 0 = max dims present, bit 1 = permutation indices present)
158    /// Byte 3: reserved
159    /// Bytes 4-7: reserved (4 bytes)
160    /// Then ndims * sizeof_size bytes for current dimensions
161    /// Then (if flag bit 0) ndims * sizeof_size bytes for max dimensions
162    /// Then (if flag bit 1) ndims * sizeof_size bytes for permutation indices
163    /// ```
164    fn decode_v1(buf: &[u8], ctx: &FormatContext) -> FormatResult<(Self, usize)> {
165        if buf.len() < 8 {
166            return Err(FormatError::BufferTooShort {
167                needed: 8,
168                available: buf.len(),
169            });
170        }
171
172        let ndims = buf[1] as usize;
173        let flags = buf[2];
174        let has_max = (flags & FLAG_MAX_DIMS) != 0;
175        let has_perm = (flags & 0x02) != 0;
176        let ss = ctx.sizeof_size as usize;
177
178        // Header is 8 bytes for v1 (4 fixed + 4 reserved)
179        let mut needed = 8 + ndims * ss;
180        if has_max {
181            needed += ndims * ss;
182        }
183        if has_perm {
184            needed += ndims * ss;
185        }
186        if buf.len() < needed {
187            return Err(FormatError::BufferTooShort {
188                needed,
189                available: buf.len(),
190            });
191        }
192
193        let mut pos = 8; // skip version(1) + ndims(1) + flags(1) + reserved(1) + reserved(4)
194
195        let mut dims = Vec::with_capacity(ndims);
196        for _ in 0..ndims {
197            dims.push(read_size(&buf[pos..], ss));
198            pos += ss;
199        }
200
201        let max_dims = if has_max {
202            let mut v = Vec::with_capacity(ndims);
203            for _ in 0..ndims {
204                v.push(read_size(&buf[pos..], ss));
205                pos += ss;
206            }
207            Some(v)
208        } else {
209            None
210        };
211
212        // Skip permutation indices if present
213        if has_perm {
214            pos += ndims * ss;
215        }
216
217        Ok((Self { dims, max_dims }, pos))
218    }
219}
220
221/// Read a little-endian unsigned integer of `n` bytes (1..=8) into a `u64`.
222fn read_size(buf: &[u8], n: usize) -> u64 {
223    let mut tmp = [0u8; 8];
224    tmp[..n].copy_from_slice(&buf[..n]);
225    u64::from_le_bytes(tmp)
226}
227
228// ======================================================================= tests
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    fn ctx8() -> FormatContext {
235        FormatContext {
236            sizeof_addr: 8,
237            sizeof_size: 8,
238        }
239    }
240
241    fn ctx4() -> FormatContext {
242        FormatContext {
243            sizeof_addr: 4,
244            sizeof_size: 4,
245        }
246    }
247
248    #[test]
249    fn roundtrip_scalar() {
250        let msg = DataspaceMessage::scalar();
251        let encoded = msg.encode(&ctx8());
252        assert_eq!(encoded.len(), 4); // version + ndims + flags + type
253        let (decoded, consumed) = DataspaceMessage::decode(&encoded, &ctx8()).unwrap();
254        assert_eq!(consumed, 4);
255        assert_eq!(decoded, msg);
256    }
257
258    #[test]
259    fn roundtrip_simple_1d() {
260        let msg = DataspaceMessage::simple(&[100]);
261        let encoded = msg.encode(&ctx8());
262        // 4 header + 1*8 dims = 12
263        assert_eq!(encoded.len(), 12);
264        let (decoded, consumed) = DataspaceMessage::decode(&encoded, &ctx8()).unwrap();
265        assert_eq!(consumed, 12);
266        assert_eq!(decoded, msg);
267    }
268
269    #[test]
270    fn roundtrip_simple_3d_ctx4() {
271        let msg = DataspaceMessage::simple(&[10, 20, 30]);
272        let encoded = msg.encode(&ctx4());
273        // 4 + 3*4 = 16
274        assert_eq!(encoded.len(), 16);
275        let (decoded, consumed) = DataspaceMessage::decode(&encoded, &ctx4()).unwrap();
276        assert_eq!(consumed, 16);
277        assert_eq!(decoded, msg);
278    }
279
280    #[test]
281    fn roundtrip_unlimited() {
282        let msg = DataspaceMessage::unlimited(&[5, 10]);
283        let encoded = msg.encode(&ctx8());
284        // 4 + 2*8 dims + 2*8 max = 36
285        assert_eq!(encoded.len(), 36);
286        let (decoded, consumed) = DataspaceMessage::decode(&encoded, &ctx8()).unwrap();
287        assert_eq!(consumed, 36);
288        assert_eq!(decoded, msg);
289        assert_eq!(decoded.max_dims.as_ref().unwrap(), &vec![u64::MAX; 2]);
290    }
291
292    #[test]
293    fn roundtrip_partial_max() {
294        let msg = DataspaceMessage {
295            dims: vec![3, 4],
296            max_dims: Some(vec![100, u64::MAX]),
297        };
298        let encoded = msg.encode(&ctx8());
299        let (decoded, _) = DataspaceMessage::decode(&encoded, &ctx8()).unwrap();
300        assert_eq!(decoded, msg);
301    }
302
303    #[test]
304    fn decode_bad_version() {
305        let buf = [99u8, 0, 0, 0]; // version 99 — unsupported
306        let err = DataspaceMessage::decode(&buf, &ctx8()).unwrap_err();
307        match err {
308            FormatError::InvalidVersion(99) => {}
309            other => panic!("unexpected error: {:?}", other),
310        }
311    }
312
313    #[test]
314    fn decode_v1_simple_1d() {
315        // Build a version 1 dataspace: 1D, dims=[100], no max
316        let mut buf = vec![
317            1, // version 1
318            1, // ndims = 1
319            0, // flags (no max dims)
320            0, // reserved
321        ];
322        buf.extend_from_slice(&[0u8; 4]); // reserved (4 bytes)
323        buf.extend_from_slice(&100u64.to_le_bytes()); // dims[0] = 100
324
325        let (msg, consumed) = DataspaceMessage::decode(&buf, &ctx8()).unwrap();
326        assert_eq!(consumed, 16); // 8 header + 8 dim
327        assert_eq!(msg.dims, vec![100]);
328        assert_eq!(msg.max_dims, None);
329    }
330
331    #[test]
332    fn decode_v1_with_max_dims() {
333        let mut buf = vec![
334            1, // version 1
335            2, // ndims = 2
336            1, // flags = has max dims
337            0, // reserved
338        ];
339        buf.extend_from_slice(&[0u8; 4]); // reserved
340        buf.extend_from_slice(&10u64.to_le_bytes()); // dims[0] = 10
341        buf.extend_from_slice(&20u64.to_le_bytes()); // dims[1] = 20
342        buf.extend_from_slice(&u64::MAX.to_le_bytes()); // max_dims[0] = unlimited
343        buf.extend_from_slice(&100u64.to_le_bytes()); // max_dims[1] = 100
344
345        let (msg, consumed) = DataspaceMessage::decode(&buf, &ctx8()).unwrap();
346        assert_eq!(consumed, 40); // 8 + 2*8 + 2*8
347        assert_eq!(msg.dims, vec![10, 20]);
348        assert_eq!(msg.max_dims, Some(vec![u64::MAX, 100]));
349    }
350
351    #[test]
352    fn decode_buffer_too_short() {
353        let buf = [2u8, 1, 0]; // version ok, but too short (need 4 header bytes)
354        let err = DataspaceMessage::decode(&buf, &ctx8()).unwrap_err();
355        match err {
356            FormatError::BufferTooShort { .. } => {}
357            other => panic!("unexpected error: {:?}", other),
358        }
359    }
360
361    #[test]
362    fn decode_buffer_too_short_for_dims() {
363        // ndims=1, no max, sizeof_size=8 => need 4 + 8 = 12 bytes, give 6
364        let buf = [2u8, 1, 0, 1, 0, 0];
365        let err = DataspaceMessage::decode(&buf, &ctx8()).unwrap_err();
366        match err {
367            FormatError::BufferTooShort {
368                needed: 12,
369                available: 6,
370            } => {}
371            other => panic!("unexpected error: {:?}", other),
372        }
373    }
374
375    #[test]
376    fn version_byte_is_two() {
377        let msg = DataspaceMessage::simple(&[42]);
378        let encoded = msg.encode(&ctx8());
379        assert_eq!(encoded[0], 2);
380    }
381}