Skip to main content

clock_bound/
client.rs

1//! A client library to communicate with ClockBound daemon. This client library is written in pure Rust.
2//!
3pub use crate::shm::CLOCKBOUND_SHM_CLIENT_DEFAULT_PATH;
4pub use crate::shm::ClockStatus;
5use crate::shm::ShmReader;
6use crate::shm::{ClockBoundNowResult, ClockBoundSnapshot, ClockErrorBound, ShmError};
7pub use crate::vmclock::shm::VMCLOCK_SHM_DEFAULT_PATH;
8use crate::vmclock::shm_reader::VMClockShmReader;
9use errno::Errno;
10use std::ffi::CString;
11use std::path::Path;
12
13/// The `ClockBoundClient`
14///
15/// Use it to return current time, the clock error bound and clock status associated with it.
16pub struct ClockBoundClient {
17    clockbound_shm: ClockBoundSHM,
18    vmclock_shm: VMClockSHM,
19}
20
21impl ClockBoundClient {
22    /// Creates and returns a new `ClockBoundClient`.
23    ///
24    /// The client accesses two shared memory segments. One written to by the ClockBound daemon.
25    /// The second one by the VMClock device (if available).
26    ///
27    /// Use default paths to the two shared memory segments.
28    ///
29    /// # Errors
30    /// Returns [`ClockBoundError`] if the shared memory segments cannot be open or accessed.
31    pub fn new() -> Result<ClockBoundClient, ClockBoundError> {
32        Self::new_with_path(CLOCKBOUND_SHM_CLIENT_DEFAULT_PATH)
33    }
34
35    /// Creates and returns a new `ClockBoundClient`.
36    ///
37    /// The client accesses two shared memory segments. One written to by the ClockBound daemon.
38    /// The second one by the VMClock device (if available).
39    ///
40    /// Specify the path to the shared memory segment written to by the VMClock device.
41    /// Use the default paths to the ClockBound daemon shared memory segment.
42    ///
43    /// # Errors
44    /// Returns [`ClockBoundError`] if the shared memory segments cannot be open or accessed.
45    pub fn new_with_path(clockbound_shm_path: &str) -> Result<ClockBoundClient, ClockBoundError> {
46        Self::new_with_paths(clockbound_shm_path, VMCLOCK_SHM_DEFAULT_PATH)
47    }
48
49    /// Creates and returns a new `ClockBoundClient`.
50    ///
51    /// The client accesses two shared memory segments. One written to by the ClockBound daemon.
52    /// The second one by the VMClock device (if available).
53    ///
54    /// Explicitly specifies the paths to the two shared memory segments.
55    ///
56    /// # Errors
57    /// Returns [`ClockBoundError`] if the shared memory segments cannot be open or accessed.
58    pub fn new_with_paths(
59        clockbound_shm_path: &str,
60        vmclock_shm_path: &str,
61    ) -> Result<ClockBoundClient, ClockBoundError> {
62        // Create the clockbound shared memory accessor
63        let mut clockbound_shm = ClockBoundSHM::new(clockbound_shm_path)?;
64
65        // Read the segment to determine whether the daemon has been instructed to provide support
66        // for clock disruption. If true, the VMClock will be accessed.
67        let cb_snapshot = clockbound_shm.snapshot()?;
68
69        // Create the VMClock shared memory accessor
70        let vmclock_shm = VMClockSHM::new(
71            vmclock_shm_path,
72            cb_snapshot.clock_disruption_support_enabled(),
73        )?;
74
75        Ok(ClockBoundClient {
76            clockbound_shm,
77            vmclock_shm,
78        })
79    }
80
81    /// Read the current time, but with a bound on accuracy and a status.
82    ///
83    /// Returns a pair of (earliest, latest) timespec between which current time exists. The
84    /// interval width is twice the clock error bound (ceb) such that:
85    ///   (earliest, latest) = ((now - ceb), (now + ceb))
86    ///
87    /// The function also returns a clock status to assert that the clock is being synchronized, or
88    /// free-running, or ...
89    ///
90    /// # Errors
91    /// Returns [`ClockBoundError`] if the shared memory segments cannot be open or accessed.
92    pub fn now(&mut self) -> Result<ClockBoundNowResult, ClockBoundError> {
93        // The very first thing to do is to read from the ClockBound shared memory segment, take a
94        // snapshot to obtain the clock parameters and bound on error, and create a timestamp.
95        let cb_snap = self.clockbound_shm.snapshot()?;
96        let mut clock_bound_now_result = cb_snap.now()?;
97
98        // Now that the timestamp is created, check whether the clockbound daemon has been
99        // restarted and the option to enable the clock disruption support has been turned on. If
100        // so, need to create a reader for the VMClock device shared memory.
101        if self.vmclock_shm.vmclock_shm_reader.is_none()
102            && cb_snap.clock_disruption_support_enabled()
103        {
104            self.vmclock_shm.vmclock_shm_reader = Some(VMClockShmReader::new(
105                self.vmclock_shm.vmclock_shm_path.as_str(),
106            )?);
107        }
108
109        // Check whether the clock is disrupted. If the support to capture the clock disruption
110        // signal has been explicitly disabled, there is nothing to do. Otherwise, and if the
111        // VMClock shared memory is successfully read, this compares the value of the disruption
112        // marker between the clockbound daemon and the VMClock. If these disagree, a disruption
113        // has occured, and the clockbound daemon has not recovered from it yet.
114        let is_disrupted = match self.vmclock_shm.disruption_marker()? {
115            Some(marker) => marker != cb_snap.disruption_marker(),
116            None => false,
117        };
118
119        // If the clock is disrupted, overwrite the status
120        if is_disrupted {
121            clock_bound_now_result.clock_status = ClockStatus::Disrupted;
122        }
123
124        Ok(clock_bound_now_result)
125    }
126}
127
128/// `ClockBoundSHM` handles access to the shared memory segment populated by the ClockBound daemon.
129struct ClockBoundSHM {
130    #[expect(dead_code)]
131    clockbound_shm_path: String,
132    clockbound_shm_reader: ShmReader,
133}
134
135impl ClockBoundSHM {
136    /// Create a new [`ClockBoundSHM`] and open the shared memory segment for reading.
137    ///
138    /// # Errors
139    /// Returns a [`ClockErrorBound`] with an appropriate `Errno`. If the content of the segment is
140    /// uninitialized, unparseable, or otherwise malformed.
141    fn new(clockbound_shm_path: &str) -> Result<ClockBoundSHM, ClockBoundError> {
142        // Fail early if the provided shared memory path does not exist.
143        if !Path::new(clockbound_shm_path).exists() {
144            let detail = format!(
145                "Path to clockbound daemon shared memory segment does not exist: {clockbound_shm_path}"
146            );
147            let error = ClockBoundError {
148                kind: ClockBoundErrorKind::SegmentNotInitialized,
149                detail,
150                errno: Errno(0),
151            };
152            return Err(error);
153        }
154
155        let shm_path = CString::new(clockbound_shm_path).expect("CString::new failed");
156        let shm_reader = ShmReader::new(shm_path.as_c_str())?;
157
158        Ok(ClockBoundSHM {
159            clockbound_shm_path: String::from(clockbound_shm_path),
160            clockbound_shm_reader: shm_reader,
161        })
162    }
163
164    /// Returns a snapshot of the shared memory segment last populated by the ClockBound daemon.
165    fn snapshot(&mut self) -> Result<&ClockErrorBound, ShmError> {
166        self.clockbound_shm_reader.snapshot()
167    }
168}
169
170/// `VMClockSHM` handles access to the shared memory segment populated by the VMClock device.
171struct VMClockSHM {
172    vmclock_shm_path: String,
173    vmclock_shm_reader: Option<VMClockShmReader>,
174}
175
176impl VMClockSHM {
177    /// Create a new [`VMClockSHM`] and open the shared memory segment for reading, if needed.
178    ///
179    /// # Errors
180    /// Returns a [`ClockErrorBound`] with an appropriate `Errno`. If the content of the segment is
181    /// uninitialized, unparseable, or otherwise malformed.
182    fn new(
183        vmclock_shm_path: &str,
184        clock_disruption_support_enabled: bool,
185    ) -> Result<VMClockSHM, ClockBoundError> {
186        // Note that the support for clock disruption signal may be disabled on the ClockBound
187        // daemon, in which case, no reader is created.
188        let mut vmclock_shm_reader: Option<VMClockShmReader> = None;
189        if clock_disruption_support_enabled {
190            // Fail early if the provided shared memory path does not exist.
191            if !Path::new(vmclock_shm_path).exists() {
192                let detail = format!(
193                    "Path to VMClock device shared memory segment does not exist: {vmclock_shm_path}"
194                );
195                let error = ClockBoundError {
196                    kind: ClockBoundErrorKind::SegmentNotInitialized,
197                    detail,
198                    errno: Errno(0),
199                };
200                return Err(error);
201            }
202            vmclock_shm_reader = Some(VMClockShmReader::new(vmclock_shm_path)?);
203        }
204
205        Ok(VMClockSHM {
206            vmclock_shm_path: String::from(vmclock_shm_path),
207            vmclock_shm_reader,
208        })
209    }
210
211    /// Take a snapshot of the VMClock shared memory segment and extract the disruption marker.
212    ///
213    /// Note that None is returned if no SHM reader is present.
214    ///
215    /// # Errors
216    /// Returns a [`ShmError`] if the content of the segment is uninitialized, unparseable, or
217    /// otherwise malformed.
218    fn disruption_marker(&mut self) -> Result<Option<u64>, ShmError> {
219        if let Some(ref mut vmclock_shm_reader) = self.vmclock_shm_reader {
220            let snap = vmclock_shm_reader.snapshot()?;
221            return Ok(Some(snap.disruption_marker));
222        }
223
224        // The clock disruption support is not enabled
225        Ok(None)
226    }
227}
228
229#[derive(Debug)]
230pub struct ClockBoundError {
231    pub kind: ClockBoundErrorKind,
232    pub errno: Errno,
233    pub detail: String,
234}
235
236impl From<ShmError> for ClockBoundError {
237    fn from(value: ShmError) -> Self {
238        let (kind, detail, errno) = match value {
239            ShmError::SyscallError(detail, errno) => (ClockBoundErrorKind::Syscall, detail, errno),
240            ShmError::SegmentNotInitialized(detail) => {
241                (ClockBoundErrorKind::SegmentNotInitialized, detail, Errno(0))
242            }
243            ShmError::SegmentMalformed(detail) => {
244                (ClockBoundErrorKind::SegmentMalformed, detail, Errno(0))
245            }
246            ShmError::CausalityBreach(detail) => {
247                (ClockBoundErrorKind::CausalityBreach, detail, Errno(0))
248            }
249            ShmError::SegmentVersionNotSupported(detail) => (
250                ClockBoundErrorKind::SegmentVersionNotSupported,
251                detail,
252                Errno(0),
253            ),
254        };
255
256        ClockBoundError {
257            kind,
258            errno,
259            detail,
260        }
261    }
262}
263
264#[derive(Hash, PartialEq, Eq, Clone, Debug)]
265pub enum ClockBoundErrorKind {
266    // FIXME: the `detail` static CString is referenced on the Syscall variant. This is a temporary
267    // implementation until the FFI to C is changed to have the caller allocate memory for it.
268    Syscall,
269    SegmentNotInitialized,
270    SegmentMalformed,
271    CausalityBreach,
272    SegmentVersionNotSupported,
273}
274
275#[cfg(test)]
276mod lib_tests {
277    use super::*;
278    use crate::shm::{ClockErrorBound, ShmWrite, ShmWriter};
279    use crate::shm::{ClockErrorBoundGeneric, ClockErrorBoundLayoutVersion};
280    use crate::vmclock::shm::{VMClockClockStatus, VMClockShmBody};
281    use crate::vmclock::shm_writer::{VMClockShmWrite, VMClockShmWriter};
282
283    use byteorder::{NativeEndian, WriteBytesExt};
284    use nix::sys::time::TimeSpec;
285    use std::fs::{File, OpenOptions};
286    use std::io::Write;
287    use std::path::Path;
288    /// We make use of tempfile::NamedTempFile to ensure that
289    /// local files that are created during a test get removed
290    /// afterwards.
291    use tempfile::NamedTempFile;
292
293    // TODO: this macro is defined in more than one crate, and the code needs to be refactored to
294    // remove duplication once most sections are implemented. For now, a bit of redundancy is ok to
295    // avoid having to think about dependencies between crates.
296    macro_rules! write_clockbound_memory_segment {
297        ($file:ident,
298         $magic_0:literal,
299         $magic_1:literal,
300         $segsize:literal,
301         $version:literal,
302         $generation:literal) => {
303            // Build a default ClockErrorBound layout version 2
304            let ceb = ClockErrorBoundGeneric::builder()
305                .clock_disruption_support_enabled(true)
306                .build(ClockErrorBoundLayoutVersion::V2);
307
308            // Convert the ceb struct into a slice so we can write it all out, fairly magic.
309            // Definitely needs the #[repr(C)] layout.
310            let slice = unsafe {
311                ::core::slice::from_raw_parts(
312                    (&ceb as *const ClockErrorBound) as *const u8,
313                    ::core::mem::size_of::<ClockErrorBound>(),
314                )
315            };
316
317            $file
318                .write_u32::<NativeEndian>($magic_0)
319                .expect("Write failed magic_0");
320            $file
321                .write_u32::<NativeEndian>($magic_1)
322                .expect("Write failed magic_1");
323            $file
324                .write_u32::<NativeEndian>($segsize)
325                .expect("Write failed segsize");
326            $file
327                .write_u16::<NativeEndian>($version)
328                .expect("Write failed version");
329            $file
330                .write_u16::<NativeEndian>($generation)
331                .expect("Write failed generation");
332            $file
333                .write_all(slice)
334                .expect("Write failed ClockErrorBound");
335            $file.sync_all().expect("Sync to disk failed");
336        };
337    }
338
339    macro_rules! vmclockshmbody {
340        () => {
341            VMClockShmBody {
342                disruption_marker: 10,
343                flags: 0_u64,
344                _padding: [0x00, 0x00],
345                clock_status: VMClockClockStatus::Unknown,
346                leap_second_smearing_hint: 0,
347                tai_offset_sec: 37_i16,
348                leap_indicator: 0,
349                counter_period_shift: 0,
350                counter_value: 0,
351                counter_period_frac_sec: 0,
352                counter_period_esterror_rate_frac_sec: 0,
353                counter_period_maxerror_rate_frac_sec: 0,
354                time_sec: 0,
355                time_frac_sec: 0,
356                time_esterror_nanosec: 0,
357                time_maxerror_nanosec: 0,
358            }
359        };
360    }
361
362    /// Test struct used to hold the expected fields in the VMClock shared memory segment.
363    #[repr(C)]
364    #[derive(Debug, Copy, Clone, PartialEq)]
365    struct VMClockContent {
366        magic: u32,
367        size: u32,
368        version: u16,
369        counter_id: u8,
370        time_type: u8,
371        seq_count: u32,
372        disruption_marker: u64,
373        flags: u64,
374        _padding: [u8; 2],
375        clock_status: VMClockClockStatus,
376        leap_second_smearing_hint: u8,
377        tai_offset_sec: i16,
378        leap_indicator: u8,
379        counter_period_shift: u8,
380        counter_value: u64,
381        counter_period_frac_sec: u64,
382        counter_period_esterror_rate_frac_sec: u64,
383        counter_period_maxerror_rate_frac_sec: u64,
384        time_sec: u64,
385        time_frac_sec: u64,
386        time_esterror_nanosec: u64,
387        time_maxerror_nanosec: u64,
388    }
389
390    fn write_vmclock_content(file: &mut File, vmclock_content: &VMClockContent) {
391        // Convert the VMClockShmBody struct into a slice so we can write it all out, fairly magic.
392        // Definitely needs the #[repr(C)] layout.
393        let slice = unsafe {
394            ::core::slice::from_raw_parts(
395                (vmclock_content as *const VMClockContent) as *const u8,
396                ::core::mem::size_of::<VMClockContent>(),
397            )
398        };
399
400        file.write_all(slice).expect("Write failed VMClockContent");
401        file.sync_all().expect("Sync to disk failed");
402    }
403
404    /// Helper function to remove files created during unit tests.
405    fn remove_file_or_directory(path: &str) {
406        // Busy looping on deleting the previous file, good enough for unit test
407        let p = Path::new(&path);
408        while p.exists() {
409            if p.is_dir() {
410                std::fs::remove_dir_all(&path).expect("failed to remove file");
411            } else {
412                std::fs::remove_file(&path).expect("failed to remove file");
413            }
414        }
415    }
416
417    /// Assert that VMClock can be created successfully and the disruption marker is retrieved when
418    /// clock_disruption_support_enabled is true and a valid file exists at the vmclock_shm_path.
419    #[test]
420    fn test_vmclock_now_with_clock_disruption_support_enabled_success() {
421        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
422        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
423        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
424        remove_file_or_directory(&vmclock_shm_path);
425
426        // Create and write the VMClock memory segment.
427        let vmclock_shm_body = vmclockshmbody!();
428        let mut vmclock_shm_writer = VMClockShmWriter::new(Path::new(&vmclock_shm_path))
429            .expect("Failed to create a VMClockShmWriter");
430        vmclock_shm_writer.write(&vmclock_shm_body);
431
432        // Create the VMClock, and assert that the creation was successful.
433        let vmclock_new_result = VMClockSHM::new(&vmclock_shm_path, true);
434        match vmclock_new_result {
435            Ok(mut vmclock) => {
436                // Assert that now() does not return an error.
437                let marker_result = vmclock.disruption_marker();
438                assert!(marker_result.is_ok());
439                assert!(marker_result.unwrap() == Some(10_u64));
440            }
441            Err(_) => {
442                assert!(false);
443            }
444        }
445    }
446
447    /// Assert that VMClock will fail to be created when clock_disruption_support_enabled is true
448    /// and no file exists at the vmclock_shm_path.
449    #[test]
450    fn test_vmclock_now_with_clock_disruption_support_enabled_failure() {
451        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
452        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
453        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
454        remove_file_or_directory(&vmclock_shm_path);
455
456        // Create the VMClock, and assert that the creation was successful.
457        let vmclock_new_result = VMClockSHM::new(&vmclock_shm_path, true);
458        assert!(vmclock_new_result.is_err());
459    }
460
461    /// Assert that VMClock can be created successfully when clock_disruption_support_enabled is
462    /// false and no file exists at the vmclock_shm_path.
463    #[test]
464    fn test_vmclock_now_with_clock_disruption_support_not_enabled() {
465        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
466        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
467        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
468        remove_file_or_directory(&vmclock_shm_path);
469
470        // Create the VMClock, and assert that the creation was successful.
471        // There should be no error even though there is no file located at vmclock_shm_path.
472        let vmclock_new_result = VMClockSHM::new(&vmclock_shm_path, false);
473        match vmclock_new_result {
474            Ok(mut vmclock) => {
475                // Assert that now() does not return an error.
476                let marker_result = vmclock.disruption_marker();
477                assert!(marker_result.is_ok());
478                assert!(marker_result.unwrap() == None)
479            }
480            Err(_) => {
481                assert!(false);
482            }
483        }
484    }
485
486    #[test]
487    fn test_new_with_path_does_not_exist() {
488        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
489        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
490        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
491        remove_file_or_directory(clockbound_shm_path);
492        let result = ClockBoundClient::new_with_path(clockbound_shm_path);
493        assert!(result.is_err());
494    }
495
496    /// Assert that the shared memory segment can be open, read and and closed. Only a sanity test.
497    #[test]
498    fn test_new_with_paths_sanity_check() {
499        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
500        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
501        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
502        let mut clockbound_shm_file = OpenOptions::new()
503            .write(true)
504            .open(clockbound_shm_path)
505            .expect("open clockbound file failed");
506        write_clockbound_memory_segment!(
507            clockbound_shm_file,
508            0x414D5A4E,
509            0x43420200,
510            800,
511            0x0303,
512            10
513        );
514
515        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
516        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
517        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
518        let mut vmclock_shm_file = OpenOptions::new()
519            .write(true)
520            .open(vmclock_shm_path)
521            .expect("open vmclock file failed");
522        let vmclock_content = VMClockContent {
523            magic: 0x4B4C4356,
524            size: 104_u32,
525            version: 1_u16,
526            counter_id: 1_u8,
527            time_type: 0_u8,
528            seq_count: 10_u32,
529            disruption_marker: 888888_u64,
530            flags: 0_u64,
531            _padding: [0x00, 0x00],
532            clock_status: VMClockClockStatus::Synchronized,
533            leap_second_smearing_hint: 0_u8,
534            tai_offset_sec: 0_i16,
535            leap_indicator: 0_u8,
536            counter_period_shift: 0_u8,
537            counter_value: 123456_u64,
538            counter_period_frac_sec: 0_u64,
539            counter_period_esterror_rate_frac_sec: 0_u64,
540            counter_period_maxerror_rate_frac_sec: 0_u64,
541            time_sec: 0_u64,
542            time_frac_sec: 0_u64,
543            time_esterror_nanosec: 0_u64,
544            time_maxerror_nanosec: 0_u64,
545        };
546        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
547
548        let mut clockbound =
549            match ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path) {
550                Ok(c) => c,
551                Err(e) => {
552                    eprintln!("{:?}", e);
553                    panic!("ClockBoundClient::new_with_paths() failed");
554                }
555            };
556
557        let now_result = match clockbound.now() {
558            Ok(result) => result,
559            Err(e) => {
560                eprintln!("{:?}", e);
561                panic!("ClockBoundClient::now() failed");
562            }
563        };
564
565        assert_eq!(now_result.clock_status, ClockStatus::Disrupted);
566    }
567
568    #[test]
569    fn test_new_with_paths_does_not_exist() {
570        // Test both clockbound and vmclock files do not exist.
571        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
572        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
573        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
574        remove_file_or_directory(clockbound_shm_path);
575        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
576        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
577        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
578        remove_file_or_directory(vmclock_shm_path);
579        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
580        assert!(result.is_err());
581
582        // Test clockbound file exists but vmclock file does not exist.
583        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
584        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
585        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
586        let mut clockbound_shm_file = OpenOptions::new()
587            .write(true)
588            .open(clockbound_shm_path)
589            .expect("open clockbound file failed");
590        write_clockbound_memory_segment!(clockbound_shm_file, 0x414D5A4E, 0x43420200, 800, 2, 10);
591        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
592        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
593        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
594        remove_file_or_directory(vmclock_shm_path);
595        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
596        assert!(result.is_err());
597        remove_file_or_directory(clockbound_shm_path);
598
599        // Test clockbound file does not exist but vmclock file exists.
600        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
601        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
602        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
603        remove_file_or_directory(clockbound_shm_path);
604        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
605        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
606        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
607        let mut vmclock_shm_file = OpenOptions::new()
608            .write(true)
609            .open(vmclock_shm_path)
610            .expect("open vmclock file failed");
611        let vmclock_content = VMClockContent {
612            magic: 0x4B4C4356,
613            size: 104_u32,
614            version: 1_u16,
615            counter_id: 1_u8,
616            time_type: 0_u8,
617            seq_count: 10_u32,
618            disruption_marker: 888888_u64,
619            flags: 0_u64,
620            _padding: [0x00, 0x00],
621            clock_status: VMClockClockStatus::Synchronized,
622            leap_second_smearing_hint: 0_u8,
623            tai_offset_sec: 0_i16,
624            leap_indicator: 0_u8,
625            counter_period_shift: 0_u8,
626            counter_value: 123456_u64,
627            counter_period_frac_sec: 0_u64,
628            counter_period_esterror_rate_frac_sec: 0_u64,
629            counter_period_maxerror_rate_frac_sec: 0_u64,
630            time_sec: 0_u64,
631            time_frac_sec: 0_u64,
632            time_esterror_nanosec: 0_u64,
633            time_maxerror_nanosec: 0_u64,
634        };
635        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
636
637        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
638        assert!(result.is_err());
639    }
640
641    /// Assert that the new() runs and returns with a ClockBoundClient if the default shared
642    /// memory path exists, or with ClockBoundError if shared memory segment does not exist.
643    /// We avoid writing to the shared memory for the default shared memory segment path
644    /// because it is possible actual clients are relying on the ClockBound data at this location.
645    #[test]
646    #[ignore = "can fail if daemon has run previously with root privs"]
647    fn test_new_sanity_check() {
648        let result = ClockBoundClient::new();
649        if Path::new(CLOCKBOUND_SHM_CLIENT_DEFAULT_PATH).exists() {
650            assert!(result.is_ok());
651        } else {
652            assert!(result.is_err());
653        }
654    }
655
656    #[test]
657    // FIXME: this will fail until the writer is upgraded
658    // https://github.com/aws/private-clock-bound-staging/pull/158
659    #[ignore = "daemon version mismatch"]
660    fn test_now_clock_error_bound_now_error() {
661        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
662        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
663        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
664        let mut clockbound_shm_file = OpenOptions::new()
665            .write(true)
666            .open(clockbound_shm_path)
667            .expect("open clockbound file failed");
668        // Writing an older version of the shared memory segmeth, that the writer should overwrite
669        write_clockbound_memory_segment!(
670            clockbound_shm_file,
671            0x414D5A4E,
672            0x43420200,
673            800,
674            0x0002,
675            10
676        );
677
678        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
679        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
680        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
681        let mut vmclock_shm_file = OpenOptions::new()
682            .write(true)
683            .open(vmclock_shm_path)
684            .expect("open vmclock file failed");
685        let vmclock_content = VMClockContent {
686            magic: 0x4B4C4356,
687            size: 104_u32,
688            version: 1_u16,
689            counter_id: 1_u8,
690            time_type: 0_u8,
691            seq_count: 10_u32,
692            disruption_marker: 888888_u64,
693            flags: 0_u64,
694            _padding: [0x00, 0x00],
695            clock_status: VMClockClockStatus::Synchronized,
696            leap_second_smearing_hint: 0_u8,
697            tai_offset_sec: 0_i16,
698            leap_indicator: 0_u8,
699            counter_period_shift: 0_u8,
700            counter_value: 123456_u64,
701            counter_period_frac_sec: 0_u64,
702            counter_period_esterror_rate_frac_sec: 0_u64,
703            counter_period_maxerror_rate_frac_sec: 0_u64,
704            time_sec: 0_u64,
705            time_frac_sec: 0_u64,
706            time_esterror_nanosec: 0_u64,
707            time_maxerror_nanosec: 0_u64,
708        };
709        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
710
711        let mut writer = ShmWriter::new(
712            Path::new(clockbound_shm_path),
713            ClockErrorBoundLayoutVersion::V2,
714            ClockErrorBoundLayoutVersion::V2,
715        )
716        .expect("Failed to create a writer");
717
718        let ceb = ClockErrorBoundGeneric::builder().build(ClockErrorBoundLayoutVersion::V3);
719        writer.write(&ceb);
720
721        let mut clockbound =
722            match ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path) {
723                Ok(c) => c,
724                Err(e) => {
725                    eprintln!("{:?}", e);
726                    panic!("ClockBoundClient::new_with_paths() failed");
727                }
728            };
729
730        // Validate now() has a Result with a successful value.
731        let now_result = clockbound.now();
732        assert!(now_result.is_ok());
733
734        // Write out data with a extremely high max_drift_ppb value so that
735        // the client will have an error when calling now().
736        let ceb = ClockErrorBoundGeneric::builder()
737            .as_of(TimeSpec::new(100, 0))
738            .void_after(TimeSpec::new(10, 0))
739            .max_drift_ppb(1_000_000_000)
740            .clock_status(ClockStatus::Synchronized)
741            .clock_disruption_support_enabled(true)
742            .build(ClockErrorBoundLayoutVersion::V3);
743        writer.write(&ceb);
744
745        // Validate now has Result with an error.
746        let now_result = clockbound.now();
747        assert!(now_result.is_err());
748    }
749
750    /// Test conversions from ShmError to ClockBoundError.
751
752    #[test]
753    fn test_shmerror_clockbounderror_conversion_syscallerror() {
754        let errno = Errno(1);
755        let detail = String::from("test detail");
756        let shm_error = ShmError::SyscallError(detail.clone(), errno);
757        // Perform the conversion.
758        let clockbounderror = ClockBoundError::from(shm_error);
759        assert_eq!(ClockBoundErrorKind::Syscall, clockbounderror.kind);
760        assert_eq!(errno, clockbounderror.errno);
761        assert_eq!(detail, clockbounderror.detail);
762    }
763
764    #[test]
765    fn test_shmerror_clockbounderror_conversion_segmentnotinitialized() {
766        let detail = String::from("test detail");
767        let shm_error = ShmError::SegmentNotInitialized(detail.clone());
768        // Perform the conversion.
769        let clockbounderror = ClockBoundError::from(shm_error);
770        assert_eq!(
771            ClockBoundErrorKind::SegmentNotInitialized,
772            clockbounderror.kind
773        );
774        assert_eq!(Errno(0), clockbounderror.errno);
775        assert_eq!(detail, clockbounderror.detail);
776    }
777
778    #[test]
779    fn test_shmerror_clockbounderror_conversion_segmentmalformed() {
780        let detail = String::from("test detail");
781        let shm_error = ShmError::SegmentMalformed(detail.clone());
782        // Perform the conversion.
783        let clockbounderror = ClockBoundError::from(shm_error);
784        assert_eq!(ClockBoundErrorKind::SegmentMalformed, clockbounderror.kind);
785        assert_eq!(Errno(0), clockbounderror.errno);
786        assert_eq!(detail, clockbounderror.detail);
787    }
788
789    #[test]
790    fn test_shmerror_clockbounderror_conversion_causalitybreach() {
791        let detail = String::from("test detail");
792        let shm_error = ShmError::CausalityBreach(detail.clone());
793        // Perform the conversion.
794        let clockbounderror = ClockBoundError::from(shm_error);
795        assert_eq!(ClockBoundErrorKind::CausalityBreach, clockbounderror.kind);
796        assert_eq!(Errno(0), clockbounderror.errno);
797        assert_eq!(detail, clockbounderror.detail);
798    }
799}