Skip to main content

zx/
clock.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Type-safe bindings for Zircon clock objects.
6
7use crate::{
8    BootTimeline, ClockUpdate, Instant, MonotonicTimeline, NullableHandle, ObjectQuery,
9    SyntheticTimeline, Ticks, Timeline, Topic, ok, sys,
10};
11use bitflags::bitflags;
12use std::mem::MaybeUninit;
13use std::ptr;
14use zx_status::Status;
15
16/// An object representing a kernel [clock], used to track the progress of time. A clock is a
17/// one-dimensional affine transformation of the [clock monotonic] reference timeline which may be
18/// atomically adjusted by a maintainer and observed by clients.
19///
20/// As essentially a subtype of `NullableHandle`, it can be freely interconverted.
21///
22/// [clock]: https://fuchsia.dev/fuchsia-src/reference/kernel_objects/clock
23/// [clock monotonic]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_get_monotonic.md
24#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
25#[repr(transparent)]
26pub struct Clock<Reference = MonotonicTimeline, Output = SyntheticTimeline>(
27    NullableHandle,
28    std::marker::PhantomData<(Reference, Output)>,
29);
30
31impl_handle_based!(Clock, [Reference: Timeline, Output: Timeline], [Reference, Output]);
32
33pub type SyntheticClock = Clock<MonotonicTimeline, SyntheticTimeline>;
34pub type SyntheticClockOnBoot = Clock<BootTimeline, SyntheticTimeline>;
35
36impl<Output: Timeline> Clock<MonotonicTimeline, Output> {
37    /// Create a new clock object with the provided arguments, with the monotonic clock as the
38    /// reference timeline. Wraps the [zx_clock_create] syscall.
39    ///
40    /// [zx_clock_create]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_create
41    pub fn create(opts: ClockOpts, backstop: Option<Instant<Output>>) -> Result<Self, Status> {
42        let mut out = 0;
43        let status = match backstop {
44            Some(backstop) => {
45                // When using backstop time, use the API v1 args struct.
46                let args = sys::zx_clock_create_args_v1_t { backstop_time: backstop.into_nanos() };
47                unsafe {
48                    sys::zx_clock_create(
49                        sys::ZX_CLOCK_ARGS_VERSION_1 | opts.bits(),
50                        std::ptr::from_ref(&args).cast::<u8>(),
51                        &mut out,
52                    )
53                }
54            }
55            None => unsafe { sys::zx_clock_create(opts.bits(), ptr::null(), &mut out) },
56        };
57        ok(status)?;
58        unsafe { Ok(Self::from(NullableHandle::from_raw(out))) }
59    }
60}
61
62impl<Output: Timeline> Clock<BootTimeline, Output> {
63    /// Create a new clock object with the provided arguments, with the boot clock as the reference
64    /// timeline. Wraps the [zx_clock_create] syscall.
65    ///
66    /// [zx_clock_create]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_create
67    pub fn create(opts: ClockOpts, backstop: Option<Instant<Output>>) -> Result<Self, Status> {
68        let mut out = 0;
69        let opts = opts | ClockOpts::BOOT;
70        let status = match backstop {
71            Some(backstop) => {
72                // When using backstop time, use the API v1 args struct.
73                let args = sys::zx_clock_create_args_v1_t { backstop_time: backstop.into_nanos() };
74                unsafe {
75                    sys::zx_clock_create(
76                        sys::ZX_CLOCK_ARGS_VERSION_1 | opts.bits(),
77                        &args as *const _ as *const u8,
78                        &mut out,
79                    )
80                }
81            }
82            None => unsafe { sys::zx_clock_create(opts.bits(), ptr::null(), &mut out) },
83        };
84        ok(status)?;
85        unsafe { Ok(Self::from(NullableHandle::from_raw(out))) }
86    }
87}
88
89impl<Reference: Timeline, Output: Timeline> Clock<Reference, Output> {
90    /// Perform a basic read of this clock. Wraps the [zx_clock_read] syscall. Requires
91    /// `ZX_RIGHT_READ` and that the clock has had an initial time established.
92    ///
93    /// [zx_clock_read]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_read
94    pub fn read(&self) -> Result<Instant<Output>, Status> {
95        let mut now = 0;
96        let status = unsafe { sys::zx_clock_read(self.raw_handle(), &mut now) };
97        ok(status)?;
98        Ok(Instant::<Output>::from_nanos(now))
99    }
100
101    /// Perform a basic read of a mapped clock. Wraps the [zx_clock_read_mapped] syscall.
102    ///
103    /// # Safety
104    ///
105    /// |zx_clock_get_details_mapped| must only be called using a |clock_addr| which is the address
106    /// of a clock's state currently mapped into the caller's address space using
107    /// |zx_vmar_map_clock|.  Attempting to get_details from any other address, or from a clock
108    /// mapping which has been unmapped (completely or partially) will result in undefined behavior.
109    ///
110    /// [zx_clock_read]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_read_mapped
111    pub unsafe fn read_mapped(clock_addr: usize) -> Result<Instant<Output>, Status> {
112        let mut now = 0;
113        let status = unsafe {
114            // SAFETY: see function docs above.
115            sys::zx_clock_read_mapped(clock_addr as *const u8, &mut now)
116        };
117        ok(status)?;
118        Ok(Instant::<Output>::from_nanos(now))
119    }
120
121    /// Get low level details of this clock's current status. Wraps the
122    /// [zx_clock_get_details] syscall. Requires `ZX_RIGHT_READ`.
123    ///
124    /// [zx_clock_get_details]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_get_details
125    pub fn get_details(&self) -> Result<ClockDetails<Reference, Output>, Status> {
126        let mut out_details = MaybeUninit::<sys::zx_clock_details_v1_t>::uninit();
127        let status = unsafe {
128            sys::zx_clock_get_details(
129                self.raw_handle(),
130                sys::ZX_CLOCK_ARGS_VERSION_1,
131                out_details.as_mut_ptr().cast::<u8>(),
132            )
133        };
134        ok(status)?;
135        let out_details = unsafe { out_details.assume_init() };
136        Ok(out_details.into())
137    }
138
139    /// Get low level details of a mapped clock's current status. Wraps the
140    /// [zx_clock_get_details_mapped] syscall.
141    ///
142    /// # Safety
143    ///
144    /// |zx_clock_get_details_mapped| must only be called using a |clock_addr| which is the address
145    /// of a clock's state currently mapped into the caller's address space using
146    /// |zx_vmar_map_clock|.  Attempting to get_details from any other address, or from a clock
147    /// mapping which has been unmapped (completely or partially) will result in undefined behavior.
148    ///
149    /// [zx_clock_get_details_mapped]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_get_details_mapped
150    pub unsafe fn get_details_mapped(
151        clock_addr: usize,
152    ) -> Result<ClockDetails<Reference, Output>, Status> {
153        let mut out_details = MaybeUninit::<sys::zx_clock_details_v1_t>::uninit();
154        let status = unsafe {
155            // SAFETY: see safety notes in the function docs above.
156            sys::zx_clock_get_details_mapped(
157                clock_addr as *const u8,
158                sys::ZX_CLOCK_ARGS_VERSION_1,
159                out_details.as_mut_ptr().cast::<u8>(),
160            )
161        };
162        ok(status)?;
163        let out_details = unsafe { out_details.assume_init() };
164        Ok(out_details.into())
165    }
166
167    // Returns the memory size for the memory region used to store clock details.
168    pub fn get_mapped_size(clock: &Clock<Reference, Output>) -> Result<usize, Status> {
169        Ok(clock.0.get_info_single::<MappedSizeQuery>()?)
170    }
171
172    /// Make adjustments to this clock. Wraps the [zx_clock_update] syscall. Requires
173    /// `ZX_RIGHT_WRITE`.
174    ///
175    /// [zx_clock_update]: https://fuchsia.dev/fuchsia-src/reference/syscalls/clock_update
176    pub fn update(&self, update: impl Into<ClockUpdate<Reference, Output>>) -> Result<(), Status> {
177        let update = update.into();
178        let options = update.options();
179        let args = update.args();
180        let status = unsafe {
181            sys::zx_clock_update(self.raw_handle(), options, std::ptr::from_ref(&args).cast::<u8>())
182        };
183        ok(status)?;
184        Ok(())
185    }
186
187    /// Convert this clock to one on a generic synthetic timeline, erasing any user-defined
188    /// timeline.
189    pub fn downcast<NewReference: Timeline>(self) -> Clock<NewReference, SyntheticTimeline> {
190        Clock(self.0, std::marker::PhantomData)
191    }
192}
193
194impl<Reference: Timeline> Clock<Reference, SyntheticTimeline> {
195    /// Cast a "base" clock to one with a user-defined timeline that will carry the timeline for
196    /// all transformations and reads.
197    pub fn cast<NewReference: Timeline, UserTimeline: Timeline>(
198        self,
199    ) -> Clock<NewReference, UserTimeline> {
200        Clock(self.0, std::marker::PhantomData)
201    }
202}
203
204struct MappedSizeQuery;
205unsafe impl ObjectQuery for MappedSizeQuery {
206    const TOPIC: Topic = Topic::CLOCK_MAPPED_SIZE;
207    type InfoTy = usize;
208}
209
210bitflags! {
211    #[repr(transparent)]
212    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
213    pub struct ClockOpts: u64 {
214        /// When set, creates a clock object which is guaranteed to never run backwards. Monotonic
215        /// clocks must always move forward.
216        const MONOTONIC = sys::ZX_CLOCK_OPT_MONOTONIC;
217
218        /// When set, creates a clock which is guaranteed to never jump either forwards or
219        /// backwards. Continuous clocks may only be maintained using frequency adjustments and are,
220        /// by definition, also monotonic.
221        const CONTINUOUS = sys::ZX_CLOCK_OPT_CONTINUOUS | Self::MONOTONIC.bits();
222
223        /// When set, creates a clock that is automatically started and is initially a clone of
224        /// clock monotonic. Users may still update the clock within the limits defined by the
225        /// other options, the handle rights, and the backstop time of the clock.
226        const AUTO_START = sys::ZX_CLOCK_OPT_AUTO_START;
227
228        /// When set, creates a clock object that uses the boot timeline as its reference timeline.
229        const BOOT = sys::ZX_CLOCK_OPT_BOOT;
230
231        /// When set, creates a clock whose state may be mapped into a VMAR, allowing the state to
232        /// be used with calls to |zx_clock_read_mapped| and |zx_clock_get_details_mapped|.
233        const MAPPABLE = sys::ZX_CLOCK_OPT_MAPPABLE;
234    }
235}
236
237/// Fine grained details of a [`Clock`] object.
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct ClockDetails<Reference = MonotonicTimeline, Output = SyntheticTimeline> {
240    /// The minimum time the clock can ever be set to.
241    pub backstop: Instant<Output>,
242
243    /// The current ticks to clock transformation.
244    pub ticks_to_synthetic: ClockTransformation<Reference, Output>,
245
246    /// The current clock monotonic to clock transformation.
247    pub reference_to_synthetic: ClockTransformation<Reference, Output>,
248
249    /// The current symmetric error estimate (if any) for the clock, measured in nanoseconds.
250    pub error_bounds: u64,
251
252    /// An observation of the system tick counter which was taken during the observation of the
253    /// clock.
254    pub query_ticks: Ticks<Reference>,
255
256    /// The last time the clock's value was updated.
257    pub last_value_update_ticks: Ticks<Reference>,
258
259    /// The last time the clock's rate adjustment was updated.
260    pub last_rate_adjust_update_ticks: Ticks<Reference>,
261
262    /// The last time the clock's error bounds were updated.
263    pub last_error_bounds_update_ticks: Ticks<Reference>,
264
265    /// The generation nonce.
266    pub generation_counter: u32,
267}
268
269impl<Reference: Timeline, Output: Timeline> From<sys::zx_clock_details_v1_t>
270    for ClockDetails<Reference, Output>
271{
272    fn from(details: sys::zx_clock_details_v1_t) -> Self {
273        ClockDetails {
274            backstop: Instant::from_nanos(details.backstop_time),
275            ticks_to_synthetic: details.reference_ticks_to_synthetic.into(),
276            reference_to_synthetic: details.reference_to_synthetic.into(),
277            error_bounds: details.error_bound,
278            query_ticks: Ticks::from_raw(details.query_ticks),
279            last_value_update_ticks: Ticks::from_raw(details.last_value_update_ticks),
280            last_rate_adjust_update_ticks: Ticks::from_raw(details.last_rate_adjust_update_ticks),
281            last_error_bounds_update_ticks: Ticks::from_raw(details.last_error_bounds_update_ticks),
282            generation_counter: details.generation_counter,
283        }
284    }
285}
286
287/// A one-dimensional affine transformation that maps points from the reference timeline to the
288/// clock timeline. See [clock transformations].
289///
290/// [clock transformations]: https://fuchsia.dev/fuchsia-src/concepts/kernel/clock_transformations
291#[derive(Debug, Clone, Eq, PartialEq)]
292pub struct ClockTransformation<Reference = MonotonicTimeline, Output = SyntheticTimeline> {
293    /// The offset on the reference timeline, measured in reference clock ticks.
294    pub reference_offset: Instant<Reference>,
295    /// The offset on the clock timeline, measured in clock ticks (typically normalized to
296    /// nanoseconds).
297    pub synthetic_offset: Instant<Output>,
298    /// The ratio of the reference to clock rate.
299    pub rate: sys::zx_clock_rate_t,
300}
301
302impl<Reference: Timeline, Output: Timeline> From<sys::zx_clock_transformation_t>
303    for ClockTransformation<Reference, Output>
304{
305    fn from(ct: sys::zx_clock_transformation_t) -> Self {
306        ClockTransformation {
307            reference_offset: Instant::<Reference>::from_nanos(ct.reference_offset),
308            synthetic_offset: Instant::<Output>::from_nanos(ct.synthetic_offset),
309            rate: ct.rate,
310        }
311    }
312}
313
314// Apply affine transformation to convert the reference time "r" to the
315// synthetic time "c".
316//
317// All values are widened to i128 before calculations and the end result is
318// converted back to a i64. If "c" is a larger number than would fit in an i64,
319// the result saturates when cast to i64.
320//
321// # Panics
322// If `r_rate` is zero, this function will panic.
323fn transform_clock(r: i64, r_offset: i64, c_offset: i64, r_rate: u32, c_rate: u32) -> i64 {
324    assert!(r_rate != 0, "r_rate may not be zero");
325    let r = r as i128;
326    let r_offset = r_offset as i128;
327    let c_offset = c_offset as i128;
328    let r_rate = r_rate as i128;
329    let c_rate = c_rate as i128;
330    let c = (((r - r_offset) * c_rate) / r_rate) + c_offset;
331    c.try_into().unwrap_or_else(|_| if c.is_positive() { i64::MAX } else { i64::MIN })
332}
333
334/// [Clock
335/// transformations](https://fuchsia.dev/fuchsia-src/concepts/kernel/clock_transformations)
336/// can be applied to convert a time from a reference time to a synthetic time.
337///
338/// The inverse transformation can be applied to convert a synthetic time back
339/// to the reference time.
340impl<Reference: Timeline + Copy, Output: Timeline + Copy> ClockTransformation<Reference, Output> {
341    /// Apply the transformation to convert a time from the reference timeline
342    /// to the output timeline.
343    ///
344    /// # Panics
345    /// If the reference clock rate is zero, this function will panic.
346    pub fn apply(&self, time: Instant<Reference>) -> Instant<Output> {
347        let c = transform_clock(
348            time.into_nanos(),
349            self.reference_offset.into_nanos(),
350            self.synthetic_offset.into_nanos(),
351            self.rate.reference_ticks,
352            self.rate.synthetic_ticks,
353        );
354
355        Instant::from_nanos(c)
356    }
357
358    /// Apply the transformation to convert a time from the output timeline
359    /// to the reference timeline.
360    ///
361    /// # Panics
362    /// If the output clock rate is zero, this function will panic.
363    pub fn apply_inverse(&self, time: Instant<Output>) -> Instant<Reference> {
364        let r = transform_clock(
365            time.into_nanos(),
366            self.synthetic_offset.into_nanos(),
367            self.reference_offset.into_nanos(),
368            self.rate.synthetic_ticks,
369            self.rate.reference_ticks,
370        );
371
372        Instant::from_nanos(r as i64)
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379    use crate::{MonotonicInstant, SyntheticInstant};
380    use assert_matches::assert_matches;
381
382    #[test]
383    fn create_clocks() {
384        assert_matches!(SyntheticClock::create(ClockOpts::empty(), None), Ok(_));
385        assert_matches!(SyntheticClock::create(ClockOpts::MONOTONIC, None), Ok(_));
386        assert_matches!(SyntheticClock::create(ClockOpts::CONTINUOUS, None), Ok(_));
387        assert_matches!(
388            SyntheticClock::create(ClockOpts::AUTO_START | ClockOpts::MONOTONIC, None),
389            Ok(_)
390        );
391        assert_matches!(
392            SyntheticClock::create(ClockOpts::AUTO_START | ClockOpts::CONTINUOUS, None),
393            Ok(_)
394        );
395
396        // Now with backstop.
397        let backstop = Some(SyntheticInstant::from_nanos(5500));
398        assert_matches!(SyntheticClock::create(ClockOpts::MONOTONIC, backstop), Ok(_));
399        assert_matches!(SyntheticClock::create(ClockOpts::CONTINUOUS, backstop), Ok(_));
400        assert_matches!(
401            SyntheticClock::create(ClockOpts::AUTO_START | ClockOpts::MONOTONIC, backstop),
402            Ok(_)
403        );
404        assert_matches!(
405            SyntheticClock::create(ClockOpts::AUTO_START | ClockOpts::CONTINUOUS, backstop),
406            Ok(_)
407        );
408    }
409
410    #[test]
411    fn read_time() {
412        let clock =
413            SyntheticClock::create(ClockOpts::MONOTONIC, None).expect("failed to create clock");
414        assert_matches!(clock.read(), Ok(_));
415    }
416
417    #[test]
418    fn get_clock_details() {
419        // No backstop.
420        let clock =
421            SyntheticClock::create(ClockOpts::MONOTONIC, None).expect("failed to create clock");
422        let details = clock.get_details().expect("failed to get details");
423        assert_eq!(details.backstop, SyntheticInstant::from_nanos(0));
424
425        // With backstop.
426        let clock =
427            SyntheticClock::create(ClockOpts::MONOTONIC, Some(SyntheticInstant::from_nanos(5500)))
428                .expect("failed to create clock");
429        let details = clock.get_details().expect("failed to get details");
430        assert_eq!(details.backstop, SyntheticInstant::from_nanos(5500));
431    }
432
433    #[test]
434    fn update_clock() {
435        let clock =
436            SyntheticClock::create(ClockOpts::MONOTONIC, None).expect("failed to create clock");
437        let before_details = clock.get_details().expect("failed to get details");
438        assert_eq!(before_details.last_value_update_ticks, Ticks::from_raw(0));
439        assert_eq!(before_details.last_rate_adjust_update_ticks, Ticks::from_raw(0));
440        assert_eq!(before_details.last_error_bounds_update_ticks, Ticks::from_raw(0));
441
442        // Update all properties.
443        clock
444            .update(
445                ClockUpdate::builder()
446                    .absolute_value(
447                        MonotonicInstant::from_nanos(999),
448                        SyntheticInstant::from_nanos(42),
449                    )
450                    .rate_adjust(52)
451                    .error_bounds(52),
452            )
453            .expect("failed to update clock");
454        let after_details = clock.get_details().expect("failed to get details");
455        assert!(before_details.generation_counter < after_details.generation_counter);
456        assert!(after_details.last_value_update_ticks > before_details.last_value_update_ticks);
457        assert_eq!(
458            after_details.last_value_update_ticks,
459            after_details.last_rate_adjust_update_ticks
460        );
461        assert_eq!(
462            after_details.last_value_update_ticks,
463            after_details.last_error_bounds_update_ticks
464        );
465        assert_eq!(after_details.error_bounds, 52);
466        assert_eq!(after_details.ticks_to_synthetic.synthetic_offset.into_nanos(), 42);
467        assert_eq!(after_details.reference_to_synthetic.reference_offset.into_nanos(), 999);
468        assert_eq!(after_details.reference_to_synthetic.synthetic_offset.into_nanos(), 42);
469
470        let before_details = after_details;
471
472        // Update only one property.
473        clock.update(ClockUpdate::builder().error_bounds(100)).expect("failed to update clock");
474        let after_details = clock.get_details().expect("failed to get details");
475        assert!(before_details.generation_counter < after_details.generation_counter);
476        assert!(
477            after_details.last_error_bounds_update_ticks > before_details.last_value_update_ticks
478        );
479        assert!(
480            after_details.last_error_bounds_update_ticks
481                > after_details.last_rate_adjust_update_ticks
482        );
483        assert_eq!(
484            after_details.last_rate_adjust_update_ticks,
485            after_details.last_value_update_ticks
486        );
487        assert_eq!(after_details.error_bounds, 100);
488        assert_eq!(after_details.ticks_to_synthetic.synthetic_offset.into_nanos(), 42);
489        assert_eq!(after_details.reference_to_synthetic.synthetic_offset.into_nanos(), 42);
490    }
491
492    #[test]
493    fn clock_identity_transformation_roundtrip() {
494        let t_0 = MonotonicInstant::ZERO;
495        // Identity clock transformation
496        let xform = ClockTransformation {
497            reference_offset: MonotonicInstant::from_nanos(0),
498            synthetic_offset: SyntheticInstant::from_nanos(0),
499            rate: sys::zx_clock_rate_t { synthetic_ticks: 1, reference_ticks: 1 },
500        };
501
502        // Transformation roundtrip should be equivalent with the identity transformation.
503        let transformed_time = xform.apply(t_0);
504        let original_time = xform.apply_inverse(transformed_time);
505        assert_eq!(t_0, original_time);
506    }
507
508    #[test]
509    fn clock_trivial_transformation() {
510        let t_0 = MonotonicInstant::ZERO;
511        // Identity clock transformation
512        let xform = ClockTransformation {
513            reference_offset: MonotonicInstant::from_nanos(3),
514            synthetic_offset: SyntheticInstant::from_nanos(2),
515            rate: sys::zx_clock_rate_t { synthetic_ticks: 6, reference_ticks: 2 },
516        };
517
518        let utc_time = xform.apply(t_0);
519        let monotonic_time = xform.apply_inverse(utc_time);
520        // Verify that the math is correct.
521        assert_eq!(3 * (t_0.into_nanos() - 3) + 2, utc_time.into_nanos());
522
523        // Transformation roundtrip should be equivalent.
524        assert_eq!(t_0, monotonic_time);
525    }
526
527    #[test]
528    fn clock_transformation_roundtrip() {
529        let t_0 = MonotonicInstant::ZERO;
530        // Arbitrary clock transformation
531        let xform = ClockTransformation {
532            reference_offset: MonotonicInstant::from_nanos(196980085208),
533            synthetic_offset: SyntheticInstant::from_nanos(1616900096031887801),
534            rate: sys::zx_clock_rate_t { synthetic_ticks: 999980, reference_ticks: 1000000 },
535        };
536
537        // Transformation roundtrip should be equivalent modulo rounding error.
538        let transformed_time = xform.apply(t_0);
539        let original_time = xform.apply_inverse(transformed_time);
540        let roundtrip_diff = t_0 - original_time;
541        assert!(roundtrip_diff.into_nanos().abs() <= 1);
542    }
543
544    #[test]
545    fn clock_trailing_transformation_roundtrip() {
546        let t_0 = MonotonicInstant::ZERO;
547        // Arbitrary clock transformation where the synthetic clock is trailing behind the
548        // reference clock.
549        let xform = ClockTransformation {
550            reference_offset: MonotonicInstant::from_nanos(1616900096031887801),
551            synthetic_offset: SyntheticInstant::from_nanos(196980085208),
552            rate: sys::zx_clock_rate_t { synthetic_ticks: 1000000, reference_ticks: 999980 },
553        };
554
555        // Transformation roundtrip should be equivalent modulo rounding error.
556        let transformed_time = xform.apply(t_0);
557        let original_time = xform.apply_inverse(transformed_time);
558        let roundtrip_diff = t_0 - original_time;
559        assert!(roundtrip_diff.into_nanos().abs() <= 1);
560    }
561
562    #[test]
563    fn clock_saturating_transformations() {
564        let t_0 = MonotonicInstant::from_nanos(i64::MAX);
565        // Clock transformation which will positively overflow t_0
566        let xform = ClockTransformation {
567            reference_offset: MonotonicInstant::from_nanos(0),
568            synthetic_offset: SyntheticInstant::from_nanos(1),
569            rate: sys::zx_clock_rate_t { synthetic_ticks: 1, reference_ticks: 1 },
570        };
571
572        // Applying the transformation will lead to saturation
573        let time = xform.apply(t_0).into_nanos();
574        assert_eq!(time, i64::MAX);
575
576        let t_0 = MonotonicInstant::from_nanos(i64::MIN);
577        // Clock transformation which will negatively overflow t_0
578        let xform = ClockTransformation {
579            reference_offset: MonotonicInstant::from_nanos(1),
580            synthetic_offset: SyntheticInstant::from_nanos(0),
581            rate: sys::zx_clock_rate_t { synthetic_ticks: 1, reference_ticks: 1 },
582        };
583
584        // Applying the transformation will lead to saturation
585        let time = xform.apply(t_0).into_nanos();
586        assert_eq!(time, i64::MIN);
587    }
588}