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    #[serde(default)]
34    reference_boot_instant_ns: i64,
35
36    // If set to nonzero, this is the UTC instant corresponding to
37    // `reference_boot_instant_ns` above.
38    #[serde(default)]
39    reference_utc_instant_ns: i64,
40}
41
42impl State {
43    pub fn new(update_rtc: bool) -> Self {
44        let mut value = Self { ..Default::default() };
45        value.set_may_update_rtc(update_rtc);
46        value
47    }
48
49    /// Returns whether updating the RTC clock is allowed.
50    pub fn may_update_rtc(&self) -> bool {
51        self.num_reboots_until_rtc_write_allowed == 0
52    }
53
54    pub fn set_may_update_rtc(&mut self, update_rtc: bool) {
55        self.num_reboots_until_rtc_write_allowed = match update_rtc {
56            true => 0,
57            // Limit the validity of the setting, expressed in number of
58            // reboots.
59            false => REBOOT_COUNT,
60        };
61    }
62
63    /// Gets a known-good reference point on the boot-to-utc affine transform.
64    pub fn get_rtc_reference(&self) -> (zx::BootInstant, zx::Instant<UtcTimeline>) {
65        let reference_boot = zx::BootInstant::from_nanos(self.reference_boot_instant_ns);
66        let reference_utc = zx::Instant::from_nanos(self.reference_utc_instant_ns);
67        (reference_boot, reference_utc)
68    }
69
70    /// 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`.
79    pub fn set_rtc_reference(
80        &mut self,
81        boot_reference: zx::BootInstant,
82        utc_reference: zx::Instant<UtcTimeline>,
83    ) {
84        self.reference_boot_instant_ns = boot_reference.into_nanos();
85        self.reference_utc_instant_ns = utc_reference.into_nanos();
86    }
87
88    // Change the reboot limit.
89    fn spend_one_reboot_count(&mut self) {
90        self.num_reboots_until_rtc_write_allowed =
91            self.num_reboots_until_rtc_write_allowed.saturating_sub(1);
92    }
93
94    /// Reads the persistent state, updating the testing-only components.
95    pub fn read_and_update() -> Result<Self> {
96        Self::read_and_update_internal(PERSISTENT_STATE_PATH)
97    }
98
99    pub fn read_and_update_internal<P: AsRef<Path>>(path: P) -> Result<Self> {
100        let path = path.as_ref();
101        let maybe_state_str: String = fs::read_to_string(path).map_err(|e| {
102            debug!("while reading: {:?}", e);
103            e
104        })?;
105        let state: State = serde_json::from_str(&maybe_state_str).map_err(|e| {
106            debug!("while deserializing: {:?}", e);
107            e
108        })?;
109        debug!("read persistent state: {:?}", &state);
110        let mut next = state.clone();
111        next.spend_one_reboot_count();
112        Self::write_internal(path, &next)?;
113        Ok(state)
114    }
115
116    /// Write the persistent state to mutable persistent storage.
117    pub fn write(state: &Self) -> Result<()> {
118        Self::write_internal(PERSISTENT_STATE_PATH, state)
119    }
120
121    pub fn write_internal<P: AsRef<Path>>(path: P, state: &Self) -> Result<()> {
122        let path = path.as_ref();
123        debug!("writing persistent state: {:?} to {:?}", state, path);
124        let state_str = serde_json::to_string(state).map_err(|e| {
125            error!("while serializing state: {:?}", e);
126            e
127        })?;
128        fs::write(path, state_str).map_err(|e| {
129            error!("while writing persistent state: {:?}: {:?}", path, e);
130            e.into()
131        })
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_roundtrip() {
141        let d = tempfile::TempDir::new().expect("tempdir created");
142        let p = d.path().join("file.json");
143        let s = State::new(false);
144        State::write_internal(&p, &s).unwrap();
145
146        // First time around, we may not update the RTC.
147        assert!(!State::read_and_update_internal(&p).unwrap().may_update_rtc());
148        // Second time around, we may.
149        assert!(State::read_and_update_internal(&p).unwrap().may_update_rtc());
150    }
151}