1const MILLION: u64 = 1_000_000;
7
8#[derive(Clone, Default, Debug, Eq, PartialEq)]
11pub struct Transform<Reference, Output> {
12 pub reference_offset: zx::Instant<Reference>,
14 pub synthetic_offset: zx::Instant<Output>,
16 pub rate_adjust_ppm: i32,
19 pub error_bound_at_offset: u64,
21 pub error_bound_growth_ppm: u32,
23}
24
25impl<Reference: zx::Timeline + Copy, Output: zx::Timeline + Copy> Transform<Reference, Output> {
26 pub fn synthetic(&self, reference: zx::Instant<Reference>) -> zx::Instant<Output> {
28 let reference_difference = (reference - self.reference_offset).into_nanos() as i128;
30 let synthetic_offset = self.synthetic_offset.into_nanos() as i128;
31 let synthetic_ticks = self.rate_adjust_ppm as i128 + MILLION as i128;
32 let reference_ticks = MILLION as i128;
33
34 let time_nanos =
35 (reference_difference * synthetic_ticks / reference_ticks) + synthetic_offset;
36 zx::Instant::from_nanos(time_nanos as i64)
37 }
38
39 pub fn error_bound(&self, reference: zx::Instant<Reference>) -> u64 {
41 let reference_difference = (reference - self.reference_offset).into_nanos() as i128;
43 if reference_difference <= 0 {
44 self.error_bound_at_offset
46 } else {
47 let error_increase =
49 (reference_difference * self.error_bound_growth_ppm as i128) / MILLION as i128;
50 self.error_bound_at_offset + error_increase as u64
51 }
52 }
53
54 pub fn difference(
57 &self,
58 other: &Self,
59 reference: zx::Instant<Reference>,
60 ) -> zx::Duration<Output> {
61 self.synthetic(reference) - other.synthetic(reference)
62 }
63
64 pub fn jump_to(&self, reference: zx::Instant<Reference>) -> zx::ClockUpdate<Reference, Output> {
67 zx::ClockUpdate::<Reference, Output>::builder()
68 .absolute_value(reference, self.synthetic(reference))
69 .rate_adjust(self.rate_adjust_ppm)
70 .error_bounds(self.error_bound(reference))
71 .build()
72 }
73}
74
75impl<Reference: zx::Timeline, Output: zx::Timeline> From<&zx::Clock<Reference, Output>>
76 for Transform<Reference, Output>
77{
78 fn from(clock: &zx::Clock<Reference, Output>) -> Self {
79 let details = clock.get_details().expect("failed to get clock details");
81 let reference_ticks = details.reference_to_synthetic.rate.reference_ticks as i64;
83 let synthetic_ticks = details.reference_to_synthetic.rate.synthetic_ticks as i64;
84 let rate_adjust_ppm =
85 ((synthetic_ticks * MILLION as i64) / reference_ticks) - MILLION as i64;
86
87 Transform {
88 reference_offset: details.reference_to_synthetic.reference_offset,
89 synthetic_offset: details.reference_to_synthetic.synthetic_offset,
90 rate_adjust_ppm: rate_adjust_ppm as i32,
91 error_bound_at_offset: details.error_bounds,
93 error_bound_growth_ppm: 0,
94 }
95 }
96}
97
98pub fn time_at_monotonic<Reference: zx::Timeline, Output: zx::Timeline>(
102 clock: &zx::Clock<Reference, Output>,
103 reference: zx::Instant<Reference>,
104) -> zx::Instant<Output> {
105 let reference_nanos = reference.into_nanos() as i128;
106 let details = clock.get_details().expect("failed to get clock details");
108 let reference_offset = details.reference_to_synthetic.reference_offset.into_nanos() as i128;
111 let synthetic_offset = details.reference_to_synthetic.synthetic_offset.into_nanos() as i128;
112 let reference_ticks = details.reference_to_synthetic.rate.reference_ticks as i128;
113 let synthetic_ticks = details.reference_to_synthetic.rate.synthetic_ticks as i128;
114
115 let time_nanos = ((reference_nanos - reference_offset) * synthetic_ticks / reference_ticks)
116 + synthetic_offset;
117 zx::Instant::from_nanos(time_nanos as i64)
118}
119
120#[cfg(test)]
121mod test {
122 use super::*;
123 use test_util::{assert_geq, assert_leq};
124
125 const BACKSTOP: zx::SyntheticInstant = zx::SyntheticInstant::from_nanos(1234567890);
126 const TIME_DIFF: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
127 const SLEW_RATE_PPM: i32 = 750;
128 const ONE_MILLION: i32 = 1_000_000;
129
130 const TEST_REFERENCE: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(70_000_000_000);
131 const TEST_OFFSET: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(5_000_000_000);
132 const TEST_ERROR_BOUND: u64 = 1234_000;
133 const TEST_ERROR_BOUND_GROWTH: u32 = 100;
134
135 const TOLERANCE: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(500_000_000);
136
137 #[fuchsia::test]
138 fn transform_properties_zero_rate_adjust() {
139 let transform = Transform {
140 reference_offset: TEST_REFERENCE,
141 synthetic_offset: zx::SyntheticInstant::from_nanos(
142 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
143 ),
144 rate_adjust_ppm: 0,
145 error_bound_at_offset: TEST_ERROR_BOUND,
146 error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
147 };
148
149 assert_eq!(
150 transform.synthetic(TEST_REFERENCE).into_nanos(),
151 (TEST_REFERENCE + TEST_OFFSET).into_nanos()
152 );
153 assert_eq!(
154 transform
155 .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
156 .into_nanos(),
157 (TEST_REFERENCE + TEST_OFFSET + zx::MonotonicDuration::from_millis(200)).into_nanos(),
158 );
159 assert_eq!(
160 transform
161 .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
162 .into_nanos(),
163 (TEST_REFERENCE + TEST_OFFSET - zx::MonotonicDuration::from_millis(100)).into_nanos(),
164 );
165
166 assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
167 assert_eq!(
168 transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_millis(1)),
169 TEST_ERROR_BOUND + TEST_ERROR_BOUND_GROWTH as u64
170 );
171 assert_eq!(
172 transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_millis(1)),
173 TEST_ERROR_BOUND as u64
174 );
175 }
176
177 #[fuchsia::test]
178 fn transform_properties_positive_rate_adjust() {
179 let transform = Transform {
180 reference_offset: TEST_REFERENCE,
181 synthetic_offset: zx::SyntheticInstant::from_nanos(
182 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
183 ),
184 rate_adjust_ppm: 25,
185 error_bound_at_offset: TEST_ERROR_BOUND,
186 error_bound_growth_ppm: 0,
187 };
188
189 assert_eq!(
190 transform.synthetic(TEST_REFERENCE).into_nanos(),
191 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
192 );
193 assert_eq!(
194 transform
195 .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
196 .into_nanos(),
197 (TEST_REFERENCE
198 + TEST_OFFSET
199 + zx::MonotonicDuration::from_millis(200)
200 + zx::MonotonicDuration::from_nanos(25 * 200))
201 .into_nanos(),
202 );
203 assert_eq!(
204 transform
205 .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
206 .into_nanos(),
207 (TEST_REFERENCE + TEST_OFFSET
208 - zx::MonotonicDuration::from_millis(100)
209 - zx::MonotonicDuration::from_nanos(25 * 100))
210 .into_nanos(),
211 );
212
213 assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
214 assert_eq!(
215 transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_millis(1)),
216 TEST_ERROR_BOUND as u64
217 );
218 assert_eq!(
219 transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_millis(1)),
220 TEST_ERROR_BOUND as u64
221 );
222 }
223
224 #[fuchsia::test]
225 fn transform_properties_negative_rate_adjust() {
226 let transform = Transform {
227 reference_offset: TEST_REFERENCE,
228 synthetic_offset: zx::SyntheticInstant::from_nanos(
229 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
230 ),
231 rate_adjust_ppm: -50,
232 error_bound_at_offset: TEST_ERROR_BOUND,
233 error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
234 };
235
236 assert_eq!(
237 transform.synthetic(TEST_REFERENCE).into_nanos(),
238 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
239 );
240 assert_eq!(
241 transform
242 .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
243 .into_nanos(),
244 (TEST_REFERENCE + TEST_OFFSET + zx::MonotonicDuration::from_millis(200)
245 - zx::MonotonicDuration::from_nanos(50 * 200))
246 .into_nanos(),
247 );
248 assert_eq!(
249 transform
250 .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
251 .into_nanos(),
252 (TEST_REFERENCE + TEST_OFFSET - zx::MonotonicDuration::from_millis(100)
253 + zx::MonotonicDuration::from_nanos(50 * 100))
254 .into_nanos(),
255 );
256
257 assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
258 assert_eq!(
259 transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_seconds(1)),
260 TEST_ERROR_BOUND + (TEST_ERROR_BOUND_GROWTH * 1000) as u64
261 );
262 assert_eq!(
263 transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_seconds(1)),
264 TEST_ERROR_BOUND as u64
265 );
266 }
267
268 #[fuchsia::test]
269 fn transform_difference() {
270 let transform_1 = Transform {
271 reference_offset: TEST_REFERENCE,
272 synthetic_offset: zx::SyntheticInstant::from_nanos(
273 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
274 ),
275 rate_adjust_ppm: 25,
276 error_bound_at_offset: TEST_ERROR_BOUND,
277 error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
278 };
279
280 let transform_2 = Transform {
281 reference_offset: TEST_REFERENCE,
282 synthetic_offset: zx::SyntheticInstant::from_nanos(TEST_REFERENCE.into_nanos()),
283 rate_adjust_ppm: -50,
284 error_bound_at_offset: TEST_ERROR_BOUND,
285 error_bound_growth_ppm: 0,
286 };
287
288 assert_eq!(
289 transform_1.difference(&transform_1, TEST_REFERENCE),
290 zx::SyntheticDuration::from_nanos(0)
291 );
292 assert_eq!(
293 transform_1.difference(&transform_2, TEST_REFERENCE),
294 zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos())
295 );
296 assert_eq!(
297 transform_2.difference(&transform_1, TEST_REFERENCE),
298 zx::SyntheticDuration::from_nanos(-TEST_OFFSET.into_nanos())
299 );
300 assert_eq!(
301 transform_1
302 .difference(&transform_2, TEST_REFERENCE + zx::MonotonicDuration::from_millis(500)),
303 zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos() + 75 * 500)
304 );
305 assert_eq!(
306 transform_1
307 .difference(&transform_2, TEST_REFERENCE - zx::MonotonicDuration::from_millis(300)),
308 zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos() - 75 * 300)
309 );
310 }
311
312 #[fuchsia::test]
313 fn transform_conversion() {
314 let transform = Transform {
315 reference_offset: TEST_REFERENCE,
316 synthetic_offset: zx::SyntheticInstant::from_nanos(
317 (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
318 ),
319 rate_adjust_ppm: -15,
320 error_bound_at_offset: TEST_ERROR_BOUND,
321 error_bound_growth_ppm: 0,
322 };
323
324 let monotonic = zx::MonotonicInstant::get();
325 let clock_update = transform.jump_to(monotonic);
326 assert_eq!(
327 clock_update,
328 zx::ClockUpdate::builder()
329 .absolute_value(monotonic, transform.synthetic(monotonic))
330 .rate_adjust(-15)
331 .error_bounds(transform.error_bound(monotonic))
332 .build()
333 );
334
335 let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), None).unwrap();
336 clock.update(clock_update).unwrap();
337
338 let double_converted = Transform::from(&clock);
339 assert_eq!(double_converted.rate_adjust_ppm, transform.rate_adjust_ppm);
340 assert_eq!(double_converted.error_bound_at_offset, transform.error_bound_at_offset);
341 assert_eq!(double_converted.error_bound_growth_ppm, 0);
342 assert_eq!(double_converted.rate_adjust_ppm, transform.rate_adjust_ppm);
343 let synthetic_from_double_converted = double_converted.synthetic(TEST_REFERENCE);
345 assert_geq!(
346 synthetic_from_double_converted.into_nanos(),
347 (TEST_REFERENCE + TEST_OFFSET - TOLERANCE).into_nanos()
348 );
349 assert_leq!(
350 synthetic_from_double_converted.into_nanos(),
351 (TEST_REFERENCE + TEST_OFFSET + TOLERANCE).into_nanos()
352 );
353 }
354
355 #[fuchsia::test]
356 fn time_at_monotonic_clock_not_started() {
357 let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
358 assert_eq!(time_at_monotonic(&clock, zx::MonotonicInstant::get() + TIME_DIFF), BACKSTOP);
359 }
360
361 #[fuchsia::test]
362 fn time_at_monotonic_clock_started() {
363 let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
364
365 let mono = zx::MonotonicInstant::get();
366 clock.update(zx::ClockUpdate::builder().absolute_value(mono, BACKSTOP)).unwrap();
367
368 let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
369 assert_eq!(
370 clock_time,
371 BACKSTOP + zx::SyntheticDuration::from_nanos(TIME_DIFF.into_nanos())
372 );
373 }
374
375 #[fuchsia::test]
376 fn time_at_monotonic_clock_slew_fast() {
377 let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
378
379 let mono = zx::MonotonicInstant::get();
380 clock
381 .update(
382 zx::ClockUpdate::builder()
383 .absolute_value(mono, BACKSTOP)
384 .rate_adjust(SLEW_RATE_PPM),
385 )
386 .unwrap();
387
388 let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
389 assert_eq!(
390 clock_time,
391 BACKSTOP
392 + zx::SyntheticDuration::from_nanos(
393 (TIME_DIFF * (ONE_MILLION + SLEW_RATE_PPM) / ONE_MILLION).into_nanos()
394 )
395 );
396 }
397
398 #[fuchsia::test]
399 fn time_at_monotonic_clock_slew_slow() {
400 let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
401
402 let mono = zx::MonotonicInstant::get();
403 clock
404 .update(
405 zx::ClockUpdate::builder()
406 .absolute_value(mono, BACKSTOP)
407 .rate_adjust(-SLEW_RATE_PPM),
408 )
409 .unwrap();
410
411 let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
412 assert_eq!(
413 clock_time,
414 BACKSTOP
415 + zx::SyntheticDuration::from_nanos(
416 (TIME_DIFF * (ONE_MILLION - SLEW_RATE_PPM) / ONE_MILLION).into_nanos()
417 )
418 );
419 }
420}