Skip to main content

time_adjust/
lib.rs

1// Copyright 2025 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//! Serves the `fuchsia.time.external/Adjust` FIDL API.
6
7use anyhow::{Context, Result};
8use fidl_fuchsia_time_external as ffte;
9use fuchsia_runtime as fxr;
10use fuchsia_trace as trace;
11use futures::channel::mpsc;
12use futures::{SinkExt, StreamExt};
13use log::{debug, error};
14use scopeguard::defer;
15use std::cell::RefCell;
16
17#[derive(Debug)]
18pub enum Command {
19    /// A power management command.
20    PowerManagement {
21        /// If true, the system is suspending (`BeforeSuspend`); if false,
22        /// the system has resumed (`AfterResume`).
23        suspended: bool,
24        /// Signaled once the power state transition has been processed.
25        done: mpsc::Sender<()>,
26    },
27    /// Report a reference point for the boot-timeline-to-utc-timeline affine
28    /// transform.
29    Reference {
30        /// Proposed boot reference instant.
31        boot_reference: zx::BootInstant,
32        /// Proposed UTC instant corresponding to `boot_reference`.
33        utc_reference: fxr::UtcInstant,
34        /// Must be responded to with a result capturing the outcome of
35        /// the adjustment attempt.
36        responder: mpsc::Sender<Result<()>>,
37    },
38    /// Reports the connectivity status.
39    Connectivity {
40        /// If true, we expect that attempting a HTTP request should work.
41        http_available: bool,
42    },
43    /// Periodic RTC update.
44    UpdateRtc,
45}
46
47/// Serves the "Adjust" FIDL API.
48pub struct Server {
49    // Every Adjust input is forwarded to this sender.
50    adjust_sender: RefCell<mpsc::Sender<Command>>,
51}
52
53impl Server {
54    /// Creates a new [Server].
55    ///
56    /// The `adjust_sender` channel must always have enough room to accepts a new adjustment
57    /// without blocking.
58    pub fn new(adjust_sender: mpsc::Sender<Command>) -> Self {
59        // RefCell to avoid &mut self where not essential.
60        Self { adjust_sender: RefCell::new(adjust_sender) }
61    }
62
63    /// Serve a single Adjust FIDL API request stream.
64    pub async fn serve(&self, mut stream: ffte::AdjustRequestStream) -> Result<()> {
65        debug!("time_adjust::serve: entering serving loop");
66        defer! {
67            debug!("time_adjust::serve: exited  serving loop");
68        };
69        while let Some(request) = stream.next().await {
70            trace::duration!("timekeeper", "adjust:request");
71            debug!("time_adjust::Server::serve: request: {:?}", request);
72            match request {
73                Ok(ffte::AdjustRequest::ReportBootToUtcMapping {
74                    boot_reference,
75                    utc_reference,
76                    responder,
77                }) => {
78                    trace::instant!("alarms", "adjust:request:params", trace::Scope::Process,
79                        "boot_reference" => boot_reference.into_nanos(), "utc_reference" => utc_reference);
80                    let utc_reference = fxr::UtcInstant::from_nanos(utc_reference);
81                    let (tx, mut rx) = mpsc::channel(1);
82                    let command =
83                        Command::Reference { boot_reference, utc_reference, responder: tx };
84                    self.adjust_sender
85                        .borrow_mut()
86                        .send(command)
87                        .await
88                        .context("while trying to send to adjust_sender")?;
89                    let result = rx.next().await.context("could not get a response")?;
90                    responder.send(
91                        result
92                            .context("while sending response to Adjust")
93                            .map_err(|e| {
94                                error!("could not send response: {:?}", e);
95                                e
96                            })
97                            .map_err(|_| ffte::Error::Internal),
98                    )?;
99                }
100                Err(e) => {
101                    error!("FIDL error: {:?}", e);
102                }
103            };
104        }
105        Ok(())
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use fuchsia_async as fasync;
113
114    #[fuchsia::test]
115    async fn basic_test() -> Result<()> {
116        let (tx, mut rx) = mpsc::channel(1);
117        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ffte::AdjustMarker>();
118        let server = Server::new(tx);
119        let _task = fasync::Task::local(async move { server.serve(stream).await });
120
121        let _success = fasync::Task::local(async move {
122            // Since this call won't return until it is acked below, make it into a
123            // coroutine so it doesn't block the test body from running.
124            proxy
125                .report_boot_to_utc_mapping(zx::BootInstant::from_nanos(42), 4200i64)
126                .await
127                .expect("infallible")
128        });
129        let recv = rx.next().await.expect("infallible");
130        match recv {
131            Command::Reference { boot_reference, utc_reference, mut responder } => {
132                responder.send(Ok(())).await.unwrap();
133                assert_eq!(boot_reference, zx::BootInstant::from_nanos(42));
134                assert_eq!(utc_reference, fxr::UtcInstant::from_nanos(4200));
135            }
136            e => {
137                error!("Unexpected response: {:?}", e)
138            }
139        }
140
141        Ok(())
142    }
143}