clock_bound_client/
lib.rs

1//! A client library to communicate with ClockBound daemon. This client library is written in pure Rust.
2//!
3pub use clock_bound_shm::ClockStatus;
4use clock_bound_shm::ShmError;
5pub use clock_bound_vmclock::shm::VMCLOCK_SHM_DEFAULT_PATH;
6use clock_bound_vmclock::VMClock;
7use errno::Errno;
8use nix::sys::time::TimeSpec;
9use std::path::Path;
10
11pub const CLOCKBOUND_SHM_DEFAULT_PATH: &str = "/var/run/clockbound/shm0";
12
13pub struct ClockBoundClient {
14    vmclock: VMClock,
15}
16
17impl ClockBoundClient {
18    /// Creates and returns a new ClockBoundClient.
19    ///
20    /// The creation process also initializes a shared memory reader
21    /// with the shared memory default path that is used by
22    /// the ClockBound daemon.
23    ///
24    pub fn new() -> Result<ClockBoundClient, ClockBoundError> {
25        // Validate that the default ClockBound shared memory path exists.
26        if !Path::new(CLOCKBOUND_SHM_DEFAULT_PATH).exists() {
27            let mut error = ClockBoundError::from(ShmError::SegmentNotInitialized);
28            error.detail = String::from(
29                "Default path for the ClockBound shared memory segment does not exist: ",
30            );
31            error.detail.push_str(CLOCKBOUND_SHM_DEFAULT_PATH);
32            return Err(error);
33        }
34
35        // Create a ClockBoundClient that makes use of the ClockBound daemon and VMClock.
36        //
37        // Clock disruption is expected to be handled by ClockBound daemon
38        // in coordination with this VMClock.
39        let vmclock = VMClock::new(CLOCKBOUND_SHM_DEFAULT_PATH, VMCLOCK_SHM_DEFAULT_PATH)?;
40
41        Ok(ClockBoundClient { vmclock })
42    }
43
44    /// Creates and returns a new ClockBoundClient, specifying a shared
45    /// memory path that is being used by the ClockBound daemon.
46    /// The VMClock will be accessed by reading the default VMClock
47    /// shared memory path.
48    pub fn new_with_path(clockbound_shm_path: &str) -> Result<ClockBoundClient, ClockBoundError> {
49        // Validate that the provided ClockBound shared memory path exists.
50        if !Path::new(clockbound_shm_path).exists() {
51            let mut error = ClockBoundError::from(ShmError::SegmentNotInitialized);
52            error.detail = String::from("Path in argument `clockbound_shm_path` does not exist: ");
53            error.detail.push_str(clockbound_shm_path);
54            return Err(error);
55        }
56
57        // Create a ClockBoundClient that makes use of the ClockBound daemon and VMClock.
58        //
59        // Clock disruption is expected to be handled by ClockBound daemon
60        // in coordination with this VMClock.
61        let vmclock = VMClock::new(clockbound_shm_path, VMCLOCK_SHM_DEFAULT_PATH)?;
62
63        Ok(ClockBoundClient { vmclock })
64    }
65
66    /// Creates and returns a new ClockBoundClient, specifying a shared
67    /// memory paths that are being used by the ClockBound daemon and by the VMClock,
68    /// respectively.
69    pub fn new_with_paths(
70        clockbound_shm_path: &str,
71        vmclock_shm_path: &str,
72    ) -> Result<ClockBoundClient, ClockBoundError> {
73        // Validate that the provided shared memory paths exists.
74        if !Path::new(clockbound_shm_path).exists() {
75            let mut error = ClockBoundError::from(ShmError::SegmentNotInitialized);
76            error.detail = String::from("Path in argument `clockbound_shm_path` does not exist: ");
77            error.detail.push_str(clockbound_shm_path);
78            return Err(error);
79        }
80
81        let vmclock = VMClock::new(clockbound_shm_path, vmclock_shm_path)?;
82
83        Ok(ClockBoundClient { vmclock })
84    }
85
86    /// Obtains the clock error bound and clock status at the current moment.
87    pub fn now(&mut self) -> Result<ClockBoundNowResult, ClockBoundError> {
88        let (earliest, latest, clock_status) = self.vmclock.now()?;
89
90        Ok(ClockBoundNowResult {
91            earliest,
92            latest,
93            clock_status,
94        })
95    }
96}
97
98#[derive(Hash, PartialEq, Eq, Clone, Debug)]
99pub enum ClockBoundErrorKind {
100    Syscall,
101    SegmentNotInitialized,
102    SegmentMalformed,
103    CausalityBreach,
104    SegmentVersionNotSupported,
105}
106
107#[derive(Debug)]
108pub struct ClockBoundError {
109    pub kind: ClockBoundErrorKind,
110    pub errno: Errno,
111    pub detail: String,
112}
113
114impl From<ShmError> for ClockBoundError {
115    fn from(value: ShmError) -> Self {
116        let kind = match value {
117            ShmError::SyscallError(_, _) => ClockBoundErrorKind::Syscall,
118            ShmError::SegmentNotInitialized => ClockBoundErrorKind::SegmentNotInitialized,
119            ShmError::SegmentMalformed => ClockBoundErrorKind::SegmentMalformed,
120            ShmError::CausalityBreach => ClockBoundErrorKind::CausalityBreach,
121            ShmError::SegmentVersionNotSupported => ClockBoundErrorKind::SegmentVersionNotSupported,
122        };
123
124        let errno = match value {
125            ShmError::SyscallError(errno, _) => errno,
126            _ => Errno(0),
127        };
128
129        let detail = match value {
130            ShmError::SyscallError(_, detail) => detail
131                .to_str()
132                .expect("Failed to convert CStr to str")
133                .to_owned(),
134            _ => String::new(),
135        };
136
137        ClockBoundError {
138            kind,
139            errno,
140            detail,
141        }
142    }
143}
144
145/// Result of the `ClockBoundClient::now()` function.
146#[derive(PartialEq, Clone, Debug)]
147pub struct ClockBoundNowResult {
148    pub earliest: TimeSpec,
149    pub latest: TimeSpec,
150    pub clock_status: ClockStatus,
151}
152
153#[cfg(test)]
154mod lib_tests {
155    use super::*;
156    use clock_bound_shm::{ClockErrorBound, ShmWrite, ShmWriter};
157    use clock_bound_vmclock::shm::VMClockClockStatus;
158    use byteorder::{NativeEndian, WriteBytesExt};
159    use std::ffi::CStr;
160    use std::fs::{File, OpenOptions};
161    use std::io::Write;
162    use std::path::Path;
163    /// We make use of tempfile::NamedTempFile to ensure that
164    /// local files that are created during a test get removed
165    /// afterwards.
166    use tempfile::NamedTempFile;
167
168    // TODO: this macro is defined in more than one crate, and the code needs to be refactored to
169    // remove duplication once most sections are implemented. For now, a bit of redundancy is ok to
170    // avoid having to think about dependencies between crates.
171    macro_rules! write_clockbound_memory_segment {
172        ($file:ident,
173         $magic_0:literal,
174         $magic_1:literal,
175         $segsize:literal,
176         $version:literal,
177         $generation:literal) => {
178            // Build a the bound on clock error data
179            let ceb = ClockErrorBound::new(
180                TimeSpec::new(0, 0),  // as_of
181                TimeSpec::new(0, 0),  // void_after
182                0,                    // bound_nsec
183                0,                    // disruption_marker
184                0,                    // max_drift_ppb
185                ClockStatus::Unknown, // clock_status
186                true,                 // clock_disruption_support_enabled
187            );
188
189            // Convert the ceb struct into a slice so we can write it all out, fairly magic.
190            // Definitely needs the #[repr(C)] layout.
191            let slice = unsafe {
192                ::core::slice::from_raw_parts(
193                    (&ceb as *const ClockErrorBound) as *const u8,
194                    ::core::mem::size_of::<ClockErrorBound>(),
195                )
196            };
197
198            $file
199                .write_u32::<NativeEndian>($magic_0)
200                .expect("Write failed magic_0");
201            $file
202                .write_u32::<NativeEndian>($magic_1)
203                .expect("Write failed magic_1");
204            $file
205                .write_u32::<NativeEndian>($segsize)
206                .expect("Write failed segsize");
207            $file
208                .write_u16::<NativeEndian>($version)
209                .expect("Write failed version");
210            $file
211                .write_u16::<NativeEndian>($generation)
212                .expect("Write failed generation");
213            $file
214                .write_all(slice)
215                .expect("Write failed ClockErrorBound");
216            $file.sync_all().expect("Sync to disk failed");
217        };
218    }
219
220    /// Test struct used to hold the expected fields in the VMClock shared memory segment.
221    #[repr(C)]
222    #[derive(Debug, Copy, Clone, PartialEq)]
223    struct VMClockContent {
224        magic: u32,
225        size: u32,
226        version: u16,
227        counter_id: u8,
228        time_type: u8,
229        seq_count: u32,
230        disruption_marker: u64,
231        flags: u64,
232        _padding: [u8; 2],
233        clock_status: VMClockClockStatus,
234        leap_second_smearing_hint: u8,
235        tai_offset_sec: i16,
236        leap_indicator: u8,
237        counter_period_shift: u8,
238        counter_value: u64,
239        counter_period_frac_sec: u64,
240        counter_period_esterror_rate_frac_sec: u64,
241        counter_period_maxerror_rate_frac_sec: u64,
242        time_sec: u64,
243        time_frac_sec: u64,
244        time_esterror_nanosec: u64,
245        time_maxerror_nanosec: u64,
246    }
247
248    fn write_vmclock_content(file: &mut File, vmclock_content: &VMClockContent) {
249        // Convert the VMClockShmBody struct into a slice so we can write it all out, fairly magic.
250        // Definitely needs the #[repr(C)] layout.
251        let slice = unsafe {
252            ::core::slice::from_raw_parts(
253                (vmclock_content as *const VMClockContent) as *const u8,
254                ::core::mem::size_of::<VMClockContent>(),
255            )
256        };
257
258        file.write_all(slice).expect("Write failed VMClockContent");
259        file.sync_all().expect("Sync to disk failed");
260    }
261
262    fn remove_path_if_exists(path_shm: &str) {
263        let path = Path::new(path_shm);
264        if path.exists() {
265            if path.is_dir() {
266                std::fs::remove_dir_all(path_shm).expect("failed to remove file");
267            } else {
268                std::fs::remove_file(path_shm).expect("failed to remove file");
269            }
270        }
271    }
272
273    #[test]
274    fn test_new_with_path_does_not_exist() {
275        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
276        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
277        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
278        remove_path_if_exists(clockbound_shm_path);
279        let result = ClockBoundClient::new_with_path(clockbound_shm_path);
280        assert!(result.is_err());
281    }
282
283    /// Assert that the shared memory segment can be open, read and and closed. Only a sanity test.
284    #[test]
285    fn test_new_with_paths_sanity_check() {
286        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
287        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
288        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
289        let mut clockbound_shm_file = OpenOptions::new()
290            .write(true)
291            .open(clockbound_shm_path)
292            .expect("open clockbound file failed");
293        write_clockbound_memory_segment!(clockbound_shm_file, 0x414D5A4E, 0x43420200, 800, 2, 10);
294
295        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
296        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
297        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
298        let mut vmclock_shm_file = OpenOptions::new()
299            .write(true)
300            .open(vmclock_shm_path)
301            .expect("open vmclock file failed");
302        let vmclock_content = VMClockContent {
303            magic: 0x4B4C4356,
304            size: 104_u32,
305            version: 1_u16,
306            counter_id: 1_u8,
307            time_type: 0_u8,
308            seq_count: 10_u32,
309            disruption_marker: 888888_u64,
310            flags: 0_u64,
311            _padding: [0x00, 0x00],
312            clock_status: VMClockClockStatus::Synchronized,
313            leap_second_smearing_hint: 0_u8,
314            tai_offset_sec: 0_i16,
315            leap_indicator: 0_u8,
316            counter_period_shift: 0_u8,
317            counter_value: 123456_u64,
318            counter_period_frac_sec: 0_u64,
319            counter_period_esterror_rate_frac_sec: 0_u64,
320            counter_period_maxerror_rate_frac_sec: 0_u64,
321            time_sec: 0_u64,
322            time_frac_sec: 0_u64,
323            time_esterror_nanosec: 0_u64,
324            time_maxerror_nanosec: 0_u64,
325        };
326        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
327
328        let mut clockbound =
329            match ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path) {
330                Ok(c) => c,
331                Err(e) => {
332                    eprintln!("{:?}", e);
333                    panic!("ClockBoundClient::new_with_paths() failed");
334                }
335            };
336
337        let now_result = match clockbound.now() {
338            Ok(result) => result,
339            Err(e) => {
340                eprintln!("{:?}", e);
341                panic!("ClockBoundClient::now() failed");
342            }
343        };
344
345        assert_eq!(now_result.clock_status, ClockStatus::Unknown);
346    }
347
348    #[test]
349    fn test_new_with_paths_does_not_exist() {
350        // Test both clockbound and vmclock files do not exist.
351        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
352        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
353        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
354        remove_path_if_exists(clockbound_shm_path);
355        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
356        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
357        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
358        remove_path_if_exists(vmclock_shm_path);
359        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
360        assert!(result.is_err());
361
362        // Test clockbound file exists but vmclock file does not exist.
363        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
364        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
365        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
366        let mut clockbound_shm_file = OpenOptions::new()
367            .write(true)
368            .open(clockbound_shm_path)
369            .expect("open clockbound file failed");
370        write_clockbound_memory_segment!(clockbound_shm_file, 0x414D5A4E, 0x43420200, 800, 2, 10);
371        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
372        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
373        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
374        remove_path_if_exists(vmclock_shm_path);
375        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
376        assert!(result.is_err());
377        remove_path_if_exists(clockbound_shm_path);
378
379        // Test clockbound file does not exist but vmclock file exists.
380        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
381        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
382        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
383        remove_path_if_exists(clockbound_shm_path);
384        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
385        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
386        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
387        let mut vmclock_shm_file = OpenOptions::new()
388            .write(true)
389            .open(vmclock_shm_path)
390            .expect("open vmclock file failed");
391        let vmclock_content = VMClockContent {
392            magic: 0x4B4C4356,
393            size: 104_u32,
394            version: 1_u16,
395            counter_id: 1_u8,
396            time_type: 0_u8,
397            seq_count: 10_u32,
398            disruption_marker: 888888_u64,
399            flags: 0_u64,
400            _padding: [0x00, 0x00],
401            clock_status: VMClockClockStatus::Synchronized,
402            leap_second_smearing_hint: 0_u8,
403            tai_offset_sec: 0_i16,
404            leap_indicator: 0_u8,
405            counter_period_shift: 0_u8,
406            counter_value: 123456_u64,
407            counter_period_frac_sec: 0_u64,
408            counter_period_esterror_rate_frac_sec: 0_u64,
409            counter_period_maxerror_rate_frac_sec: 0_u64,
410            time_sec: 0_u64,
411            time_frac_sec: 0_u64,
412            time_esterror_nanosec: 0_u64,
413            time_maxerror_nanosec: 0_u64,
414        };
415        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
416
417        let result = ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path);
418        assert!(result.is_err());
419    }
420
421    /// Assert that the new() runs and returns with a ClockBoundClient if the default shared
422    /// memory path exists, or with ClockBoundError if shared memory segment does not exist.
423    /// We avoid writing to the shared memory for the default shared memory segment path
424    /// because it is possible actual clients are relying on the ClockBound data at this location.
425    #[test]
426    fn test_new_sanity_check() {
427        let result = ClockBoundClient::new();
428        if Path::new(CLOCKBOUND_SHM_DEFAULT_PATH).exists() {
429            assert!(result.is_ok());
430        } else {
431            assert!(result.is_err());
432        }
433    }
434
435    #[test]
436    fn test_now_clock_error_bound_now_error() {
437        let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
438        let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
439        let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
440        let mut clockbound_shm_file = OpenOptions::new()
441            .write(true)
442            .open(clockbound_shm_path)
443            .expect("open clockbound file failed");
444        write_clockbound_memory_segment!(clockbound_shm_file, 0x414D5A4E, 0x43420200, 800, 2, 10);
445
446        let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
447        let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
448        let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
449        let mut vmclock_shm_file = OpenOptions::new()
450            .write(true)
451            .open(vmclock_shm_path)
452            .expect("open vmclock file failed");
453        let vmclock_content = VMClockContent {
454            magic: 0x4B4C4356,
455            size: 104_u32,
456            version: 1_u16,
457            counter_id: 1_u8,
458            time_type: 0_u8,
459            seq_count: 10_u32,
460            disruption_marker: 888888_u64,
461            flags: 0_u64,
462            _padding: [0x00, 0x00],
463            clock_status: VMClockClockStatus::Synchronized,
464            leap_second_smearing_hint: 0_u8,
465            tai_offset_sec: 0_i16,
466            leap_indicator: 0_u8,
467            counter_period_shift: 0_u8,
468            counter_value: 123456_u64,
469            counter_period_frac_sec: 0_u64,
470            counter_period_esterror_rate_frac_sec: 0_u64,
471            counter_period_maxerror_rate_frac_sec: 0_u64,
472            time_sec: 0_u64,
473            time_frac_sec: 0_u64,
474            time_esterror_nanosec: 0_u64,
475            time_maxerror_nanosec: 0_u64,
476        };
477        write_vmclock_content(&mut vmclock_shm_file, &vmclock_content);
478
479        let mut writer =
480            ShmWriter::new(Path::new(clockbound_shm_path)).expect("Failed to create a writer");
481
482        let ceb = ClockErrorBound::new(
483            TimeSpec::new(0, 0),  // as_of
484            TimeSpec::new(0, 0),  // void_after
485            0,                    // bound_nsec
486            0,                    // disruption_marker
487            0,                    // max_drift_ppb
488            ClockStatus::Unknown, // clock_status
489            true,                 // clock_disruption_support_enabled
490        );
491        writer.write(&ceb);
492
493        let mut clockbound =
494            match ClockBoundClient::new_with_paths(clockbound_shm_path, vmclock_shm_path) {
495                Ok(c) => c,
496                Err(e) => {
497                    eprintln!("{:?}", e);
498                    panic!("ClockBoundClient::new_with_paths() failed");
499                }
500            };
501
502        // Validate now() has a Result with a successful value.
503        let now_result = clockbound.now();
504        assert!(now_result.is_ok());
505
506        // Write out data with a extremely high max_drift_ppb value so that
507        // the client will have an error when calling now().
508        let ceb = ClockErrorBound::new(
509            TimeSpec::new(100, 0),
510            TimeSpec::new(10, 0),
511            0,
512            0,
513            1_000_000_000, // max_drift_ppb
514            ClockStatus::Synchronized,
515            true,
516        );
517        writer.write(&ceb);
518
519        // Validate now has Result with an error.
520        let now_result = clockbound.now();
521        assert!(now_result.is_err());
522    }
523
524    /// Test conversions from ShmError to ClockBoundError.
525
526    #[test]
527    fn test_shmerror_clockbounderror_conversion_syscallerror() {
528        let errno = Errno(1);
529        let detail: &CStr =
530            ::std::ffi::CStr::from_bytes_with_nul("test_detail\0".as_bytes()).unwrap();
531        let detail_str_slice: &str = detail.to_str().unwrap();
532        let detail_string: String = detail_str_slice.to_owned();
533        let shm_error = ShmError::SyscallError(errno, detail);
534        // Perform the conversion.
535        let clockbounderror = ClockBoundError::from(shm_error);
536        assert_eq!(ClockBoundErrorKind::Syscall, clockbounderror.kind);
537        assert_eq!(errno, clockbounderror.errno);
538        assert_eq!(detail_string, clockbounderror.detail);
539    }
540
541    #[test]
542    fn test_shmerror_clockbounderror_conversion_segmentnotinitialized() {
543        let shm_error = ShmError::SegmentNotInitialized;
544        // Perform the conversion.
545        let clockbounderror = ClockBoundError::from(shm_error);
546        assert_eq!(
547            ClockBoundErrorKind::SegmentNotInitialized,
548            clockbounderror.kind
549        );
550        assert_eq!(Errno(0), clockbounderror.errno);
551        assert_eq!(String::new(), clockbounderror.detail);
552    }
553
554    #[test]
555    fn test_shmerror_clockbounderror_conversion_segmentmalformed() {
556        let shm_error = ShmError::SegmentMalformed;
557        // Perform the conversion.
558        let clockbounderror = ClockBoundError::from(shm_error);
559        assert_eq!(ClockBoundErrorKind::SegmentMalformed, clockbounderror.kind);
560        assert_eq!(Errno(0), clockbounderror.errno);
561        assert_eq!(String::new(), clockbounderror.detail);
562    }
563
564    #[test]
565    fn test_shmerror_clockbounderror_conversion_causalitybreach() {
566        let shm_error = ShmError::CausalityBreach;
567        // Perform the conversion.
568        let clockbounderror = ClockBoundError::from(shm_error);
569        assert_eq!(ClockBoundErrorKind::CausalityBreach, clockbounderror.kind);
570        assert_eq!(Errno(0), clockbounderror.errno);
571        assert_eq!(String::new(), clockbounderror.detail);
572    }
573}