clock_bound_vmclock/
lib.rs

1use std::ffi::CString;
2use tracing::debug;
3
4use crate::shm_reader::VMClockShmReader;
5use clock_bound_shm::{ClockStatus, ShmError, ShmReader};
6use nix::sys::time::TimeSpec;
7
8pub mod shm;
9pub mod shm_reader;
10pub mod shm_writer;
11
12/// VMClock provides the following capabilities:
13///
14/// - Error-bounded timestamps obtained from ClockBound daemon.
15/// - Clock disruption signaling via the VMClock.
16pub struct VMClock {
17    clockbound_shm_reader: ShmReader,
18    vmclock_shm_path: String,
19    vmclock_shm_reader: Option<VMClockShmReader>,
20}
21
22impl VMClock {
23    /// Open the VMClock shared memory segment and the ClockBound shared memory segment for reading.
24    ///
25    /// On error, returns an appropriate `Errno`. If the content of the segment
26    /// is uninitialized, unparseable, or otherwise malformed, EPROTO will be
27    /// returned.
28    pub fn new(clockbound_shm_path: &str, vmclock_shm_path: &str) -> Result<VMClock, ShmError> {
29        let clockbound_shm_path = CString::new(clockbound_shm_path).expect("CString::new failed");
30        let mut clockbound_shm_reader = ShmReader::new(clockbound_shm_path.as_c_str())?;
31        let clockbound_snapshot = clockbound_shm_reader.snapshot()?;
32
33        let mut vmclock_shm_reader: Option<VMClockShmReader> = None;
34        if clockbound_snapshot.clock_disruption_support_enabled {
35            vmclock_shm_reader = Some(VMClockShmReader::new(vmclock_shm_path)?);
36        }
37
38        Ok(VMClock {
39            clockbound_shm_reader,
40            vmclock_shm_path: String::from(vmclock_shm_path),
41            vmclock_shm_reader,
42        })
43    }
44
45    /// The VMClock equivalent of clock_gettime(), but with bound on accuracy.
46    ///
47    /// Returns a pair of (earliest, latest) timespec between which current time exists. The
48    /// interval width is twice the clock error bound (ceb) such that:
49    ///   (earliest, latest) = ((now - ceb), (now + ceb))
50    /// The function also returns a clock status to assert that the clock is being synchronized, or
51    /// free-running, or ...
52    pub fn now(&mut self) -> Result<(TimeSpec, TimeSpec, ClockStatus), ShmError> {
53        // Read from the ClockBound shared memory segment.
54        let clockbound_snapshot = self.clockbound_shm_reader.snapshot()?;
55
56        if self.vmclock_shm_reader.is_none() && clockbound_snapshot.clock_disruption_support_enabled
57        {
58            self.vmclock_shm_reader = Some(VMClockShmReader::new(self.vmclock_shm_path.as_str())?);
59        }
60
61        let (earliest, latest, clock_status) = clockbound_snapshot.now()?;
62
63        if clockbound_snapshot.clock_disruption_support_enabled {
64            if let Some(ref mut vmclock_shm_reader) = self.vmclock_shm_reader {
65                // Read from the VMClock shared memory segment.
66                let vmclock_snapshot = vmclock_shm_reader.snapshot()?;
67
68                // Comparing the disruption marker between the VMClock snapshot and the
69                // ClockBound snapshot will tell us if the clock status provided by the
70                // ClockBound daemon is trustworthy.
71                debug!("clock_status: {:?}, vmclock_snapshot.disruption_marker: {:?}, clockbound_snapshot.disruption_marker: {:?}",
72                    clock_status, vmclock_snapshot.disruption_marker,
73                    clockbound_snapshot.disruption_marker);
74
75                if vmclock_snapshot.disruption_marker == clockbound_snapshot.disruption_marker {
76                    // ClockBound's shared memory segment has the latest clock disruption status from
77                    // VMClock and this means the clock status here can be trusted.
78                    return Ok((earliest, latest, clock_status));
79                } else {
80                    // ClockBound has stale clock disruption status and it is not up-to-date with
81                    // VMClock.
82
83                    // Override the clock disruption status with ClockStatus::Unknown until
84                    // ClockBound daemon is able to pick up the latest clock disruption status
85                    // from VMClock.
86                    return Ok((earliest, latest, ClockStatus::Unknown));
87                }
88            }
89        }
90
91        debug!("clock_status: {:?}", clock_status);
92        Ok((earliest, latest, clock_status))
93    }
94}
95
96#[cfg(test)]
97mod t_lib {
98    use super::*;
99
100    use clock_bound_shm::{ClockErrorBound, ShmWrite, ShmWriter};
101    use std::path::Path;
102
103    use crate::shm::{VMClockClockStatus, VMClockShmBody};
104    use crate::shm_writer::{VMClockShmWrite, VMClockShmWriter};
105    /// We make use of tempfile::NamedTempFile to ensure that
106    /// local files that are created during a test get removed
107    /// afterwards.
108    use tempfile::NamedTempFile;
109
110    macro_rules! vmclockshmbody {
111        () => {
112            VMClockShmBody {
113                disruption_marker: 10,
114                flags: 0_u64,
115                _padding: [0x00, 0x00],
116                clock_status: VMClockClockStatus::Unknown,
117                leap_second_smearing_hint: 0,
118                tai_offset_sec: 37_i16,
119                leap_indicator: 0,
120                counter_period_shift: 0,
121                counter_value: 0,
122                counter_period_frac_sec: 0,
123                counter_period_esterror_rate_frac_sec: 0,
124                counter_period_maxerror_rate_frac_sec: 0,
125                time_sec: 0,
126                time_frac_sec: 0,
127                time_esterror_nanosec: 0,
128                time_maxerror_nanosec: 0,
129            }
130        };
131    }
132
133    /// Helper function to remove files created during unit tests.
134    fn remove_file_or_directory(path: &str) {
135        // Busy looping on deleting the previous file, good enough for unit test
136        let p = Path::new(&path);
137        while p.exists() {
138            if p.is_dir() {
139                std::fs::remove_dir_all(&path).expect("failed to remove file");
140            } else {
141                std::fs::remove_file(&path).expect("failed to remove file");
142            }
143        }
144    }
145
146    /// Assert that VMClock can be created successfully and now() function successful when
147    /// clock_disruption_support_enabled is true and a valid file exists at the vmclock_shm_path.
148    #[test]
149    fn test_vmclock_now_with_clock_disruption_support_enabled_success() {
150        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
151        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
152        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
153        remove_file_or_directory(&clockbound_shm_path);
154        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
155        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
156        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
157        remove_file_or_directory(&vmclock_shm_path);
158
159        // Create and wipe the ClockBound memory segment.
160        let ceb = ClockErrorBound::new(
161            TimeSpec::new(1, 2),       // as_of
162            TimeSpec::new(3, 4),       // void_after
163            123,                       // bound_nsec
164            10,                        // disruption_marker
165            100,                       // max_drift_ppb
166            ClockStatus::Synchronized, // clock_status
167            true,                      // clock_disruption_support_enabled
168        );
169
170        let mut clockbound_shm_writer =
171            ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
172        clockbound_shm_writer.write(&ceb);
173
174        // Create and write the VMClock memory segment.
175        let vmclock_shm_body = vmclockshmbody!();
176        let mut vmclock_shm_writer = VMClockShmWriter::new(Path::new(&vmclock_shm_path))
177            .expect("Failed to create a VMClockShmWriter");
178        vmclock_shm_writer.write(&vmclock_shm_body);
179
180        // Create the VMClock, and assert that the creation was successful.
181        let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
182        match vmclock_new_result {
183            Ok(mut vmclock) => {
184                // Assert that now() does not return an error.
185                let now_result = vmclock.now();
186                assert!(now_result.is_ok());
187            }
188            Err(_) => {
189                assert!(false);
190            }
191        }
192    }
193
194    /// Assert that VMClock will fail to be created when clock_disruption_support_enabled
195    /// is true and no file exists at the vmclock_shm_path.
196    #[test]
197    fn test_vmclock_now_with_clock_disruption_support_enabled_failure() {
198        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
199        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
200        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
201        remove_file_or_directory(&clockbound_shm_path);
202        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
203        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
204        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
205        remove_file_or_directory(&vmclock_shm_path);
206
207        // Create and wipe the ClockBound memory segment.
208        let ceb = ClockErrorBound::new(
209            TimeSpec::new(1, 2),       // as_of
210            TimeSpec::new(3, 4),       // void_after
211            123,                       // bound_nsec
212            10,                        // disruption_marker
213            100,                       // max_drift_ppb
214            ClockStatus::Synchronized, // clock_status
215            true,                      // clock_disruption_support_enabled
216        );
217
218        let mut clockbound_shm_writer =
219            ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
220        clockbound_shm_writer.write(&ceb);
221
222        // Create the VMClock, and assert that the creation was successful.
223        let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
224        assert!(vmclock_new_result.is_err());
225    }
226
227    /// Assert that VMClock can be created successfully and now() runs successfully
228    /// when clock_disruption_support_enabled is false and no file exists at the vmclock_shm_path.
229    #[test]
230    fn test_vmclock_now_with_clock_disruption_support_not_enabled() {
231        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
232        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
233        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
234        remove_file_or_directory(&clockbound_shm_path);
235        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
236        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
237        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
238        remove_file_or_directory(&vmclock_shm_path);
239
240        // Create and wipe the ClockBound memory segment.
241        let ceb = ClockErrorBound::new(
242            TimeSpec::new(1, 2),       // as_of
243            TimeSpec::new(3, 4),       // void_after
244            123,                       // bound_nsec
245            10,                        // disruption_marker
246            100,                       // max_drift_ppb
247            ClockStatus::Synchronized, // clock_status
248            false,                     // clock_disruption_support_enabled
249        );
250
251        let mut clockbound_shm_writer =
252            ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
253        clockbound_shm_writer.write(&ceb);
254
255        // Create the VMClock, and assert that the creation was successful.
256        // There should be no error even though there is no file located at vmclock_shm_path.
257        let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
258        match vmclock_new_result {
259            Ok(mut vmclock) => {
260                // Assert that now() does not return an error.
261                let now_result = vmclock.now();
262                assert!(now_result.is_ok());
263            }
264            Err(_) => {
265                assert!(false);
266            }
267        }
268    }
269}