clockbound/
lib.rs

1//! ClockBound Foreign Function Interface
2//!
3//! This crate implements the FFI for ClockBound and builds into the libclockbound library.
4
5// Align with C naming conventions
6#![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/// Error kind exposed over the FFI.
16///
17/// These have to match the C header definition.
18#[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/// Error struct exposed over the FFI.
29///
30/// The definition has to match the C header definition.
31#[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
80/// The clockbound context given to the caller.
81///
82/// The members of this structure are private to keep this structure opaque. The caller is not
83/// meant to rely on the content of this structure, only pass it back to flex the clockbound API.
84/// This allow to extend the context with extra information if needed.
85pub struct clockbound_ctx {
86    err: clockbound_err,
87    clockbound_shm_reader: Option<ShmReader>,
88    vmclock: Option<VMClock>,
89}
90
91impl clockbound_ctx {
92    /// Obtain error-bounded timestamps and the ClockStatus.
93    ///
94    /// The result on success is a tuple of:
95    /// - TimeSpec: earliest timestamp.
96    /// - TimeSpec: latest timestamp.
97    /// - ClockStatus: Status of the clock.
98    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/// Clock status exposed over the FFI.
113///.
114#[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/// Result of the `clockbound_now()` function exposed over the FFI.
135///
136/// These have to match the C header definition.
137#[repr(C)]
138pub struct clockbound_now_result {
139    earliest: libc::timespec,
140    latest: libc::timespec,
141    clock_status: clockbound_clock_status,
142}
143
144/// Open and create a reader to the Clockbound shared memory segment.
145///
146/// Create a ShmReader pointing at the path passed to this call, and package it (and any other side
147/// information) into a `clockbound_ctx`. A reference to the context is passed back to the C
148/// caller, and needs to live beyond the scope of this function.
149///
150/// # Safety
151/// Rely on the caller to pass valid pointers.
152///
153#[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 the clockbound_ctx.
181    //
182    // The caller is responsible for calling clockbound_close() with this context which will
183    // perform memory clean-up.
184    return Box::leak(Box::new(ctx));
185}
186
187/// Open and create a reader to the Clockbound shared memory segment and the VMClock shared memory segment.
188///
189/// Create a VMClock pointing at the paths passed to this call, and package it (and any other side
190/// information) into a `clockbound_ctx`. A reference to the context is passed back to the C
191/// caller, and needs to live beyond the scope of this function.
192///
193/// # Safety
194/// Rely on the caller to pass valid pointers.
195#[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 the clockbound_ctx.
227    //
228    // The caller is responsible for calling clockbound_close() with this context which will
229    // perform memory clean-up.
230    return Box::leak(Box::new(ctx));
231}
232
233/// Close the clockbound context.
234///
235/// Effectively unmap the shared memory segment and drop the ShmReader.
236///
237/// # Safety
238///
239/// Rely on the caller to pass valid pointers.
240#[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/// Call to the `now()` operation of the ClockBound API.
247///
248/// Grab the most up to date data defining the clock error bound CEB, read the current time C(t)
249/// from the system clock and returns the interval [(C(t) - CEB), (C(t) + CEB)] in which true time
250/// exists. The call also populate an enum with the underlying clock status.
251///
252/// # Safety
253///
254/// Have no choice but rely on the caller to pass valid pointers.
255#[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    /// 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 the bound on clock error data
304            let ceb = ClockErrorBound::default();
305
306            // Convert the ceb struct into a slice so we can write it all out, fairly magic.
307            // Definitely needs the #[repr(C)] layout.
308            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    /// Assert that the shared memory segment can be open, read and and closed. Only a sanity test.
366    #[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    /// Assert that the clock status is converted correctly between representations. This is a bit
430    /// of a "useless" unit test since it mimics the code closely. However, this is a core
431    /// property we give to the callers, so may as well.
432    #[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}