blob: a085c12c914e686d0801ed2949b4177996e51c26 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fuchsia_zircon as zx;
use std::cmp;
/// An estimate of the worst possible time drift in the device's oscillator.
const MAX_DRIFT_PPM: i64 = 200;
const ONE_MILLION: i64 = 1_000_000;
/// A representation of the range of possible UTC times at an instant of monotonic time.
#[derive(Debug, PartialEq)]
pub struct Bound {
/// Monotonic time for which the bound is valid.
pub monotonic: zx::Time,
/// The minimum possible UTC time.
pub utc_min: zx::Time,
/// The maximum possible UTC time.
pub utc_max: zx::Time,
}
impl Bound {
/// Take the intersection of two bounds to produce a (possibly) tighter bound. Returns None if
/// the bounds do not intersect.
pub fn combine(&self, other: &Bound) -> Option<Bound> {
let (earlier, later) =
if self.monotonic < other.monotonic { (self, other) } else { (other, self) };
let projected = earlier.project(later.monotonic);
let combined = Bound {
monotonic: later.monotonic,
utc_min: cmp::max(later.utc_min, projected.utc_min),
utc_max: cmp::min(later.utc_max, projected.utc_max),
};
if combined.utc_min > combined.utc_max {
None
} else {
Some(combined)
}
}
/// Project a bound to a different monotonic time. The bound is expanded by the time
/// elapsed between the bound and the provided monotonic time multiplied by the drift.
fn project(&self, later_monotonic: zx::Time) -> Bound {
let time_delta = later_monotonic - self.monotonic;
let max_drift = (time_delta * MAX_DRIFT_PPM) / ONE_MILLION;
Bound {
monotonic: later_monotonic,
utc_min: self.utc_min + time_delta - max_drift,
utc_max: self.utc_max + time_delta + max_drift,
}
}
/// Returns the size of the possible range.
pub fn size(&self) -> zx::Duration {
self.utc_max - self.utc_min
}
/// Returns the center of the possible UTC range.
pub fn center(&self) -> zx::Time {
self.utc_min + self.size() / 2
}
}
#[cfg(test)]
mod test {
use super::*;
use lazy_static::lazy_static;
const DURATION_MICROS_500: zx::Duration = zx::Duration::from_micros(500);
const MONOTONIC_TIME: zx::Time = zx::Time::from_nanos(1_000_000_000_000);
lazy_static! {
static ref DRIFT_IN_500_MICROS: zx::Duration =
DURATION_MICROS_500 * MAX_DRIFT_PPM / ONE_MILLION;
}
fn assert_combine_commutative(bound_1: &Bound, bound_2: &Bound) {
assert_eq!(bound_1.combine(bound_2), bound_2.combine(bound_1))
}
#[fuchsia::test]
fn combine_bounds_with_same_monotonic_times() {
let earlier_utc_bound = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let later_utc_bound = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_500_000),
utc_max: zx::Time::from_nanos(2_500_000),
};
assert_eq!(
earlier_utc_bound.combine(&later_utc_bound).unwrap(),
Bound {
monotonic: MONOTONIC_TIME,
utc_min: later_utc_bound.utc_min,
utc_max: earlier_utc_bound.utc_max,
}
);
assert_combine_commutative(&earlier_utc_bound, &later_utc_bound);
let enclosing_utc_bound = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let enclosed_utc_bound = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_200_000),
utc_max: zx::Time::from_nanos(1_800_000),
};
assert_eq!(enclosed_utc_bound.combine(&enclosing_utc_bound).unwrap(), enclosed_utc_bound);
assert_combine_commutative(&enclosed_utc_bound, &enclosing_utc_bound);
}
#[fuchsia::test]
fn combine_bounds_with_different_monotonic_times() {
let earlier = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let later = Bound {
monotonic: MONOTONIC_TIME + DURATION_MICROS_500,
utc_min: zx::Time::from_nanos(2_000_000),
utc_max: zx::Time::from_nanos(3_000_000),
};
assert_eq!(
earlier.combine(&later).unwrap(),
Bound {
monotonic: later.monotonic,
utc_min: later.utc_min,
utc_max: earlier.utc_max + DURATION_MICROS_500 + *DRIFT_IN_500_MICROS
}
);
assert_combine_commutative(&earlier, &later);
let earlier_enclosing = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let later_enclosed = Bound {
monotonic: MONOTONIC_TIME + DURATION_MICROS_500,
utc_min: zx::Time::from_nanos(1_700_000),
utc_max: zx::Time::from_nanos(2_300_000),
};
assert_eq!(earlier_enclosing.combine(&later_enclosed).unwrap(), later_enclosed);
assert_combine_commutative(&earlier_enclosing, &later_enclosed);
let earlier_enclosed = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_200_000),
utc_max: zx::Time::from_nanos(1_800_000),
};
let later_enclosing = Bound {
monotonic: MONOTONIC_TIME + DURATION_MICROS_500,
utc_min: zx::Time::from_nanos(1_500_000),
utc_max: zx::Time::from_nanos(2_500_000),
};
assert_eq!(
earlier_enclosed.combine(&later_enclosing).unwrap(),
Bound {
monotonic: later_enclosing.monotonic,
utc_min: earlier_enclosed.utc_min + DURATION_MICROS_500 - *DRIFT_IN_500_MICROS,
utc_max: earlier_enclosed.utc_max + DURATION_MICROS_500 + *DRIFT_IN_500_MICROS,
}
);
assert_combine_commutative(&earlier_enclosed, &later_enclosing);
}
#[fuchsia::test]
fn combine_bounds_no_overlap() {
let earlier_utc = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let later_utc = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(2_500_000),
utc_max: zx::Time::from_nanos(3_500_000),
};
assert!(earlier_utc.combine(&later_utc).is_none());
assert_combine_commutative(&earlier_utc, &later_utc);
let earlier = Bound {
monotonic: MONOTONIC_TIME,
utc_min: zx::Time::from_nanos(1_000_000),
utc_max: zx::Time::from_nanos(2_000_000),
};
let later = Bound {
monotonic: MONOTONIC_TIME + DURATION_MICROS_500,
utc_min: zx::Time::from_nanos(2_600_000),
utc_max: zx::Time::from_nanos(3_600_000),
};
assert!(earlier.combine(&later).is_none());
assert_combine_commutative(&earlier, &later);
}
}