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}