clock_bound_c/lib.rs
1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//! A client library to communicate with ClockBoundD.
4//! # Usage
5//! ClockBoundC requires ClockBoundD to be running to work. See [ClockBoundD documentation](../clock-bound-d/README.md) for installation instructions.
6//!
7//! For Rust programs built with Cargo, add "clock-bound-c" as a dependency in your Cargo.toml.
8//!
9//! For example:
10//! ```text
11//! [dependencies]
12//! clock-bound-c = "0.1.0"
13//! ```
14//!
15//! ## Examples
16//!
17//! Runnable examples exist at [examples](examples) and can be run with Cargo.
18//!
19//! "/run/clockboundd/clockboundd.sock" is the expected default clockboundd.sock location, but the examples can be run with a
20//! different socket location if desired:
21//!
22//! ```text
23//! cargo run --example now /run/clockboundd/clockboundd.sock
24//! cargo run --example before /run/clockboundd/clockboundd.sock
25//! cargo run --example after /run/clockboundd/clockboundd.sock
26//! cargo run --example timing /run/clockboundd/clockboundd.sock
27//! ```
28//!
29//! # Updating README
30//!
31//! This README is generated via [cargo-readme](https://crates.io/crates/cargo-readme). Updating can be done by running:
32//! ```text
33//! cargo readme > README.md
34//! ```
35mod error;
36
37use crate::error::ClockBoundCError;
38use byteorder::{ByteOrder, NetworkEndian};
39use rand::distributions::Alphanumeric;
40use rand::{thread_rng, Rng};
41use std::fs;
42use std::os::unix::fs::PermissionsExt;
43use std::os::unix::net::UnixDatagram;
44use std::path::PathBuf;
45use std::time::{Duration, SystemTime, UNIX_EPOCH};
46
47
48
49/// The default Unix Datagram Socket file that is generated by ClockBoundD.
50pub const CLOCKBOUNDD_SOCKET_ADDRESS_PATH: &str = "/run/clockboundd/clockboundd.sock";
51/// The prefix of a ClockBoundC socket name. It is appended with a randomized string
52/// Ex: clockboundc-G0uv7ULMLNyeLIGKSejG.sock
53pub const CLOCKBOUNDC_SOCKET_NAME_PREFIX: &str = "clockboundc";
54
55/// Setting clock frequency to 1ppm to match chrony
56pub const FREQUENCY_ERROR: u64 = 1; //1ppm
57
58/// A structure for containing the error bounds returned from ClockBoundD. The values represent
59/// the time since the Unix Epoch in nanoseconds.
60pub struct Bound {
61 /// System time minus the error calculated from chrony in nanoseconds since the Unix Epoch
62 pub earliest: u64,
63 /// System time plus the error calculated from chrony in nanoseconds since the Unix Epoch
64 pub latest: u64,
65}
66
67/// A structure for holding the header of the received response.
68pub struct ResponseHeader {
69 /// The version of the response received from ClockBoundD.
70 pub response_version: u8,
71 /// The type of the response received from ClockBoundD.
72 pub response_type: u8,
73 /// A flag representing if Chrony is reporting as unsynchronized.
74 pub unsynchronized_flag: bool,
75}
76
77/// A structure for holding the response of a now request.
78pub struct ResponseNow {
79 pub header: ResponseHeader,
80 pub bound: Bound,
81 /// The timestamp, represented as nanoseconds since the Unix Epoch, bounded against.
82 pub timestamp: u64,
83}
84
85/// A structure for holding the response of a before request.
86pub struct ResponseBefore {
87 pub header: ResponseHeader,
88 /// A boolean indicating if the requested time is before the current error bounds or not.
89 pub before: bool,
90}
91
92/// A structure for holding the response of an after request.
93pub struct ResponseAfter {
94 pub header: ResponseHeader,
95 /// A boolean indicating if the requested time is after the current error bounds or not.
96 pub after: bool,
97}
98
99/// A structure for holding the response of a timing request.
100#[derive(Debug)]
101pub struct TimingResult {
102 /// Callback began executing no earlier than this time
103 pub earliest_start : SystemTime,
104 /// Callback finished executing no later than this time
105 pub latest_finish : SystemTime,
106 /// No less than this amount of time elapsed from when timing() was called
107 /// to when it returned.
108 pub min_execution_time : Duration,
109 /// No more than this amount of time elapsed when the callback was invoked
110 /// to when it returned.
111 pub max_execution_time : Duration
112}
113
114/// A structure for holding a client to communicate with ClockBoundD.
115pub struct ClockBoundClient {
116 /// A ClockBoundClient must have a socket to communicate with ClockBoundD.
117 socket: UnixDatagram,
118}
119
120impl ClockBoundClient {
121 /// Create a new ClockBoundClient using the default clockboundd.sock path at
122 /// "/run/clockboundd/clockboundd.sock".
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// use clock_bound_c::ClockBoundClient;
128 /// let client = match ClockBoundClient::new(){
129 /// Ok(client) => client,
130 /// Err(e) => {
131 /// println!("Couldn't create client: {}", e);
132 /// return
133 /// }
134 /// };
135 pub fn new() -> Result<ClockBoundClient, ClockBoundCError> {
136 ClockBoundClient::new_with_path(std::path::PathBuf::from(CLOCKBOUNDD_SOCKET_ADDRESS_PATH))
137 }
138 /// Create a new ClockBoundClient using a defined clockboundd.sock path.
139 ///
140 /// The expected default socket path is at "/run/clockboundd/clockboundd.sock", but if a
141 /// different desired location has been set up this allows its usage.
142 ///
143 /// If using the default location at "/run/clockboundd/clockboundd.sock", use new() instead.
144 ///
145 /// # Arguments
146 ///
147 /// * `clock_bound_d_socket` - The path at which the clockboundd.sock lives.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use clock_bound_c::ClockBoundClient;
153 /// let client = match ClockBoundClient::new_with_path(std::path::PathBuf::from("/run/clockboundd/clockboundd.sock")){
154 /// Ok(client) => client,
155 /// Err(e) => {
156 /// println!("Couldn't create client: {}", e);
157 /// return
158 /// }
159 /// };
160 /// ```
161 pub fn new_with_path(
162 clock_bound_d_socket: PathBuf,
163 ) -> Result<ClockBoundClient, ClockBoundCError> {
164 let client_path = get_socket_path();
165
166 // Binding will fail if the socket file already exists. However, since the socket file is
167 // uniquely created based on the current time this should not fail.
168 let sock = match UnixDatagram::bind(client_path.as_path()) {
169 Ok(sock) => sock,
170 Err(e) => return Err(ClockBoundCError::BindError(e)),
171 };
172
173 let mode = 0o666;
174 let permissions = fs::Permissions::from_mode(mode);
175 match fs::set_permissions(client_path, permissions) {
176 Err(e) => return Err(ClockBoundCError::SetPermissionsError(e)),
177 _ => {}
178 }
179
180 match sock.connect(clock_bound_d_socket.as_path()) {
181 Err(e) => return Err(ClockBoundCError::ConnectError(e)),
182 _ => {}
183 }
184
185 Ok(ClockBoundClient { socket: sock })
186 }
187
188 /// Returns the bounds of the current system time +/- the error calculated from chrony.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// use clock_bound_c::ClockBoundClient;
194 /// let client = match ClockBoundClient::new(){
195 /// Ok(client) => client,
196 /// Err(e) => {
197 /// println!("Couldn't create client: {}", e);
198 /// return
199 /// }
200 /// };
201 /// let response = match client.now(){
202 /// Ok(response) => response,
203 /// Err(e) => {
204 /// println!("Couldn't complete now request: {}", e);
205 /// return
206 /// }
207 /// };
208 /// ```
209 pub fn now(&self) -> Result<ResponseNow, ClockBoundCError> {
210 // Header
211 // 1st - Version
212 // 2nd - Command Type
213 // 3rd, 4th - Reserved
214 let mut request: [u8; 4] = [1, 1, 0, 0];
215
216 match self.socket.send(&mut request) {
217 Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
218 _ => {}
219 }
220 let mut response: [u8; 20] = [0; 20];
221 match self.socket.recv(&mut response) {
222 Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
223 _ => {}
224 }
225 let response_version = response[0];
226 let response_type = response[1];
227 let unsynchronized_flag = response[2] != 0;
228 let earliest = NetworkEndian::read_u64(&response[4..12]);
229 let latest = NetworkEndian::read_u64(&response[12..20]);
230 Ok(ResponseNow {
231 header: ResponseHeader {
232 response_version,
233 response_type,
234 unsynchronized_flag,
235 },
236 bound: Bound { earliest, latest },
237 // Since the bounds are the system time +/- the Clock Error Bound, the system time
238 // timestamp can be calculated with the below formula.
239 timestamp: (latest - ((latest - earliest) / 2)),
240 })
241 }
242
243 /// Returns true if the provided timestamp is before the earliest error bound.
244 /// Otherwise, returns false.
245 ///
246 /// # Arguments
247 ///
248 /// * `before_time` - A timestamp, represented as nanoseconds since the Unix Epoch, that is
249 /// tested against the earliest error bound.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// use clock_bound_c::ClockBoundClient;
255 /// let client = match ClockBoundClient::new(){
256 /// Ok(client) => client,
257 /// Err(e) => {
258 /// println!("Couldn't create client: {}", e);
259 /// return
260 /// }
261 /// };
262 /// // Using 0 which equates to the Unix Epoch
263 /// let response = match client.before(0){
264 /// Ok(response) => response,
265 /// Err(e) => {
266 /// println!("Couldn't complete before request: {}", e);
267 /// return
268 /// }
269 /// };
270 /// ```
271 pub fn before(&self, before_time: u64) -> Result<ResponseBefore, ClockBoundCError> {
272 // Header
273 // 1st - Version
274 // 2nd - Command Type
275 // 3rd, 4th - Reserved
276 let mut request: [u8; 12] = [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
277
278 // Body
279 NetworkEndian::write_u64(&mut request[4..12], before_time);
280
281 match self.socket.send(&mut request) {
282 Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
283 _ => {}
284 }
285 let mut response: [u8; 5] = [0; 5];
286 match self.socket.recv(&mut response) {
287 Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
288 _ => {}
289 }
290 let response_version = response[0];
291 let response_type = response[1];
292 let unsynchronized_flag = response[2] != 0;
293 let before = response[4] != 0;
294 Ok(ResponseBefore {
295 header: ResponseHeader {
296 response_version,
297 response_type,
298 unsynchronized_flag,
299 },
300 before,
301 })
302 }
303
304 /// Returns true if the provided timestamp is after the latest error bound.
305 /// Otherwise, returns false.
306 ///
307 /// # Arguments
308 ///
309 /// * `after_time` - A timestamp, represented as nanoseconds since the Unix Epoch, that is
310 /// tested against the latest error bound.
311 ///
312 /// # Examples
313 ///
314 /// ```
315 /// use clock_bound_c::ClockBoundClient;
316 /// let client = match ClockBoundClient::new(){
317 /// Ok(client) => client,
318 /// Err(e) => {
319 /// println!("Couldn't create client: {}", e);
320 /// return
321 /// }
322 /// };
323 /// // Using 0 which equates to the Unix Epoch
324 /// let response = match client.after(0){
325 /// Ok(response) => response,
326 /// Err(e) => {
327 /// println!("Couldn't complete after request: {}", e);
328 /// return
329 /// }
330 /// };
331 /// ```
332 pub fn after(&self, after_time: u64) -> Result<ResponseAfter, ClockBoundCError> {
333 // Header
334 // 1st - Version
335 // 2nd - Command Type
336 // 3rd, 4th - Reserved
337 let mut request: [u8; 12] = [1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
338
339 // Body
340 NetworkEndian::write_u64(&mut request[4..12], after_time);
341
342 match self.socket.send(&mut request) {
343 Err(e) => return Err(ClockBoundCError::SendMessageError(e)),
344 _ => {}
345 }
346 let mut response: [u8; 5] = [0; 5];
347 match self.socket.recv(&mut response) {
348 Err(e) => return Err(ClockBoundCError::ReceiveMessageError(e)),
349 _ => {}
350 }
351 let response_version = response[0];
352 let response_type = response[1];
353 let unsynchronized_flag = response[2] != 0;
354 let after = response[4] != 0;
355 Ok(ResponseAfter {
356 header: ResponseHeader {
357 response_version,
358 response_type,
359 unsynchronized_flag,
360 },
361 after,
362 })
363 }
364
365 ///Execute `f` and return bounds on execution time
366 pub fn timing<A, F>(&self, f: F) -> Result<(TimingResult, A), (ClockBoundCError, Result<A,F>)>
367 where F: FnOnce() -> A {
368
369 // Get the first timestamps
370
371 // Header
372 // 1st - Version
373 // 2nd - Command Type
374 // 3rd, 4th - Reserved
375 let mut request: [u8; 4] = [1, 1, 0, 0];
376
377 match self.socket.send(&mut request) {
378 Err(e) => return Err((ClockBoundCError::SendMessageError(e), Err(f))),
379 _ => {}
380 }
381 let mut response: [u8; 20] = [0; 20];
382 match self.socket.recv(&mut response) {
383 Err(e) => return Err((ClockBoundCError::ReceiveMessageError(e), Err(f))),
384 _ => {}
385 }
386 let earliest_start = NetworkEndian::read_u64(&response[4..12]);
387 let latest_start = NetworkEndian::read_u64(&response[12..20]);
388
389 // Execute the provided function, f
390 let callback = f();
391
392 // Get the second timestamps
393 let mut request: [u8; 4] = [1, 1, 0, 0];
394 match self.socket.send(&mut request) {
395 Err(e) => return Err((ClockBoundCError::SendMessageError(e), Ok(callback))),
396 _ => {}
397 }
398 let mut response: [u8; 20] = [0; 20];
399 match self.socket.recv(&mut response) {
400 Err(e) => return Err((ClockBoundCError::ReceiveMessageError(e), Ok(callback))),
401 _ => {}
402 }
403 let earliest_finish = NetworkEndian::read_u64(&response[4..12]);
404 let latest_finish = NetworkEndian::read_u64(&response[12..20]);
405
406 // Calculate midpoints of start and finish
407 let start_midpoint = (earliest_start + latest_start)/2;
408 let end_midpoint = (earliest_finish + latest_finish)/2;
409
410 // Convert to SystemTime
411 let earliest_start = UNIX_EPOCH + Duration::from_nanos(earliest_start);
412 let latest_finish = UNIX_EPOCH + Duration::from_nanos(latest_finish);
413
414 // Calculates duration between the two midpoints
415 let execution_time = end_midpoint - start_midpoint;
416 let error_rate = (execution_time * FREQUENCY_ERROR) / 1_000_000 +
417 //Ugly way of saying .div_ceil() until it stabilizes
418 if (execution_time * FREQUENCY_ERROR) % 1_000_000 == 0 { 0 } else { 1 };
419
420 let min_execution_time = Duration::from_nanos(execution_time - error_rate);
421 let max_execution_time = Duration::from_nanos(execution_time + error_rate);
422
423 Ok((TimingResult{
424 earliest_start,
425 latest_finish,
426 min_execution_time,
427 max_execution_time
428 }, callback))
429 }
430}
431
432impl Drop for ClockBoundClient {
433 /// Remove the client socket file when a ClockBoundClient is dropped.
434 fn drop(&mut self) {
435 if let Ok(addr) = self.socket.local_addr() {
436 if let Some(path) = addr.as_pathname() {
437 let _ = self.socket.shutdown(std::net::Shutdown::Both);
438 let _ = std::fs::remove_file(path);
439 }
440 }
441 }
442}
443
444/// Create a unique client socket file in the system's temp directory
445///
446/// The socket name will have clockboundc as a prefix, followed by a random string of 20
447/// alphanumeric characters.
448/// Ex: clockboundc-G0uv7ULMLNyeLIGKSejG.sock
449fn get_socket_path() -> PathBuf {
450 let dir = std::env::temp_dir();
451 let mut rng = thread_rng();
452 let random_str: String = (&mut rng)
453 .sample_iter(Alphanumeric)
454 .take(20)
455 .map(char::from)
456 .collect();
457
458 let client_path_buf =
459 dir.join(CLOCKBOUNDC_SOCKET_NAME_PREFIX.to_owned() + "-" + &*random_str + ".sock");
460 return client_path_buf;
461}