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