1// Copyright 2024 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.
45//! Testing-only code for persistent Timekeeper behavior changes around
6//! real-time clock (RTC) handling.
78use anyhow::Result;
9use fuchsia_runtime::UtcTimeline;
10use log::{debug, error};
11use serde::{Deserialize, Serialize};
12use std::fs;
13use std::path::Path;
1415/// The path to the internal production persistent state file.
16const PERSISTENT_STATE_PATH: &'static str = "/data/persistent_state.json";
1718/// How many reboots should the setting persist for?
19const REBOOT_COUNT: u8 = 1;
2021/// The persistent Timekeeper state.
22///
23/// See the method documentation for the specific components of the
24/// state that is being persisted.
25#[derive(Serialize, Deserialize, Debug, Default, Clone)]
26pub struct State {
27// This many reboots remain until we're allowed to write to RTC again. This
28 // counter is decreased by Timekeeper on each restart, until zero is reached.
29num_reboots_until_rtc_write_allowed: u8,
3031// If set to nonzero, this is the reference instant on the boot timeline
32 // used for estimating UTC.
33#[serde(default)]
34reference_boot_instant_ns: i64,
3536// If set to nonzero, this is the UTC instant corresponding to
37 // `reference_boot_instant_ns` above.
38#[serde(default)]
39reference_utc_instant_ns: i64,
40}
4142impl State {
43pub fn new(update_rtc: bool) -> Self {
44let mut value = Self { ..Default::default() };
45 value.set_may_update_rtc(update_rtc);
46 value
47 }
4849/// Returns whether updating the RTC clock is allowed.
50pub fn may_update_rtc(&self) -> bool {
51self.num_reboots_until_rtc_write_allowed == 0
52}
5354pub fn set_may_update_rtc(&mut self, update_rtc: bool) {
55self.num_reboots_until_rtc_write_allowed = match update_rtc {
56true => 0,
57// Limit the validity of the setting, expressed in number of
58 // reboots.
59false => REBOOT_COUNT,
60 };
61 }
6263/// Gets a known-good reference point on the boot-to-utc affine transform.
64pub fn get_rtc_reference(&self) -> (zx::BootInstant, zx::Instant<UtcTimeline>) {
65let reference_boot = zx::BootInstant::from_nanos(self.reference_boot_instant_ns);
66let reference_utc = zx::Instant::from_nanos(self.reference_utc_instant_ns);
67 (reference_boot, reference_utc)
68 }
6970/// Sets a known-good reference point on the boot-to-utc affine transform.
71 ///
72 /// This setting can be used to recover a known-good UTC estimate.
73 ///
74 /// # Args
75 ///
76 /// - `boot_reference`: a reference instant on the boot timeline.
77 /// - `utc_reference`: a reference instant on the UTC timeline that corresponds
78 /// to `boot_timeline`.
79pub fn set_rtc_reference(
80&mut self,
81 boot_reference: zx::BootInstant,
82 utc_reference: zx::Instant<UtcTimeline>,
83 ) {
84self.reference_boot_instant_ns = boot_reference.into_nanos();
85self.reference_utc_instant_ns = utc_reference.into_nanos();
86 }
8788// Change the reboot limit.
89fn spend_one_reboot_count(&mut self) {
90self.num_reboots_until_rtc_write_allowed =
91self.num_reboots_until_rtc_write_allowed.saturating_sub(1);
92 }
9394/// Reads the persistent state, updating the testing-only components.
95pub fn read_and_update() -> Result<Self> {
96Self::read_and_update_internal(PERSISTENT_STATE_PATH)
97 }
9899pub fn read_and_update_internal<P: AsRef<Path>>(path: P) -> Result<Self> {
100let path = path.as_ref();
101let maybe_state_str: String = fs::read_to_string(path).map_err(|e| {
102debug!("while reading: {:?}", e);
103 e
104 })?;
105let state: State = serde_json::from_str(&maybe_state_str).map_err(|e| {
106debug!("while deserializing: {:?}", e);
107 e
108 })?;
109debug!("read persistent state: {:?}", &state);
110let mut next = state.clone();
111 next.spend_one_reboot_count();
112Self::write_internal(path, &next)?;
113Ok(state)
114 }
115116/// Write the persistent state to mutable persistent storage.
117pub fn write(state: &Self) -> Result<()> {
118Self::write_internal(PERSISTENT_STATE_PATH, state)
119 }
120121pub fn write_internal<P: AsRef<Path>>(path: P, state: &Self) -> Result<()> {
122let path = path.as_ref();
123debug!("writing persistent state: {:?} to {:?}", state, path);
124let state_str = serde_json::to_string(state).map_err(|e| {
125error!("while serializing state: {:?}", e);
126 e
127 })?;
128 fs::write(path, state_str).map_err(|e| {
129error!("while writing persistent state: {:?}: {:?}", path, e);
130 e.into()
131 })
132 }
133}
134135#[cfg(test)]
136mod tests {
137use super::*;
138139#[test]
140fn test_roundtrip() {
141let d = tempfile::TempDir::new().expect("tempdir created");
142let p = d.path().join("file.json");
143let s = State::new(false);
144 State::write_internal(&p, &s).unwrap();
145146// First time around, we may not update the RTC.
147assert!(!State::read_and_update_internal(&p).unwrap().may_update_rtc());
148// Second time around, we may.
149assert!(State::read_and_update_internal(&p).unwrap().may_update_rtc());
150 }
151}