1mod chrony_client;
6mod clock_bound_runner;
7mod clock_snapshot_poller;
8mod clock_state_fsm;
9mod clock_state_fsm_no_disruption;
10mod phc_utils;
11pub mod signal;
12
13use std::path::Path;
14use std::str::FromStr;
15use std::sync::atomic;
16
17#[cfg(any(test, feature = "test"))]
18use crate::phc_utils::MockPhcWithSysfsErrorBound as PhcWithSysfsErrorBound;
19#[cfg(not(any(test, feature = "test")))]
20use crate::phc_utils::PhcWithSysfsErrorBound;
21use clock_bound_shm::ShmWriter;
22use clock_bound_vmclock::{shm::VMCLOCK_SHM_DEFAULT_PATH, shm_reader::VMClockShmReader};
23use chrony_client::UnixDomainSocket;
24use clock_bound_runner::ClockBoundRunner;
25use clock_snapshot_poller::chronyd_snapshot_poller::ChronyDaemonSnapshotPoller;
26use tracing::{debug, error};
27
28pub use phc_utils::get_error_bound_sysfs_path;
29
30pub const CLOCKBOUND_SHM_DEFAULT_PATH: &str = "/var/run/clockbound/shm0";
32
33#[derive(Clone, PartialEq, Eq, Hash, Debug)]
36pub struct PhcInfo {
37 pub refid: u32,
38 pub sysfs_error_bound_path: std::path::PathBuf,
39}
40
41pub static FORCE_DISRUPTION_PENDING: atomic::AtomicBool = atomic::AtomicBool::new(false);
44
45pub static FORCE_DISRUPTION_STATE: atomic::AtomicBool = atomic::AtomicBool::new(false);
47
48#[derive(Debug, Copy, Clone, PartialEq)]
50pub enum ChronyClockStatus {
51 Unknown = 0,
53
54 Synchronized = 1,
56
57 FreeRunning = 2,
59}
60
61impl From<u16> for ChronyClockStatus {
62 fn from(value: u16) -> Self {
64 match value {
65 0..=2 => Self::Synchronized,
66 3 => Self::FreeRunning,
67 _ => Self::Unknown,
68 }
69 }
70}
71
72#[derive(Debug, Copy, Clone, PartialEq)]
74pub enum ClockDisruptionState {
75 Unknown,
76 Reliable,
77 Disrupted,
78}
79
80#[derive(Clone, PartialEq, Eq, Hash, Debug)]
84pub struct ParseError;
85
86#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
88pub enum ClockErrorBoundSource {
89 Chrony,
91
92 VMClock,
94}
95
96impl FromStr for ClockErrorBoundSource {
98 type Err = ParseError;
99 fn from_str(input: &str) -> Result<ClockErrorBoundSource, Self::Err> {
100 match input.to_lowercase().as_str() {
101 "chrony" => Ok(ClockErrorBoundSource::Chrony),
102 "vmclock" => Ok(ClockErrorBoundSource::VMClock),
103 _ => {
104 error!("ClockErrorBoundSource '{:?}' is not supported", input);
105 Err(ParseError)
106 }
107 }
108 }
109}
110
111pub fn refid_to_u32(ref_id: &str) -> Result<u32, String> {
117 let bytes = ref_id.bytes();
118 if bytes.len() <= 4 && bytes.clone().all(|b| b.is_ascii()) {
119 let bytes_as_u32: Vec<u32> = bytes.map(|val| val as u32).collect();
120 Ok(bytes_as_u32
121 .iter()
122 .rev()
123 .enumerate()
124 .fold(0, |acc, (i, val)| acc | (val << (i * 8))))
125 } else {
126 Err(String::from(
127 "The PHC reference ID supplied was not a 4 character ASCII string.",
128 ))
129 }
130}
131
132pub fn run(
133 max_drift_ppb: u32,
134 maybe_phc_info: Option<PhcInfo>,
135 clock_error_bound_source: ClockErrorBoundSource,
136 clock_disruption_support_enabled: bool,
137) {
138 let mut writer = match ShmWriter::new(Path::new(CLOCKBOUND_SHM_DEFAULT_PATH)) {
140 Ok(writer) => {
141 debug!("Created a new ShmWriter");
142 writer
143 }
144 Err(e) => {
145 error!(
146 "Failed to create the SHM writer at {:?} {}",
147 CLOCKBOUND_SHM_DEFAULT_PATH, e
148 );
149 panic!("Failed to create SHM writer");
150 }
151 };
152 let clock_status_snapshot_poller = match clock_error_bound_source {
153 ClockErrorBoundSource::Chrony => ChronyDaemonSnapshotPoller::new(
154 Box::new(UnixDomainSocket::default()),
155 maybe_phc_info.map(|phc_info| {
156 PhcWithSysfsErrorBound::new(phc_info.sysfs_error_bound_path, phc_info.refid)
157 }),
158 ),
159 ClockErrorBoundSource::VMClock => {
160 unimplemented!("VMClock ClockErrorBoundSource is not yet implemented");
161 }
162 };
163 let mut vmclock_shm_reader = if !clock_disruption_support_enabled {
164 None
165 } else {
166 match VMClockShmReader::new(VMCLOCK_SHM_DEFAULT_PATH) {
167 Ok(reader) => Some(reader),
168 Err(e) => {
169 panic!(
170 "VMClockPoller: Failed to create VMClockShmReader. Please check if path {:?} exists and is readable. {:?}",
171 VMCLOCK_SHM_DEFAULT_PATH, e
172 );
173 }
174 }
175 };
176
177 let mut clock_bound_runner =
178 ClockBoundRunner::new(clock_disruption_support_enabled, max_drift_ppb);
179 clock_bound_runner.run(
180 &mut vmclock_shm_reader,
181 &mut writer,
182 clock_status_snapshot_poller,
183 UnixDomainSocket::default(),
184 );
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_str_to_clockerrorboundsource_conversion() {
193 assert_eq!(
194 ClockErrorBoundSource::from_str("chrony"),
195 Ok(ClockErrorBoundSource::Chrony)
196 );
197 assert_eq!(
198 ClockErrorBoundSource::from_str("Chrony"),
199 Ok(ClockErrorBoundSource::Chrony)
200 );
201 assert_eq!(
202 ClockErrorBoundSource::from_str("CHRONY"),
203 Ok(ClockErrorBoundSource::Chrony)
204 );
205 assert_eq!(
206 ClockErrorBoundSource::from_str("cHrOnY"),
207 Ok(ClockErrorBoundSource::Chrony)
208 );
209 assert_eq!(
210 ClockErrorBoundSource::from_str("vmclock"),
211 Ok(ClockErrorBoundSource::VMClock)
212 );
213 assert_eq!(
214 ClockErrorBoundSource::from_str("VMClock"),
215 Ok(ClockErrorBoundSource::VMClock)
216 );
217 assert_eq!(
218 ClockErrorBoundSource::from_str("VMCLOCK"),
219 Ok(ClockErrorBoundSource::VMClock)
220 );
221 assert_eq!(
222 ClockErrorBoundSource::from_str("vmClock"),
223 Ok(ClockErrorBoundSource::VMClock)
224 );
225 assert!(ClockErrorBoundSource::from_str("other").is_err());
226 assert!(ClockErrorBoundSource::from_str("None").is_err());
227 assert!(ClockErrorBoundSource::from_str("null").is_err());
228 assert!(ClockErrorBoundSource::from_str("").is_err());
229 }
230
231 #[test]
232 fn test_refid_to_u32() {
233 assert!(refid_to_u32("morethan4characters").is_err());
235 let non_valid_ascii_str = "©";
236 assert!(non_valid_ascii_str.len() <= 4);
237 assert!(refid_to_u32(non_valid_ascii_str).is_err());
238
239 assert_eq!(
242 refid_to_u32("PHC0").unwrap(),
243 80 << 24 | 72 << 16 | 67 << 8 | 48
244 );
245 assert_eq!(refid_to_u32("PHC").unwrap(), 80 << 16 | 72 << 8 | 67);
246 assert_eq!(refid_to_u32("PH").unwrap(), 80 << 8 | 72);
247 assert_eq!(refid_to_u32("P").unwrap(), 80);
248 }
249}