1pub 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 pub fn new() -> Result<ClockBoundClient, ClockBoundError> {
25 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 let vmclock = VMClock::new(CLOCKBOUND_SHM_DEFAULT_PATH, VMCLOCK_SHM_DEFAULT_PATH)?;
40
41 Ok(ClockBoundClient { vmclock })
42 }
43
44 pub fn new_with_path(clockbound_shm_path: &str) -> Result<ClockBoundClient, ClockBoundError> {
49 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 let vmclock = VMClock::new(clockbound_shm_path, VMCLOCK_SHM_DEFAULT_PATH)?;
62
63 Ok(ClockBoundClient { vmclock })
64 }
65
66 pub fn new_with_paths(
70 clockbound_shm_path: &str,
71 vmclock_shm_path: &str,
72 ) -> Result<ClockBoundClient, ClockBoundError> {
73 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 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#[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 use tempfile::NamedTempFile;
167
168 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 let ceb = ClockErrorBound::new(
180 TimeSpec::new(0, 0), TimeSpec::new(0, 0), 0, 0, 0, ClockStatus::Unknown, true, );
188
189 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 #[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 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 #[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 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 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 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 #[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), TimeSpec::new(0, 0), 0, 0, 0, ClockStatus::Unknown, true, );
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 let now_result = clockbound.now();
504 assert!(now_result.is_ok());
505
506 let ceb = ClockErrorBound::new(
509 TimeSpec::new(100, 0),
510 TimeSpec::new(10, 0),
511 0,
512 0,
513 1_000_000_000, ClockStatus::Synchronized,
515 true,
516 );
517 writer.write(&ceb);
518
519 let now_result = clockbound.now();
521 assert!(now_result.is_err());
522 }
523
524 #[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 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 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 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 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}