epics_ca/types/
epics.rs

1use chrono::{TimeZone, Utc};
2use derive_more::{From, Into};
3use std::{
4    cmp::Ordering,
5    ffi::{c_char, CStr},
6    fmt::{self, Debug, Formatter},
7    ops::Deref,
8    ptr::copy_nonoverlapping,
9    time::{Duration, SystemTime},
10};
11
12#[repr(transparent)]
13#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord, From, Into)]
14pub struct EpicsEnum(pub u16);
15
16#[repr(transparent)]
17#[derive(Clone, Copy)]
18pub struct EpicsTimeStamp(pub sys::epicsTimeStamp);
19
20impl EpicsTimeStamp {
21    pub fn sec(&self) -> u32 {
22        self.0.secPastEpoch
23    }
24    pub fn nsec(&self) -> u32 {
25        self.0.nsec
26    }
27
28    pub fn to_system(self) -> SystemTime {
29        let unix_epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap();
30        let epics_epoch = Utc.with_ymd_and_hms(1990, 1, 1, 0, 0, 0).unwrap();
31        let diff = (epics_epoch - unix_epoch).to_std().unwrap();
32        SystemTime::UNIX_EPOCH
33            + diff
34            + (Duration::from_secs(self.0.secPastEpoch as u64)
35                + Duration::from_nanos(self.0.nsec as u64))
36    }
37}
38
39impl PartialEq for EpicsTimeStamp {
40    fn eq(&self, other: &Self) -> bool {
41        self.0.secPastEpoch == other.0.secPastEpoch && self.0.nsec == other.0.nsec
42    }
43}
44
45impl Eq for EpicsTimeStamp {}
46
47impl PartialOrd for EpicsTimeStamp {
48    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49        Some(self.cmp(other))
50    }
51}
52
53impl Ord for EpicsTimeStamp {
54    fn cmp(&self, other: &Self) -> Ordering {
55        let o = self.0.secPastEpoch.cmp(&other.0.secPastEpoch);
56        if matches!(o, Ordering::Equal) {
57            self.0.nsec.cmp(&other.0.nsec)
58        } else {
59            o
60        }
61    }
62}
63
64impl Debug for EpicsTimeStamp {
65    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
66        write!(
67            f,
68            "EpicsTimeStamp {{ sec: {}, nsec: {} }}",
69            self.sec(),
70            self.nsec()
71        )
72    }
73}
74
75#[derive(Clone, Copy)]
76#[repr(transparent)]
77pub struct StaticCString<const N: usize> {
78    data: [c_char; N],
79}
80
81impl<const N: usize> Default for StaticCString<N> {
82    fn default() -> Self {
83        Self { data: [0; N] }
84    }
85}
86
87impl<const N: usize> PartialEq for StaticCString<N> {
88    fn eq(&self, other: &Self) -> bool {
89        self.deref().eq(other.deref())
90    }
91}
92
93impl<const N: usize> Eq for StaticCString<N> {}
94
95impl<const N: usize> PartialOrd for StaticCString<N> {
96    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97        self.deref().partial_cmp(other.deref())
98    }
99}
100
101impl<const N: usize> Ord for StaticCString<N> {
102    fn cmp(&self, other: &Self) -> Ordering {
103        self.deref().cmp(other.deref())
104    }
105}
106
107impl<const N: usize> StaticCString<N> {
108    pub const MAX_LEN: usize = N - 1;
109
110    pub fn len(&self) -> Option<usize> {
111        self.data
112            .iter()
113            .copied()
114            .enumerate()
115            .find(|(_, c)| *c == 0)
116            .map(|(i, _)| i)
117    }
118    pub fn is_empty(&self) -> bool {
119        self.data[0] == 0
120    }
121    pub fn from_array(data: [c_char; N]) -> Option<Self> {
122        if data.iter().copied().any(|c| c == 0) {
123            Some(Self { data })
124        } else {
125            None
126        }
127    }
128    pub fn from_cstr(cstr: &CStr) -> Option<Self> {
129        let bytes = cstr.to_bytes();
130        if bytes.len() < N {
131            let mut this = Self::default();
132            unsafe {
133                copy_nonoverlapping(
134                    bytes.as_ptr() as *const c_char,
135                    this.data.as_mut_ptr(),
136                    bytes.len() + 1,
137                )
138            };
139            Some(this)
140        } else {
141            None
142        }
143    }
144}
145
146impl<const N: usize> Deref for StaticCString<N> {
147    type Target = CStr;
148    fn deref(&self) -> &CStr {
149        debug_assert!(
150            self.data.iter().copied().any(|c| c == 0),
151            "String is not nul-terminated"
152        );
153        unsafe { CStr::from_ptr(self.data.as_ptr()) }
154    }
155}
156
157impl<const N: usize> Debug for StaticCString<N> {
158    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159        write!(f, "{:?}", self.deref())
160    }
161}
162
163pub type EpicsString = StaticCString<{ sys::MAX_STRING_SIZE as usize }>;