time_persistence/
lib.rs

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.
4
5//! Testing-only code for persistent Timekeeper behavior changes around
6//! real-time clock (RTC) handling.
7
8use anyhow::Result;
9use fuchsia_runtime::UtcTimeline;
10use log::{debug, error};
11use serde::{Deserialize, Serialize};
12use std::fs;
13use std::path::Path;
14
15/// The path to the internal production persistent state file.
16const PERSISTENT_STATE_PATH: &'static str = "/data/persistent_state.json";
17
18/// How many reboots should the setting persist for?
19const REBOOT_COUNT: u8 = 1;
20
21/// 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.
29    num_reboots_until_rtc_write_allowed: u8,
30
31    // If set to nonzero, this is the reference instant on the boot timeline
32    // used for estimating UTC.
33    //
34    // This reverence value MUST be either from this or the immediately preceding boot.
35    // This means that we must update this value at least once on each boot to
36    // ensure correct handling of the UTC timeline recovery from RTC.
37    #[serde(default)]
38    reference_boot_instant_ns: i64,
39
40    // If set to nonzero, this is the UTC instant corresponding to
41    // `reference_boot_instant_ns` above.
42    #[serde(default)]
43    reference_utc_instant_ns: i64,
44}
45
46impl State {
47    pub fn new(update_rtc: bool) -> Self {
48        let mut value = Self { ..Default::default() };
49        value.set_may_update_rtc(update_rtc);
50        value
51    }
52
53    /// Returns whether updating the RTC clock is allowed.
54    pub fn may_update_rtc(&self) -> bool {
55        self.num_reboots_until_rtc_write_allowed == 0
56    }
57
58    pub fn set_may_update_rtc(&mut self, update_rtc: bool) {
59        self.num_reboots_until_rtc_write_allowed = match update_rtc {
60            true => 0,
61            // Limit the validity of the setting, expressed in number of
62            // reboots.
63            false => REBOOT_COUNT,
64        };
65    }
66
67    /// Gets a known-good reference point on the boot-to-utc affine transform.
68    pub fn get_rtc_reference(&self) -> (zx::BootInstant, zx::Instant<UtcTimeline>) {
69        let reference_boot = zx::BootInstant::from_nanos(self.reference_boot_instant_ns);
70        let reference_utc = zx::Instant::from_nanos(self.reference_utc_instant_ns);
71        (reference_boot, reference_utc)
72    }
73
74    /// Sets a known-good reference point on the boot-to-utc affine transform.
75    ///
76    /// This setting can be used to recover a known-good UTC estimate.
77    ///
78    /// # Args
79    ///
80    /// - `boot_reference`: a reference instant on the boot timeline.
81    /// - `utc_reference`: a reference instant on the UTC timeline that corresponds
82    ///   to `boot_timeline`.
83    pub fn set_rtc_reference(
84        &mut self,
85        boot_reference: zx::BootInstant,
86        utc_reference: zx::Instant<UtcTimeline>,
87    ) {
88        self.reference_boot_instant_ns = boot_reference.into_nanos();
89        self.reference_utc_instant_ns = utc_reference.into_nanos();
90    }
91
92    // Change the reboot limit.
93    fn spend_one_reboot_count(&mut self) {
94        self.num_reboots_until_rtc_write_allowed =
95            self.num_reboots_until_rtc_write_allowed.saturating_sub(1);
96    }
97
98    /// Reads the persistent state, updating the testing-only components.
99    pub fn read_and_update() -> Result<Self> {
100        Self::read_and_update_internal(PERSISTENT_STATE_PATH)
101    }
102
103    pub fn read_and_update_internal<P: AsRef<Path>>(path: P) -> Result<Self> {
104        let path = path.as_ref();
105        let maybe_state_str: String = fs::read_to_string(path).map_err(|e| {
106            debug!("while reading: {:?}", e);
107            e
108        })?;
109        let state: State = serde_json::from_str(&maybe_state_str).map_err(|e| {
110            debug!("while deserializing: {:?}", e);
111            e
112        })?;
113        // TODO(b/478927688): Convert to `debug!` once fix is confirmed.
114        log::info!("read persistent RTC state: {state:?}");
115        let mut next = state.clone();
116        next.spend_one_reboot_count();
117        Self::write_internal(path, &next)?;
118        Ok(state)
119    }
120
121    /// Write the persistent state to mutable persistent storage.
122    pub fn write(state: &Self) -> Result<()> {
123        Self::write_internal(PERSISTENT_STATE_PATH, state)
124    }
125
126    pub fn write_internal<P: AsRef<Path>>(path: P, state: &Self) -> Result<()> {
127        let path = path.as_ref();
128        // TODO(b/478927688): Convert to `debug!` once fix is confirmed.
129        log::info!("writing persistent RTC state: {:?} to {:?}", state, path);
130        let state_str = serde_json::to_string(state).map_err(|e| {
131            error!("while serializing state: {:?}", e);
132            e
133        })?;
134        fs::write(path, state_str).map_err(|e| {
135            error!("while writing persistent state: {:?}: {:?}", path, e);
136            e.into()
137        })
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_roundtrip() {
147        let d = tempfile::TempDir::new().expect("tempdir created");
148        let p = d.path().join("file.json");
149        let s = State::new(false);
150        State::write_internal(&p, &s).unwrap();
151
152        // First time around, we may not update the RTC.
153        assert!(!State::read_and_update_internal(&p).unwrap().may_update_rtc());
154        // Second time around, we may.
155        assert!(State::read_and_update_internal(&p).unwrap().may_update_rtc());
156    }
157}