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