1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
use std::ffi::CString;
use tracing::debug;
use crate::shm_reader::VMClockShmReader;
use clock_bound_shm::{ClockStatus, ShmError, ShmReader};
use nix::sys::time::TimeSpec;
pub mod shm;
pub mod shm_reader;
pub mod shm_writer;
/// VMClock provides the following capabilities:
///
/// - Error-bounded timestamps obtained from ClockBound daemon.
/// - Clock disruption signaling via the VMClock.
pub struct VMClock {
clockbound_shm_reader: ShmReader,
vmclock_shm_path: String,
vmclock_shm_reader: Option<VMClockShmReader>,
}
impl VMClock {
/// Open the VMClock shared memory segment and the ClockBound shared memory segment for reading.
///
/// On error, returns an appropriate `Errno`. If the content of the segment
/// is uninitialized, unparseable, or otherwise malformed, EPROTO will be
/// returned.
pub fn new(clockbound_shm_path: &str, vmclock_shm_path: &str) -> Result<VMClock, ShmError> {
let clockbound_shm_path = CString::new(clockbound_shm_path).expect("CString::new failed");
let mut clockbound_shm_reader = ShmReader::new(clockbound_shm_path.as_c_str())?;
let clockbound_snapshot = clockbound_shm_reader.snapshot()?;
let mut vmclock_shm_reader: Option<VMClockShmReader> = None;
if clockbound_snapshot.clock_disruption_support_enabled {
vmclock_shm_reader = Some(VMClockShmReader::new(vmclock_shm_path)?);
}
Ok(VMClock {
clockbound_shm_reader,
vmclock_shm_path: String::from(vmclock_shm_path),
vmclock_shm_reader,
})
}
/// The VMClock equivalent of clock_gettime(), but with bound on accuracy.
///
/// Returns a pair of (earliest, latest) timespec between which current time exists. The
/// interval width is twice the clock error bound (ceb) such that:
/// (earliest, latest) = ((now - ceb), (now + ceb))
/// The function also returns a clock status to assert that the clock is being synchronized, or
/// free-running, or ...
pub fn now(&mut self) -> Result<(TimeSpec, TimeSpec, ClockStatus), ShmError> {
// Read from the ClockBound shared memory segment.
let clockbound_snapshot = self.clockbound_shm_reader.snapshot()?;
if self.vmclock_shm_reader.is_none() && clockbound_snapshot.clock_disruption_support_enabled
{
self.vmclock_shm_reader = Some(VMClockShmReader::new(self.vmclock_shm_path.as_str())?);
}
let (earliest, latest, clock_status) = clockbound_snapshot.now()?;
if clockbound_snapshot.clock_disruption_support_enabled {
if let Some(ref mut vmclock_shm_reader) = self.vmclock_shm_reader {
// Read from the VMClock shared memory segment.
let vmclock_snapshot = vmclock_shm_reader.snapshot()?;
// Comparing the disruption marker between the VMClock snapshot and the
// ClockBound snapshot will tell us if the clock status provided by the
// ClockBound daemon is trustworthy.
debug!("clock_status: {:?}, vmclock_snapshot.disruption_marker: {:?}, clockbound_snapshot.disruption_marker: {:?}",
clock_status, vmclock_snapshot.disruption_marker,
clockbound_snapshot.disruption_marker);
if vmclock_snapshot.disruption_marker == clockbound_snapshot.disruption_marker {
// ClockBound's shared memory segment has the latest clock disruption status from
// VMClock and this means the clock status here can be trusted.
return Ok((earliest, latest, clock_status));
} else {
// ClockBound has stale clock disruption status and it is not up-to-date with
// VMClock.
// Override the clock disruption status with ClockStatus::Unknown until
// ClockBound daemon is able to pick up the latest clock disruption status
// from VMClock.
return Ok((earliest, latest, ClockStatus::Unknown));
}
}
}
debug!("clock_status: {:?}", clock_status);
Ok((earliest, latest, clock_status))
}
}
#[cfg(test)]
mod t_lib {
use super::*;
use clock_bound_shm::{ClockErrorBound, ShmWrite, ShmWriter};
use std::path::Path;
use crate::shm::{VMClockClockStatus, VMClockShmBody};
use crate::shm_writer::{VMClockShmWrite, VMClockShmWriter};
/// We make use of tempfile::NamedTempFile to ensure that
/// local files that are created during a test get removed
/// afterwards.
use tempfile::NamedTempFile;
macro_rules! vmclockshmbody {
() => {
VMClockShmBody {
disruption_marker: 10,
flags: 0_u64,
_padding: [0x00, 0x00],
clock_status: VMClockClockStatus::Unknown,
leap_second_smearing_hint: 0,
tai_offset_sec: 37_i16,
leap_indicator: 0,
counter_period_shift: 0,
counter_value: 0,
counter_period_frac_sec: 0,
counter_period_esterror_rate_frac_sec: 0,
counter_period_maxerror_rate_frac_sec: 0,
time_sec: 0,
time_frac_sec: 0,
time_esterror_nanosec: 0,
time_maxerror_nanosec: 0,
}
};
}
/// Helper function to remove files created during unit tests.
fn remove_file_or_directory(path: &str) {
// Busy looping on deleting the previous file, good enough for unit test
let p = Path::new(&path);
while p.exists() {
if p.is_dir() {
std::fs::remove_dir_all(&path).expect("failed to remove file");
} else {
std::fs::remove_file(&path).expect("failed to remove file");
}
}
}
/// Assert that VMClock can be created successfully and now() function successful when
/// clock_disruption_support_enabled is true and a valid file exists at the vmclock_shm_path.
#[test]
fn test_vmclock_now_with_clock_disruption_support_enabled_success() {
let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
remove_file_or_directory(&clockbound_shm_path);
let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
remove_file_or_directory(&vmclock_shm_path);
// Create and wipe the ClockBound memory segment.
let ceb = ClockErrorBound::new(
TimeSpec::new(1, 2), // as_of
TimeSpec::new(3, 4), // void_after
123, // bound_nsec
10, // disruption_marker
100, // max_drift_ppb
ClockStatus::Synchronized, // clock_status
true, // clock_disruption_support_enabled
);
let mut clockbound_shm_writer =
ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
clockbound_shm_writer.write(&ceb);
// Create and write the VMClock memory segment.
let vmclock_shm_body = vmclockshmbody!();
let mut vmclock_shm_writer = VMClockShmWriter::new(Path::new(&vmclock_shm_path))
.expect("Failed to create a VMClockShmWriter");
vmclock_shm_writer.write(&vmclock_shm_body);
// Create the VMClock, and assert that the creation was successful.
let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
match vmclock_new_result {
Ok(mut vmclock) => {
// Assert that now() does not return an error.
let now_result = vmclock.now();
assert!(now_result.is_ok());
}
Err(_) => {
assert!(false);
}
}
}
/// Assert that VMClock will fail to be created when clock_disruption_support_enabled
/// is true and no file exists at the vmclock_shm_path.
#[test]
fn test_vmclock_now_with_clock_disruption_support_enabled_failure() {
let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
remove_file_or_directory(&clockbound_shm_path);
let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
remove_file_or_directory(&vmclock_shm_path);
// Create and wipe the ClockBound memory segment.
let ceb = ClockErrorBound::new(
TimeSpec::new(1, 2), // as_of
TimeSpec::new(3, 4), // void_after
123, // bound_nsec
10, // disruption_marker
100, // max_drift_ppb
ClockStatus::Synchronized, // clock_status
true, // clock_disruption_support_enabled
);
let mut clockbound_shm_writer =
ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
clockbound_shm_writer.write(&ceb);
// Create the VMClock, and assert that the creation was successful.
let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
assert!(vmclock_new_result.is_err());
}
/// Assert that VMClock can be created successfully and now() runs successfully
/// when clock_disruption_support_enabled is false and no file exists at the vmclock_shm_path.
#[test]
fn test_vmclock_now_with_clock_disruption_support_not_enabled() {
let clockbound_shm_tempfile = NamedTempFile::new().expect("create clockbound file failed");
let clockbound_shm_temppath = clockbound_shm_tempfile.into_temp_path();
let clockbound_shm_path = clockbound_shm_temppath.to_str().unwrap();
remove_file_or_directory(&clockbound_shm_path);
let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
remove_file_or_directory(&vmclock_shm_path);
// Create and wipe the ClockBound memory segment.
let ceb = ClockErrorBound::new(
TimeSpec::new(1, 2), // as_of
TimeSpec::new(3, 4), // void_after
123, // bound_nsec
10, // disruption_marker
100, // max_drift_ppb
ClockStatus::Synchronized, // clock_status
false, // clock_disruption_support_enabled
);
let mut clockbound_shm_writer =
ShmWriter::new(Path::new(&clockbound_shm_path)).expect("Failed to create a ShmWriter");
clockbound_shm_writer.write(&ceb);
// Create the VMClock, and assert that the creation was successful.
// There should be no error even though there is no file located at vmclock_shm_path.
let vmclock_new_result = VMClock::new(&clockbound_shm_path, &vmclock_shm_path);
match vmclock_new_result {
Ok(mut vmclock) => {
// Assert that now() does not return an error.
let now_result = vmclock.now();
assert!(now_result.is_ok());
}
Err(_) => {
assert!(false);
}
}
}
}