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}