Skip to main content

rust_hdf5/format/messages/
mod_time.rs

1//! Object modification time message (`H5O_MTIME_NEW`, type 0x12).
2//!
3//! Where a version-1 object header records the one time it keeps. A version-2
4//! header has four time fields in its prefix instead and never carries this
5//! message — `H5O_touch_oh` branches on `oh->version == H5O_VERSION_1` and
6//! writes the prefix fields otherwise (H5Oint.c:1290-1345).
7//!
8//! The message is only ever *created* by a forced touch, which happens at one
9//! place in the library: `H5D__update_oh_info` calls `H5O_touch_oh(file, oh,
10//! true)` for a dataset whose file is below the v1.8 bound (H5Dint.c:1022-1026).
11//! Every other caller passes `force = false` (H5Oattribute.c:376, :909, :1158,
12//! :1517, :1603; H5Omessage.c:1193, :1799), which updates a message that is
13//! already there and creates nothing — which is why a version-1 group or
14//! committed datatype has no modification time even though its object header
15//! is tracking times.
16//!
17//! There is an older form of the same idea, `H5O_MTIME` (type 0x0E), which
18//! stored the time as a formatted date string; nothing since 1.6 writes it and
19//! this crate does not either.
20
21use crate::format::{FormatError, FormatResult};
22
23/// `H5O_MTIME_VERSION` (H5Omtime.c) — the only version this message has.
24const MTIME_VERSION: u8 = 1;
25
26/// Encoded size of the message: version, three reserved bytes, and a 32-bit
27/// time (`H5O__mtime_new_size`, H5Omtime.c returns 8).
28pub const MTIME_MESSAGE_SIZE: usize = 8;
29
30/// The seconds-since-the-epoch a modification time message carries.
31///
32/// A 32-bit count, as `H5O__mtime_new_encode` writes it (H5Omtime.c): the
33/// value saturates in 2106 whatever the platform's `time_t` is.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct ModificationTime(pub u32);
36
37impl ModificationTime {
38    /// The message body: version 1, three reserved zero bytes, then the time
39    /// (`H5O__mtime_new_encode`, H5Omtime.c).
40    pub fn encode(self) -> Vec<u8> {
41        let mut buf = Vec::with_capacity(MTIME_MESSAGE_SIZE);
42        buf.push(MTIME_VERSION);
43        buf.extend_from_slice(&[0, 0, 0]);
44        buf.extend_from_slice(&self.0.to_le_bytes());
45        buf
46    }
47
48    /// Read a message body (`H5O__mtime_new_decode`, H5Omtime.c).
49    pub fn decode(buf: &[u8]) -> FormatResult<Self> {
50        if buf.len() < MTIME_MESSAGE_SIZE {
51            return Err(FormatError::BufferTooShort {
52                needed: MTIME_MESSAGE_SIZE,
53                available: buf.len(),
54            });
55        }
56        if buf[0] != MTIME_VERSION {
57            return Err(FormatError::InvalidVersion(buf[0]));
58        }
59        Ok(Self(u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]])))
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn round_trips_through_the_eight_bytes_upstream_writes() {
69        let encoded = ModificationTime(0x5F00_1234).encode();
70        assert_eq!(encoded.len(), MTIME_MESSAGE_SIZE);
71        assert_eq!(&encoded[..4], &[1, 0, 0, 0], "version then three reserved");
72        assert_eq!(ModificationTime::decode(&encoded).unwrap().0, 0x5F00_1234);
73    }
74
75    /// `H5O__mtime_new_decode` fails the message rather than guessing when the
76    /// version is not the one version it has.
77    #[test]
78    fn a_foreign_version_is_refused() {
79        let mut encoded = ModificationTime(1).encode();
80        encoded[0] = 2;
81        assert!(ModificationTime::decode(&encoded).is_err());
82    }
83
84    #[test]
85    fn a_short_body_is_refused() {
86        assert!(ModificationTime::decode(&[1, 0, 0, 0, 0]).is_err());
87    }
88}