1#![allow(non_camel_case_types)]
7
8use clock_bound_shm::{ClockStatus, ShmError, ShmReader};
9use clock_bound_vmclock::shm::VMCLOCK_SHM_DEFAULT_PATH;
10use clock_bound_vmclock::VMClock;
11use core::ptr;
12use nix::sys::time::TimeSpec;
13use std::ffi::{c_char, CStr};
14
15#[repr(C)]
19pub enum clockbound_err_kind {
20 CLOCKBOUND_ERR_NONE,
21 CLOCKBOUND_ERR_SYSCALL,
22 CLOCKBOUND_ERR_SEGMENT_NOT_INITIALIZED,
23 CLOCKBOUND_ERR_SEGMENT_MALFORMED,
24 CLOCKBOUND_ERR_CAUSALITY_BREACH,
25 CLOCKBOUND_ERR_SEGMENT_VERSION_NOT_SUPPORTED,
26}
27
28#[repr(C)]
32pub struct clockbound_err {
33 pub kind: clockbound_err_kind,
34 pub errno: i32,
35 pub detail: *const c_char,
36}
37
38impl Default for clockbound_err {
39 fn default() -> Self {
40 clockbound_err {
41 kind: clockbound_err_kind::CLOCKBOUND_ERR_NONE,
42 errno: 0,
43 detail: ptr::null(),
44 }
45 }
46}
47
48impl From<ShmError> for clockbound_err {
49 fn from(value: ShmError) -> Self {
50 let kind = match value {
51 ShmError::SyscallError(_, _) => clockbound_err_kind::CLOCKBOUND_ERR_SYSCALL,
52 ShmError::SegmentNotInitialized => {
53 clockbound_err_kind::CLOCKBOUND_ERR_SEGMENT_NOT_INITIALIZED
54 }
55 ShmError::SegmentMalformed => clockbound_err_kind::CLOCKBOUND_ERR_SEGMENT_MALFORMED,
56 ShmError::CausalityBreach => clockbound_err_kind::CLOCKBOUND_ERR_CAUSALITY_BREACH,
57 ShmError::SegmentVersionNotSupported => {
58 clockbound_err_kind::CLOCKBOUND_ERR_SEGMENT_VERSION_NOT_SUPPORTED
59 }
60 };
61
62 let errno = match value {
63 ShmError::SyscallError(errno, _) => errno.0,
64 _ => 0,
65 };
66
67 let detail = match value {
68 ShmError::SyscallError(_, detail) => detail.as_ptr(),
69 _ => ptr::null(),
70 };
71
72 clockbound_err {
73 kind,
74 errno,
75 detail,
76 }
77 }
78}
79
80pub struct clockbound_ctx {
86 err: clockbound_err,
87 clockbound_shm_reader: Option<ShmReader>,
88 vmclock: Option<VMClock>,
89}
90
91impl clockbound_ctx {
92 fn now(&mut self) -> Result<(TimeSpec, TimeSpec, ClockStatus), ShmError> {
99 if let Some(ref mut clockbound_shm_reader) = self.clockbound_shm_reader {
100 match clockbound_shm_reader.snapshot() {
101 Ok(clockerrorbound_snapshot) => clockerrorbound_snapshot.now(),
102 Err(e) => Err(e),
103 }
104 } else if let Some(ref mut vmclock) = self.vmclock {
105 vmclock.now()
106 } else {
107 Err(ShmError::SegmentNotInitialized)
108 }
109 }
110}
111
112#[repr(C)]
115#[derive(Debug, PartialEq)]
116pub enum clockbound_clock_status {
117 CLOCKBOUND_STA_UNKNOWN,
118 CLOCKBOUND_STA_SYNCHRONIZED,
119 CLOCKBOUND_STA_FREE_RUNNING,
120 CLOCKBOUND_STA_DISRUPTED,
121}
122
123impl From<ClockStatus> for clockbound_clock_status {
124 fn from(value: ClockStatus) -> Self {
125 match value {
126 ClockStatus::Unknown => Self::CLOCKBOUND_STA_UNKNOWN,
127 ClockStatus::Synchronized => Self::CLOCKBOUND_STA_SYNCHRONIZED,
128 ClockStatus::FreeRunning => Self::CLOCKBOUND_STA_FREE_RUNNING,
129 ClockStatus::Disrupted => Self::CLOCKBOUND_STA_DISRUPTED,
130 }
131 }
132}
133
134#[repr(C)]
138pub struct clockbound_now_result {
139 earliest: libc::timespec,
140 latest: libc::timespec,
141 clock_status: clockbound_clock_status,
142}
143
144#[no_mangle]
154pub unsafe extern "C" fn clockbound_open(
155 clockbound_shm_path: *const c_char,
156 err: *mut clockbound_err,
157) -> *mut clockbound_ctx {
158 let clockbound_shm_path_cstr = CStr::from_ptr(clockbound_shm_path);
159 let clockbound_shm_path = clockbound_shm_path_cstr
160 .to_str()
161 .expect("Failed to convert ClockBound shared memory path to str");
162 let vmclock_shm_path = VMCLOCK_SHM_DEFAULT_PATH;
163
164 let vmclock: VMClock = match VMClock::new(clockbound_shm_path, vmclock_shm_path) {
165 Ok(vmclock) => vmclock,
166 Err(e) => {
167 if !err.is_null() {
168 err.write(e.into())
169 }
170 return ptr::null_mut();
171 }
172 };
173
174 let ctx = clockbound_ctx {
175 err: Default::default(),
176 clockbound_shm_reader: None,
177 vmclock: Some(vmclock),
178 };
179
180 return Box::leak(Box::new(ctx));
185}
186
187#[no_mangle]
196pub unsafe extern "C" fn clockbound_vmclock_open(
197 clockbound_shm_path: *const c_char,
198 vmclock_shm_path: *const c_char,
199 err: *mut clockbound_err,
200) -> *mut clockbound_ctx {
201 let clockbound_shm_path_cstr = CStr::from_ptr(clockbound_shm_path);
202 let clockbound_shm_path = clockbound_shm_path_cstr
203 .to_str()
204 .expect("Failed to convert ClockBound shared memory path to str");
205 let vmclock_shm_path_cstr = CStr::from_ptr(vmclock_shm_path);
206 let vmclock_shm_path = vmclock_shm_path_cstr
207 .to_str()
208 .expect("Failed to convert VMClock shared memory path to str");
209
210 let vmclock: VMClock = match VMClock::new(clockbound_shm_path, vmclock_shm_path) {
211 Ok(vmclock) => vmclock,
212 Err(e) => {
213 if !err.is_null() {
214 err.write(e.into())
215 }
216 return ptr::null_mut();
217 }
218 };
219
220 let ctx = clockbound_ctx {
221 err: Default::default(),
222 clockbound_shm_reader: None,
223 vmclock: Some(vmclock),
224 };
225
226 return Box::leak(Box::new(ctx));
231}
232
233#[no_mangle]
241pub unsafe extern "C" fn clockbound_close(ctx: *mut clockbound_ctx) -> *const clockbound_err {
242 std::mem::drop(Box::from_raw(ctx));
243 ptr::null()
244}
245
246#[inline]
256#[no_mangle]
257pub unsafe extern "C" fn clockbound_now(
258 ctx: *mut clockbound_ctx,
259 output: *mut clockbound_now_result,
260) -> *const clockbound_err {
261 let ctx = &mut *ctx;
262
263 let (earliest, latest, clock_status) = match ctx.now() {
264 Ok(now) => now,
265 Err(e) => {
266 ctx.err = e.into();
267 return &ctx.err;
268 }
269 };
270
271 output.write(clockbound_now_result {
272 earliest: *earliest.as_ref(),
273 latest: *latest.as_ref(),
274 clock_status: clock_status.into(),
275 });
276 ptr::null()
277}
278
279#[cfg(test)]
280mod t_ffi {
281 use super::*;
282 use clock_bound_shm::ClockErrorBound;
283 use byteorder::{LittleEndian, NativeEndian, WriteBytesExt};
284 use std::ffi::CString;
285 use std::fs::OpenOptions;
286 use std::io::Write;
287 use std::os::unix::ffi::OsStrExt;
288 use tempfile::NamedTempFile;
292
293 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 let ceb = ClockErrorBound::default();
305
306 let slice = unsafe {
309 ::core::slice::from_raw_parts(
310 (&ceb as *const ClockErrorBound) as *const u8,
311 ::core::mem::size_of::<ClockErrorBound>(),
312 )
313 };
314
315 $file
316 .write_u32::<NativeEndian>($magic_0)
317 .expect("Write failed magic_0");
318 $file
319 .write_u32::<NativeEndian>($magic_1)
320 .expect("Write failed magic_1");
321 $file
322 .write_u32::<NativeEndian>($segsize)
323 .expect("Write failed segsize");
324 $file
325 .write_u16::<NativeEndian>($version)
326 .expect("Write failed version");
327 $file
328 .write_u16::<NativeEndian>($generation)
329 .expect("Write failed generation");
330 $file
331 .write_all(slice)
332 .expect("Write failed ClockErrorBound");
333 $file.sync_all().expect("Sync to disk failed");
334 };
335 }
336
337 macro_rules! write_vmclock_shm_header {
338 ($file:ident,
339 $magic:literal,
340 $size:literal,
341 $version:literal,
342 $counter_id:literal,
343 $time_type:literal,
344 $seq_count:literal) => {
345 $file
346 .write_u32::<LittleEndian>($magic)
347 .expect("Write failed magic");
348 $file
349 .write_u32::<LittleEndian>($size)
350 .expect("Write failed size");
351 $file
352 .write_u16::<LittleEndian>($version)
353 .expect("Write failed version");
354 $file
355 .write_u8($counter_id)
356 .expect("Write failed counter_id");
357 $file.write_u8($time_type).expect("Write failed time_type");
358 $file
359 .write_u32::<LittleEndian>($seq_count)
360 .expect("Write failed seq_count");
361 $file.sync_all().expect("Sync to disk failed");
362 };
363 }
364
365 #[test]
367 fn test_clockbound_vmclock_open_sanity_check() {
368 let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
369 let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
370 let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
371 let mut clockbound_shm_file = OpenOptions::new()
372 .write(true)
373 .open(clockbound_shm_path)
374 .expect("open clockbound file failed");
375 write_clockbound_memory_segment!(clockbound_shm_file, 0x414D5A4E, 0x43420200, 800, 2, 10);
376
377 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
378 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
379 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
380 let mut vmclock_shm_file = OpenOptions::new()
381 .write(true)
382 .open(vmclock_shm_path)
383 .expect("open vmclock file failed");
384 write_vmclock_shm_header!(
385 vmclock_shm_file,
386 0x4B4C4356,
387 104_u32,
388 1_u16,
389 0_u8,
390 0_u8,
391 0_u32
392 );
393
394 let clockbound_path_cstring = CString::new(
395 std::path::Path::new(clockbound_shm_path)
396 .as_os_str()
397 .as_bytes(),
398 )
399 .unwrap();
400 let vmclock_path_cstring = CString::new(
401 std::path::Path::new(vmclock_shm_path)
402 .as_os_str()
403 .as_bytes(),
404 )
405 .unwrap();
406 unsafe {
407 let mut err: clockbound_err = Default::default();
408 let mut now_result: clockbound_now_result = std::mem::zeroed();
409
410 let ctx = clockbound_vmclock_open(
411 clockbound_path_cstring.as_ptr(),
412 vmclock_path_cstring.as_ptr(),
413 &mut err,
414 );
415 assert!(!ctx.is_null());
416
417 let errptr = clockbound_now(ctx, &mut now_result);
418 assert!(errptr.is_null());
419 assert_eq!(
420 now_result.clock_status,
421 clockbound_clock_status::CLOCKBOUND_STA_UNKNOWN
422 );
423
424 let errptr = clockbound_close(ctx);
425 assert!(errptr.is_null());
426 }
427 }
428
429 #[test]
433 fn test_clock_status_conversion() {
434 assert_eq!(
435 clockbound_clock_status::from(ClockStatus::Unknown),
436 clockbound_clock_status::CLOCKBOUND_STA_UNKNOWN
437 );
438 assert_eq!(
439 clockbound_clock_status::from(ClockStatus::Synchronized),
440 clockbound_clock_status::CLOCKBOUND_STA_SYNCHRONIZED
441 );
442 assert_eq!(
443 clockbound_clock_status::from(ClockStatus::FreeRunning),
444 clockbound_clock_status::CLOCKBOUND_STA_FREE_RUNNING
445 );
446 assert_eq!(
447 clockbound_clock_status::from(ClockStatus::Disrupted),
448 clockbound_clock_status::CLOCKBOUND_STA_DISRUPTED
449 );
450 }
451}