Skip to main content

rust_hdf5/format/messages/
fill_value.rs

1//! Fill value message (type 0x05) — specifies the default fill value for
2//! unwritten elements.
3//!
4//! Three on-disk versions exist for this message and a conforming reader
5//! must accept all of them (`H5O__fill_new_decode`, H5Ofill.c):
6//!
7//! Versions 1 & 2:
8//!   Byte 0: version (1 or 2)
9//!   Byte 1: space allocation time (1=early, 2=late, 3=incremental)
10//!   Byte 2: fill value write time (0=on_alloc, 1=never, 2=if_set)
11//!   Byte 3: fill value defined (0=undefined, non-zero=defined)
12//!   [if defined != 0]: u32 LE size + `size` bytes of fill data
13//!
14//! Version 3 (the version this crate writes):
15//!   Byte 0: version = 3
16//!   Byte 1: flags — bits 0-1 allocation time, bits 2-3 fill write time,
17//!           0x10 = fill value undefined, 0x20 = explicit fill value present
18//!   [if 0x20 set]: u32 LE size + `size` bytes of fill data
19//!
20//! libhdf5 2.0.0 still writes version 2 for this message, so decoding must
21//! handle every version even though encoding always emits version 3.
22
23use crate::format::{FormatError, FormatResult};
24
25const VERSION: u8 = 3;
26
27// Version-3 flags byte layout (H5Ofill.c).
28const FLAG_MASK_ALLOC: u8 = 0x03;
29const FLAG_MASK_FILL: u8 = 0x03;
30const FLAG_SHIFT_FILL: u8 = 2;
31const FLAG_UNDEFINED: u8 = 0x10;
32const FLAG_HAVE_VALUE: u8 = 0x20;
33const FLAGS_ALL: u8 =
34    FLAG_MASK_ALLOC | (FLAG_MASK_FILL << FLAG_SHIFT_FILL) | FLAG_UNDEFINED | FLAG_HAVE_VALUE;
35
36/// `H5D_ALLOC_TIME_EARLY` (`H5Dpublic.h`): space is allocated as soon as the
37/// dataset is created. `H5P__set_layout`'s default switch (H5Pdcpl.c:1864-
38/// 1877) gives this to a compact dataset — its storage *is* the object
39/// header, so it exists as soon as the dataset does.
40pub const ALLOC_TIME_EARLY: u8 = 1;
41
42/// `H5D_ALLOC_TIME_LATE`: space is allocated when data is first written.
43/// The default for a contiguous dataset (`H5P__set_layout`, H5Pdcpl.c:1870).
44pub const ALLOC_TIME_LATE: u8 = 2;
45
46/// `H5D_ALLOC_TIME_INCR`: space is allocated incrementally, as chunks (or
47/// virtual source datasets) are written. The default for a chunked or
48/// virtual dataset (`H5P__set_layout`, H5Pdcpl.c:1874-1877).
49pub const ALLOC_TIME_INCR: u8 = 3;
50
51/// `H5D_FILL_TIME_ALLOC` (`H5Dpublic.h`): fill at allocation regardless of
52/// whether a fill value was ever set — an unset value fills with the
53/// default (zeros), same as leaving newly allocated space untouched.
54pub const FILL_TIME_ALLOC: u8 = 0;
55
56/// `H5D_FILL_TIME_NEVER`: the fill value is never written into allocated
57/// storage. `H5D__chunk_lock`'s cache-miss path (H5Dchunk.c:4894) gates on
58/// it, and this crate's writer mirrors that gate at its own two eager-fill
59/// sites — the immediate tiling `set_dataset_fill_value` does for a
60/// compact/contiguous/implicit dataset, and the buffer a chunked partial
61/// write builds for a chunk touched for the first time — unlike a shrink's
62/// straddler refill (`H5D__chunk_prune_fill`), which fills unconditionally
63/// because it is repairing data about to become reachable again, not
64/// filling at allocation.
65pub const FILL_TIME_NEVER: u8 = 1;
66
67/// `H5D_FILL_TIME_IFSET`, the write time the default dataset creation
68/// property list carries (`H5D_CRT_FILL_TIME_DEF`, H5Dpkg.h) and therefore the
69/// one every dataset gets unless `H5Pset_fill_time` says otherwise.
70///
71/// It means "fill at allocation only when the user set a fill value", where
72/// `H5D_FILL_TIME_ALLOC` fills at allocation either way. The two differ only
73/// for a dataset with no fill value of its own, where `ALLOC` writes the
74/// default fill — zeros — and `IFSET` writes nothing into space that reads as
75/// zeros regardless. So this is what the writer's own allocation-time fills
76/// already do, whichever of the two the message claimed.
77pub const FILL_TIME_IFSET: u8 = 2;
78
79/// Fill value message payload.
80#[derive(Debug, Clone, PartialEq)]
81pub struct FillValueMessage {
82    /// Space allocation time: 1=early, 2=late, 3=incremental.
83    pub alloc_time: u8,
84    /// Fill value write time: 0=on alloc, 1=never, 2=if set.
85    pub fill_write_time: u8,
86    /// Fill value defined: 0=undefined, 1=default (zeros), 2=user-defined.
87    pub fill_defined: u8,
88    /// User-defined fill value data.  Present only when `fill_defined == 2`.
89    pub fill_value: Option<Vec<u8>>,
90}
91
92impl Default for FillValueMessage {
93    fn default() -> Self {
94        Self {
95            alloc_time: ALLOC_TIME_LATE,
96            fill_write_time: FILL_TIME_IFSET,
97            fill_defined: 1, // default value (zeros)
98            fill_value: None,
99        }
100    }
101}
102
103impl FillValueMessage {
104    /// A user-defined fill value.
105    pub fn with_value(data: Vec<u8>) -> Self {
106        Self {
107            alloc_time: ALLOC_TIME_LATE,
108            fill_write_time: FILL_TIME_IFSET,
109            fill_defined: 2,
110            fill_value: Some(data),
111        }
112    }
113
114    /// An undefined fill value (no fill is performed).
115    pub fn undefined() -> Self {
116        Self {
117            alloc_time: ALLOC_TIME_LATE,
118            fill_write_time: FILL_TIME_NEVER,
119            fill_defined: 0,
120            fill_value: None,
121        }
122    }
123
124    // ------------------------------------------------------------------ encode
125
126    /// Encode as a version-3 fill-value message (`H5O__fill_new_encode`).
127    pub fn encode(&self) -> Vec<u8> {
128        self.encode_for(crate::format::ObjectFormat::Modern)
129    }
130
131    /// Encode at the version a file of this `format` calls for
132    /// (`H5O__fill_new_encode`, H5Ofill.c:409).
133    ///
134    /// Version 2 spells out allocation time, write time and definedness as
135    /// three bytes instead of packing them into flags, and — the part a
136    /// version-3 reader must not assume — always writes the four-byte size
137    /// field when the value is defined, even when the size is zero. That
138    /// `defined = 1, size = 0` shape is what libhdf5 writes for the default
139    /// fill of a contiguous dataset in a classic file.
140    pub fn encode_for(&self, format: crate::format::ObjectFormat) -> Vec<u8> {
141        if format.fill_value_version() < VERSION {
142            let mut buf = Vec::with_capacity(12);
143            buf.push(format.fill_value_version());
144            buf.push(self.alloc_time);
145            buf.push(self.fill_write_time);
146            if self.fill_defined == 0 {
147                buf.push(0);
148                return buf;
149            }
150            buf.push(1);
151            let value = self.fill_value.as_deref().unwrap_or(&[]);
152            buf.extend_from_slice(&(value.len() as u32).to_le_bytes());
153            buf.extend_from_slice(value);
154            return buf;
155        }
156        let mut buf = Vec::with_capacity(10);
157        buf.push(VERSION);
158
159        let flags = (self.alloc_time & FLAG_MASK_ALLOC)
160            | ((self.fill_write_time & FLAG_MASK_FILL) << FLAG_SHIFT_FILL);
161
162        if self.fill_defined == 0 {
163            // Explicitly undefined: no value follows.
164            buf.push(flags | FLAG_UNDEFINED);
165        } else if let Some(data) = self.fill_value.as_ref().filter(|d| !d.is_empty()) {
166            // Explicit value present.
167            buf.push(flags | FLAG_HAVE_VALUE);
168            buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
169            buf.extend_from_slice(data);
170        } else {
171            // Defined, but no explicit value (default zero fill).
172            buf.push(flags);
173        }
174
175        buf
176    }
177
178    // ------------------------------------------------------------------ decode
179
180    /// Decode a fill-value message of version 1, 2, or 3.
181    pub fn decode(buf: &[u8]) -> FormatResult<(Self, usize)> {
182        if buf.is_empty() {
183            return Err(FormatError::BufferTooShort {
184                needed: 1,
185                available: 0,
186            });
187        }
188        match buf[0] {
189            1 | 2 => Self::decode_v1v2(buf),
190            3 => Self::decode_v3(buf),
191            other => Err(FormatError::InvalidVersion(other)),
192        }
193    }
194
195    /// Decode the version-1/2 layout (separate alloc/fill-time/defined bytes).
196    fn decode_v1v2(buf: &[u8]) -> FormatResult<(Self, usize)> {
197        if buf.len() < 4 {
198            return Err(FormatError::BufferTooShort {
199                needed: 4,
200                available: buf.len(),
201            });
202        }
203        let alloc_time = buf[1];
204        let fill_write_time = buf[2];
205        let defined_byte = buf[3];
206
207        let mut pos = 4;
208        let mut fill_value = None;
209        if defined_byte != 0 {
210            if buf.len() < pos + 4 {
211                return Err(FormatError::BufferTooShort {
212                    needed: pos + 4,
213                    available: buf.len(),
214                });
215            }
216            let size =
217                u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
218            pos += 4;
219            if size > 0 {
220                if buf.len() < pos + size {
221                    return Err(FormatError::BufferTooShort {
222                        needed: pos + size,
223                        available: buf.len(),
224                    });
225                }
226                fill_value = Some(buf[pos..pos + size].to_vec());
227                pos += size;
228            }
229        }
230
231        // Normalize `fill_defined` onto this crate's tri-state: an explicit
232        // non-empty value is "user-defined", any other defined message is
233        // "default zero fill", an undefined message is "undefined".
234        let fill_defined = if fill_value.is_some() {
235            2
236        } else if defined_byte != 0 {
237            1
238        } else {
239            0
240        };
241
242        Ok((
243            Self {
244                alloc_time,
245                fill_write_time,
246                fill_defined,
247                fill_value,
248            },
249            pos,
250        ))
251    }
252
253    /// Decode the version-3 layout (packed flags byte).
254    fn decode_v3(buf: &[u8]) -> FormatResult<(Self, usize)> {
255        if buf.len() < 2 {
256            return Err(FormatError::BufferTooShort {
257                needed: 2,
258                available: buf.len(),
259            });
260        }
261        let flags = buf[1];
262        if flags & !FLAGS_ALL != 0 {
263            return Err(FormatError::InvalidData(format!(
264                "unknown flags 0x{flags:02x} in version-3 fill-value message"
265            )));
266        }
267        let alloc_time = flags & FLAG_MASK_ALLOC;
268        let fill_write_time = (flags >> FLAG_SHIFT_FILL) & FLAG_MASK_FILL;
269
270        let mut pos = 2;
271        let (fill_defined, fill_value) = if flags & FLAG_UNDEFINED != 0 {
272            if flags & FLAG_HAVE_VALUE != 0 {
273                return Err(FormatError::InvalidData(
274                    "fill-value message sets both the undefined and have-value flags".into(),
275                ));
276            }
277            (0, None)
278        } else if flags & FLAG_HAVE_VALUE != 0 {
279            if buf.len() < pos + 4 {
280                return Err(FormatError::BufferTooShort {
281                    needed: pos + 4,
282                    available: buf.len(),
283                });
284            }
285            let size =
286                u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
287            pos += 4;
288            if buf.len() < pos + size {
289                return Err(FormatError::BufferTooShort {
290                    needed: pos + size,
291                    available: buf.len(),
292                });
293            }
294            let data = buf[pos..pos + size].to_vec();
295            pos += size;
296            (2, Some(data))
297        } else {
298            (1, None)
299        };
300
301        Ok((
302            Self {
303                alloc_time,
304                fill_write_time,
305                fill_defined,
306                fill_value,
307            },
308            pos,
309        ))
310    }
311}
312
313// ================================================================ tiling helper
314
315/// Build a `total`-byte buffer whose contents are `fill_value` tiled one
316/// element wide, or all zeros when `fill_value` is `None` or empty.
317///
318/// This is the single source of truth for materializing fill values:
319/// chunked reads use it to initialize output buffers, and the writer uses
320/// it to pad partial chunks so that unwritten elements read back as the
321/// fill value rather than zero.
322pub(crate) fn tiled_fill(total: usize, fill_value: Option<&[u8]>) -> Vec<u8> {
323    match fill_value {
324        Some(fv) if !fv.is_empty() && total > 0 => {
325            let mut buf = vec![0u8; total];
326            for slot in buf.chunks_mut(fv.len()) {
327                let n = slot.len().min(fv.len());
328                slot[..n].copy_from_slice(&fv[..n]);
329            }
330            buf
331        }
332        _ => vec![0u8; total],
333    }
334}
335
336/// Fallible variant of [`tiled_fill`] for reader paths.
337///
338/// `total` on a read path is derived from untrusted file fields (dataspace
339/// dimensions, element size). A crafted file can declare an absurd dataset
340/// size; allocating it with `vec![0u8; total]` aborts the process on
341/// allocation failure. This variant uses `try_reserve_exact`, returning a
342/// `TryReserveError` the caller can surface as a clean error instead.
343pub(crate) fn try_tiled_fill(
344    total: usize,
345    fill_value: Option<&[u8]>,
346) -> Result<Vec<u8>, std::collections::TryReserveError> {
347    let mut buf: Vec<u8> = Vec::new();
348    buf.try_reserve_exact(total)?;
349    buf.resize(total, 0);
350    if let Some(fv) = fill_value {
351        if !fv.is_empty() && total > 0 {
352            for slot in buf.chunks_mut(fv.len()) {
353                let n = slot.len().min(fv.len());
354                slot[..n].copy_from_slice(&fv[..n]);
355            }
356        }
357    }
358    Ok(buf)
359}
360
361// ======================================================================= tests
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn roundtrip_default() {
369        let msg = FillValueMessage::default();
370        let encoded = msg.encode();
371        // Version 3: version byte + flags byte, no value.
372        assert_eq!(encoded.len(), 2);
373        let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
374        assert_eq!(consumed, 2);
375        assert_eq!(decoded, msg);
376    }
377
378    #[test]
379    fn roundtrip_user_defined() {
380        let msg = FillValueMessage::with_value(vec![0xDE, 0xAD, 0xBE, 0xEF]);
381        let encoded = msg.encode();
382        // version + flags + u32 size + 4 data = 10
383        assert_eq!(encoded.len(), 10);
384        let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
385        assert_eq!(consumed, 10);
386        assert_eq!(decoded, msg);
387        assert_eq!(
388            decoded.fill_value.as_ref().unwrap(),
389            &vec![0xDE, 0xAD, 0xBE, 0xEF]
390        );
391    }
392
393    #[test]
394    fn roundtrip_undefined() {
395        let msg = FillValueMessage::undefined();
396        let encoded = msg.encode();
397        assert_eq!(encoded.len(), 2);
398        let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
399        assert_eq!(consumed, 2);
400        assert_eq!(decoded, msg);
401    }
402
403    #[test]
404    fn version_3_flags_byte_layout() {
405        // alloc_time=3 (bits 0-1), fill_write_time=2 (bits 2-3),
406        // explicit value present -> 0x20.
407        let msg = FillValueMessage {
408            alloc_time: 3,
409            fill_write_time: 2,
410            fill_defined: 2,
411            fill_value: Some(vec![0x01, 0x02]),
412        };
413        let encoded = msg.encode();
414        assert_eq!(encoded[0], 3);
415        assert_eq!(encoded[1], 0x03 | (0x02 << 2) | FLAG_HAVE_VALUE);
416        assert_eq!(&encoded[2..6], &2u32.to_le_bytes());
417        assert_eq!(&encoded[6..8], &[0x01, 0x02]);
418        assert_eq!(encoded.len(), 8);
419    }
420
421    #[test]
422    fn version_3_undefined_flag() {
423        let encoded = FillValueMessage::undefined().encode();
424        assert_eq!(encoded[1] & FLAG_UNDEFINED, FLAG_UNDEFINED);
425        assert_eq!(encoded[1] & FLAG_HAVE_VALUE, 0);
426    }
427
428    #[test]
429    fn empty_user_data_normalizes_to_default() {
430        // An empty explicit value is indistinguishable from default fill in
431        // the on-disk format, so it round-trips as `fill_defined == 1`.
432        let msg = FillValueMessage {
433            alloc_time: 1,
434            fill_write_time: 2,
435            fill_defined: 2,
436            fill_value: Some(vec![]),
437        };
438        let encoded = msg.encode();
439        assert_eq!(encoded.len(), 2);
440        let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
441        assert_eq!(consumed, 2);
442        assert_eq!(decoded.alloc_time, 1);
443        assert_eq!(decoded.fill_write_time, 2);
444        assert_eq!(decoded.fill_defined, 1);
445        assert_eq!(decoded.fill_value, None);
446    }
447
448    #[test]
449    fn decode_version_1_message() {
450        // Version-1 message with an explicit 4-byte value.
451        let buf = [1u8, 2, 0, 1, 4, 0, 0, 0, 0xAA, 0xBB, 0xCC, 0xDD];
452        let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
453        assert_eq!(consumed, 12);
454        assert_eq!(decoded.alloc_time, 2);
455        assert_eq!(decoded.fill_defined, 2);
456        assert_eq!(
457            decoded.fill_value.as_ref().unwrap(),
458            &vec![0xAA, 0xBB, 0xCC, 0xDD]
459        );
460    }
461
462    #[test]
463    fn decode_version_2_message_libhdf5_default() {
464        // The body libhdf5 2.0.0 actually writes: version 2, alloc=2,
465        // fill_time=2, defined=1, size=4.
466        let buf = [2u8, 2, 2, 1, 4, 0, 0, 0, 0x00, 0x00, 0x80, 0xBF];
467        let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
468        assert_eq!(consumed, 12);
469        assert_eq!(decoded.alloc_time, 2);
470        assert_eq!(decoded.fill_write_time, 2);
471        assert_eq!(decoded.fill_defined, 2);
472        assert_eq!(
473            decoded.fill_value.as_ref().unwrap(),
474            &vec![0x00, 0x00, 0x80, 0xBF]
475        );
476    }
477
478    #[test]
479    fn decode_version_2_defined_without_value() {
480        // Defined (byte != 0) but size 0: no explicit value.
481        let buf = [2u8, 2, 0, 1, 0, 0, 0, 0];
482        let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
483        assert_eq!(consumed, 8);
484        assert_eq!(decoded.fill_defined, 1);
485        assert_eq!(decoded.fill_value, None);
486    }
487
488    #[test]
489    fn decode_bad_version() {
490        for bad in [0u8, 4, 9] {
491            let buf = [bad, 0, 0, 0];
492            match FillValueMessage::decode(&buf).unwrap_err() {
493                FormatError::InvalidVersion(v) if v == bad => {}
494                other => panic!("unexpected error for version {bad}: {other:?}"),
495            }
496        }
497    }
498
499    #[test]
500    fn decode_buffer_too_short() {
501        // Only the version byte — a v3 message needs the flags byte too.
502        let buf = [3u8];
503        match FillValueMessage::decode(&buf).unwrap_err() {
504            FormatError::BufferTooShort { .. } => {}
505            other => panic!("unexpected error: {other:?}"),
506        }
507    }
508
509    #[test]
510    fn decode_v3_unknown_flag_rejected() {
511        // Bit 0x40 is not a defined flag.
512        let buf = [3u8, 0x40];
513        match FillValueMessage::decode(&buf).unwrap_err() {
514            FormatError::InvalidData(_) => {}
515            other => panic!("unexpected error: {other:?}"),
516        }
517    }
518
519    #[test]
520    fn decode_v3_truncated_size() {
521        // HAVE_VALUE flag set but the u32 size field is missing.
522        let buf = [3u8, FLAG_HAVE_VALUE, 0xFF];
523        match FillValueMessage::decode(&buf).unwrap_err() {
524            FormatError::BufferTooShort { .. } => {}
525            other => panic!("unexpected error: {other:?}"),
526        }
527    }
528
529    #[test]
530    fn decode_v3_truncated_data() {
531        // HAVE_VALUE, size=4, but only 2 bytes of data.
532        let buf = [3u8, FLAG_HAVE_VALUE, 4, 0, 0, 0, 0xAA, 0xBB];
533        match FillValueMessage::decode(&buf).unwrap_err() {
534            FormatError::BufferTooShort {
535                needed: 10,
536                available: 8,
537            } => {}
538            other => panic!("unexpected error: {other:?}"),
539        }
540    }
541
542    #[test]
543    fn version_byte() {
544        let encoded = FillValueMessage::default().encode();
545        assert_eq!(encoded[0], 3);
546    }
547
548    #[test]
549    fn tiled_fill_repeats_pattern() {
550        assert_eq!(tiled_fill(0, Some(&[1, 2])), Vec::<u8>::new());
551        assert_eq!(tiled_fill(6, None), vec![0u8; 6]);
552        assert_eq!(tiled_fill(6, Some(&[])), vec![0u8; 6]);
553        assert_eq!(
554            tiled_fill(6, Some(&[0xAB, 0xCD])),
555            vec![0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD]
556        );
557        // Partial tail when total is not a multiple of the pattern width.
558        assert_eq!(tiled_fill(5, Some(&[1, 2])), vec![1, 2, 1, 2, 1]);
559    }
560
561    /// The 8 bytes libhdf5 1.14.6 wrote for the default fill of a contiguous
562    /// dataset in a default (superblock-0) file: version 2, and the size field
563    /// present-but-zero that a version-3 reader never sees.
564    #[test]
565    fn a_legacy_fill_value_matches_the_bytes_libhdf5_wrote() {
566        let fv = FillValueMessage {
567            alloc_time: 2,
568            fill_write_time: 2,
569            fill_defined: 1,
570            fill_value: None,
571        };
572        let buf = fv.encode_for(crate::format::ObjectFormat::Legacy);
573        assert_eq!(buf, vec![0x02, 0x02, 0x02, 0x01, 0, 0, 0, 0]);
574        let (back, consumed) = FillValueMessage::decode(&buf).unwrap();
575        assert_eq!(consumed, buf.len());
576        assert_eq!(back, fv);
577    }
578
579    #[test]
580    fn a_legacy_user_fill_value_round_trips() {
581        let fv = FillValueMessage::with_value(vec![7, 0, 0, 0]);
582        let buf = fv.encode_for(crate::format::ObjectFormat::Legacy);
583        assert_eq!(&buf[..4], &[0x02, 0x02, 0x02, 0x01]);
584        let (back, _) = FillValueMessage::decode(&buf).unwrap();
585        assert_eq!(back, fv);
586    }
587
588    #[test]
589    fn a_legacy_undefined_fill_value_writes_no_size() {
590        let buf = FillValueMessage::undefined().encode_for(crate::format::ObjectFormat::Legacy);
591        assert_eq!(buf, vec![0x02, 0x02, 0x01, 0x00]);
592        let (back, _) = FillValueMessage::decode(&buf).unwrap();
593        assert_eq!(back, FillValueMessage::undefined());
594    }
595}