endpoint_sec/
utils.rs

1//! Utilities related to the handling of time, ect.
2
3use std::borrow::Cow;
4use std::ffi::{CStr, CString};
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::time::{Duration, Instant};
7
8use mach2::mach_time::{mach_absolute_time, mach_timebase_info};
9
10use crate::TimeError;
11
12/// Convert a Mach absolute time to an [`Instant`].
13pub(crate) fn convert_mach_time_to_instant(mach_time: u64) -> Result<Instant, TimeError> {
14    let now = Instant::now();
15    // Safety: always safe to call
16    let now_mach = unsafe { mach_absolute_time() } as i128;
17    // No underflow possible: we had two u64, the result of `a - b` will always fit in an i128
18    let time_til_ticks = (mach_time as i128) - now_mach;
19    let back_in_time = time_til_ticks < 0;
20    let time_til = convert_mach_time_to_duration(time_til_ticks.unsigned_abs() as u64);
21
22    if back_in_time {
23        now.checked_sub(time_til).ok_or(TimeError::Overflow)
24    } else {
25        now.checked_add(time_til).ok_or(TimeError::Overflow)
26    }
27}
28
29/// Convert a Mach absolute time to a [Duration].
30pub(crate) fn convert_mach_time_to_duration(mach_time: u64) -> Duration {
31    /// Storage for [struct@mach_timebase_info]
32    ///
33    /// NOTE: We pack the [struct@mach_timebase_info] structure into a [u64] to take advantage of atomics.
34    static TIME_BASE_RAW_INFO: AtomicU64 = AtomicU64::new(0);
35
36    let mut value = mti_from_u64(TIME_BASE_RAW_INFO.load(Ordering::Relaxed));
37
38    // Similar to https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/time.rs#L226-L253
39    if value.denom == 0 || value.numer == 0 {
40        // Safety: value needs to be a pointer initialized to a mach_timebase_info.
41        unsafe { mach_timebase_info(&mut value) };
42
43        TIME_BASE_RAW_INFO.store(u64_from_mti(value), Ordering::Relaxed)
44    }
45
46    let nanos = (mach_time * u64::from(value.numer)) / u64::from(value.denom);
47
48    Duration::from_nanos(nanos)
49}
50
51/// [`u64`] to [`mach_timebase_info`][struct@mach_timebase_info]
52fn mti_from_u64(raw_info: u64) -> mach_timebase_info {
53    mach_timebase_info {
54        numer: raw_info as u32,
55        denom: (raw_info >> 32) as u32,
56    }
57}
58
59/// [`mach_timebase_info`][struct@mach_timebase_info] to [`u64`]
60fn u64_from_mti(info: mach_timebase_info) -> u64 {
61    (u64::from(info.denom) << 32) | u64::from(info.numer)
62}
63
64/// Convert a [`endpoint_sec_sys::timespec`] to a [`Duration`].
65#[inline(always)]
66pub(crate) fn convert_timespec_to_duration(t: endpoint_sec_sys::timespec) -> Duration {
67    Duration::new(t.tv_sec as u64, t.tv_nsec as u32)
68}
69
70/// Converts an arbitrary slice of bytes to a [`CStr`] if it contains a nul byte or a [`CString`]
71/// if it doesn't (and adds a nul byte at the end in this case).
72///
73/// It allocates only when there is no nul byte in the slice.
74///
75/// If the slice contains several nul bytes, it's cut at the first encountered.
76#[inline(always)]
77pub(crate) fn convert_byte_slice_to_cow_cstr(bytes: &[u8]) -> Cow<'_, CStr> {
78    match bytes.iter().position(|x| *x == b'\0').map(|x| x.checked_add(1)) {
79        Some(Some(one_past_nul)) => {
80            // Safety: There is a nul in the string, it's not at `usize::MAX`
81            Cow::Borrowed(unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[..one_past_nul]) })
82        },
83        // Safety: There is a nul in the string, it's at `usize::MAX`
84        Some(None) => Cow::Borrowed(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }),
85        // We are forced to allocate in this case
86        // Safety: there is no nul byte in the string
87        None => Cow::Owned(unsafe { CString::from_vec_unchecked(bytes.into()) }),
88    }
89}