Skip to main content

timekeeper_integration_lib/
lib.rs

1// Copyright 2021 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
5use anyhow::Context;
6use chrono::{Datelike, TimeZone, Timelike};
7use fidl::endpoints::ServerEnd;
8use fidl_fuchsia_hardware_rtc::{DeviceRequest, DeviceRequestStream};
9use fidl_fuchsia_io as fio;
10use fidl_fuchsia_metrics::MetricEvent;
11use fidl_fuchsia_metrics_test::{LogMethod, MetricEventLoggerQuerierProxy};
12use fidl_fuchsia_testing::{FakeClockControlProxy, FakeClockProxy};
13use fidl_fuchsia_time::{MaintenanceRequest, MaintenanceRequestStream};
14use fidl_fuchsia_time_external::{PushSourceMarker, Status, TimeSample};
15use fidl_test_time::{TimeSourceControlRequest, TimeSourceControlRequestStream};
16use fuchsia_async as fasync;
17use fuchsia_component::server::ServiceFs;
18use fuchsia_component_test::{
19    Capability, ChildOptions, ChildRef, LocalComponentHandles, RealmBuilder, RealmInstance, Ref,
20    Route,
21};
22use fuchsia_sync::Mutex;
23use futures::channel::mpsc::Sender;
24use futures::stream::{Stream, StreamExt, TryStreamExt};
25use futures::{Future, FutureExt, SinkExt};
26use push_source::{PushSource, TestUpdateAlgorithm, Update};
27use std::ops::Deref;
28use std::sync::{Arc, LazyLock};
29use time_metrics_registry::PROJECT_ID;
30use vfs::pseudo_directory;
31use zx::{self as zx, Rights};
32
33/// URL for timekeeper.
34const TIMEKEEPER_URL: &str = "#meta/timekeeper_for_integration.cm";
35/// URL for timekeeper with fake time.
36const TIMEKEEPER_FAKE_TIME_URL: &str = "#meta/timekeeper_with_fake_time.cm";
37/// URL for fake cobalt.
38const COBALT_URL: &str = "#meta/fake_cobalt.cm";
39/// URL for the fake clock component.
40const FAKE_CLOCK_URL: &str = "#meta/fake_clock.cm";
41
42/// A reference to a timekeeper running inside a nested environment which runs fake versions of
43/// the services timekeeper requires.
44pub struct NestedTimekeeper {
45    _realm_instance: RealmInstance,
46}
47
48impl Into<RealmInstance> for NestedTimekeeper {
49    // Deconstructs [Self] into an underlying [RealmInstance].
50    fn into(self) -> RealmInstance {
51        self._realm_instance
52    }
53}
54
55impl NestedTimekeeper {
56    /// Creates a new [NestedTimekeeper].
57    ///
58    /// Launches an instance of timekeeper maintaining the provided |clock| in a nested
59    /// environment.
60    ///
61    /// If |initial_rtc_time| is provided, then the environment contains a fake RTC
62    /// device that reports the time as |initial_rtc_time|.
63    ///
64    /// If use_fake_clock is true, also launches a fake monotonic clock service.
65    ///
66    /// Returns a `NestedTimekeeper`, handles to the PushSource and RTC it obtains updates from,
67    /// Cobalt debug querier, and a fake clock control handle if use_fake_clock is true.
68    pub async fn new(
69        clock: Arc<zx::Clock>,
70        rtc_options: RtcOptions,
71        use_fake_clock: bool,
72    ) -> (
73        Self,
74        Arc<PushSourcePuppet>,
75        RtcUpdates,
76        MetricEventLoggerQuerierProxy,
77        Option<FakeClockController>,
78    ) {
79        let push_source_puppet = Arc::new(PushSourcePuppet::new());
80
81        let builder = RealmBuilder::new().await.unwrap();
82        let fake_cobalt =
83            builder.add_child("fake_cobalt", COBALT_URL, ChildOptions::new()).await.unwrap();
84
85        let timekeeper_url = if use_fake_clock { TIMEKEEPER_FAKE_TIME_URL } else { TIMEKEEPER_URL };
86        log::trace!("using timekeeper_url: {}", timekeeper_url);
87        let timekeeper = builder
88            .add_child("timekeeper_test", timekeeper_url, ChildOptions::new().eager())
89            .await
90            .with_context(|| format!("while starting up timekeeper_test from: {timekeeper_url}"))
91            .unwrap();
92
93        let timesource_server = builder
94            .add_local_child(
95                "timesource_mock",
96                {
97                    let push_source_puppet = Arc::clone(&push_source_puppet);
98                    move |handles: LocalComponentHandles| {
99                        Box::pin(timesource_mock_server(handles, Arc::clone(&push_source_puppet)))
100                    }
101                },
102                ChildOptions::new(),
103            )
104            .await
105            .context("while starting up timesource_mock")
106            .unwrap();
107
108        let maintenance_server = builder
109            .add_local_child(
110                "maintenance_mock",
111                move |handles: LocalComponentHandles| {
112                    Box::pin(maintenance_mock_server(handles, Arc::clone(&clock)))
113                },
114                ChildOptions::new(),
115            )
116            .await
117            .context("while starting up maintenance_mock")
118            .unwrap();
119
120        // Launch fake clock if needed.
121        if use_fake_clock {
122            let fake_clock =
123                builder.add_child("fake_clock", FAKE_CLOCK_URL, ChildOptions::new()).await.unwrap();
124
125            builder
126                .add_route(
127                    Route::new()
128                        .capability(Capability::protocol_by_name(
129                            "fuchsia.testing.FakeClockControl",
130                        ))
131                        .from(&fake_clock)
132                        .to(Ref::parent()),
133                )
134                .await
135                .context("while setting up FakeClockControl")
136                .unwrap();
137
138            builder
139                .add_route(
140                    Route::new()
141                        .capability(Capability::protocol_by_name("fuchsia.testing.FakeClock"))
142                        .from(&fake_clock)
143                        .to(Ref::parent())
144                        .to(&timekeeper),
145                )
146                .await
147                .context("while setting up FakeClock")
148                .unwrap();
149
150            builder
151                .add_route(
152                    Route::new()
153                        .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
154                        .from(Ref::parent())
155                        .to(&fake_clock),
156                )
157                .await
158                .context("while setting up LogSink")
159                .unwrap();
160        };
161
162        builder
163            .add_route(
164                Route::new()
165                    .capability(Capability::protocol_by_name("fuchsia.time.Maintenance"))
166                    .from(&maintenance_server)
167                    .to(&timekeeper),
168            )
169            .await
170            .context("while setting up Maintenance")
171            .unwrap();
172
173        builder
174            .add_route(
175                Route::new()
176                    .capability(Capability::protocol_by_name("test.time.TimeSourceControl"))
177                    .from(&timesource_server)
178                    .to(&timekeeper),
179            )
180            .await
181            .unwrap();
182
183        builder
184            .add_route(
185                Route::new()
186                    .capability(Capability::protocol_by_name(
187                        "fuchsia.metrics.test.MetricEventLoggerQuerier",
188                    ))
189                    .from(&fake_cobalt)
190                    .to(Ref::parent()),
191            )
192            .await
193            .unwrap();
194
195        builder
196            .add_route(
197                Route::new()
198                    .capability(Capability::protocol_by_name(
199                        "fuchsia.metrics.MetricEventLoggerFactory",
200                    ))
201                    .from(&fake_cobalt)
202                    .to(&timekeeper),
203            )
204            .await
205            .unwrap();
206
207        builder
208            .add_route(
209                Route::new()
210                    .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
211                    .from(Ref::parent())
212                    .to(&fake_cobalt)
213                    .to(&timekeeper)
214                    .to(&timesource_server)
215                    .to(&maintenance_server),
216            )
217            .await
218            .unwrap();
219
220        let parent_configs = [
221            "fuchsia.time.config.BackOffTimeBetweenPullSamplesSec",
222            "fuchsia.time.config.DisableDelays",
223            "fuchsia.time.config.HasRealTimeClock",
224            "fuchsia.time.config.MaxFrequencyErrorPpm",
225            "fuchsia.time.config.MinAcceptableBootTimeForUtcUpdateMinutes",
226            "fuchsia.time.config.MinUtcReferenceToBackstopDiffMinutes",
227            "fuchsia.time.config.PrimaryTimeSourceUrl",
228            "fuchsia.time.config.PrimaryUsesPull",
229            "fuchsia.time.config.ServeTestProtocols",
230            "fuchsia.time.config.UtcStartAtStartup",
231            "fuchsia.time.config.WritableUTCTime",
232        ];
233        builder
234            .add_route(
235                parent_configs
236                    .into_iter()
237                    .fold(Route::new(), |route, name| {
238                        route.capability(Capability::configuration(name))
239                    })
240                    .from(Ref::parent())
241                    .to(&timekeeper),
242            )
243            .await
244            .unwrap();
245
246        // Note: `realm_builder_server` automatically sets `Availability::Optional` when routing
247        // configuration capabilities from `Ref::void()`.
248        let void_configs = [
249            "fuchsia.time.config.EarlyExit",
250            "fuchsia.time.config.FirstSamplingDelaySec",
251            "fuchsia.time.config.HasAlwaysOnCounter",
252            "fuchsia.time.config.InitialFrequencyPpm",
253            "fuchsia.time.config.MonitorTimeSourceUrl",
254            "fuchsia.time.config.MonitorUsesPull",
255            "fuchsia.time.config.OscillatorErrorStdDevPpm",
256            "fuchsia.time.config.PeriodicRtcUpdateIntervalMinutes",
257            "fuchsia.time.config.PowerTopologyIntegrationEnabled",
258            "fuchsia.time.config.RtcAllowSettingPastUtc",
259            "fuchsia.time.config.ServeFuchsiaTimeAlarms",
260            "fuchsia.time.config.TimeSourceEndpointUrl",
261            "fuchsia.time.config.UseConnectivity",
262            "fuchsia.time.config.UtcMaxAllowedDeltaFutureSec",
263            "fuchsia.time.config.UtcMaxAllowedDeltaPastSec",
264            "fuchsia.time.config.UtcStartAtStartupWhenInvalidRtc",
265        ];
266        builder
267            .add_route(
268                void_configs
269                    .into_iter()
270                    .fold(Route::new(), |route, name| {
271                        route.capability(Capability::configuration(name))
272                    })
273                    .from(Ref::void())
274                    .to(&timekeeper),
275            )
276            .await
277            .unwrap();
278
279        let rtc_updates = setup_rtc(rtc_options, &builder, &timekeeper).await;
280        let realm_instance = builder.build().await.unwrap();
281
282        let fake_clock_control = if use_fake_clock {
283            let control_proxy = realm_instance.root.connect_to_protocol_at_exposed_dir().unwrap();
284            let clock_proxy = realm_instance.root.connect_to_protocol_at_exposed_dir().unwrap();
285            Some(FakeClockController { control_proxy, clock_proxy })
286        } else {
287            None
288        };
289
290        let cobalt_querier = realm_instance
291            .root
292            .connect_to_protocol_at_exposed_dir()
293            .expect("the connection succeeds");
294
295        let nested_timekeeper = Self { _realm_instance: realm_instance };
296
297        (nested_timekeeper, push_source_puppet, rtc_updates, cobalt_querier, fake_clock_control)
298    }
299}
300
301pub struct RemotePushSourcePuppet {
302    proxy: fidl_test_time_realm::PushSourcePuppetProxy,
303}
304
305impl RemotePushSourcePuppet {
306    /// Creates a new [RemotePushSourcePuppet].
307    pub fn new(proxy: fidl_test_time_realm::PushSourcePuppetProxy) -> Arc<Self> {
308        Arc::new(Self { proxy })
309    }
310
311    /// Set the next sample reported by the time source.
312    pub async fn set_sample(&self, sample: TimeSample) {
313        self.proxy.set_sample(&sample).await.expect("original API was infallible");
314    }
315
316    /// Set the next status reported by the time source.
317    pub async fn set_status(&self, status: Status) {
318        self.proxy.set_status(status).await.expect("original API was infallible");
319    }
320
321    /// Simulate a crash by closing client channels and wiping state.
322    pub async fn simulate_crash(&self) {
323        self.proxy.crash().await.expect("original local API was infallible");
324    }
325
326    /// Returns the number of cumulative connections served. This allows asserting
327    /// behavior such as whether Timekeeper has restarted a connection.
328    pub async fn lifetime_served_connections(&self) -> u32 {
329        self.proxy.get_lifetime_served_connections().await.expect("original API was infallible")
330    }
331}
332
333/// A `PushSource` that allows a single client and can be controlled by a test.
334pub struct PushSourcePuppet {
335    /// Internal state for the current PushSource. May be dropped and replaced
336    /// to clear all state.
337    inner: Mutex<PushSourcePuppetInner>,
338    /// The number of client connections received over the lifetime of the puppet.
339    cumulative_clients: Mutex<u32>,
340}
341
342impl PushSourcePuppet {
343    /// Create a new `PushSourcePuppet`.
344    fn new() -> Self {
345        Self { inner: Mutex::new(PushSourcePuppetInner::new()), cumulative_clients: Mutex::new(0) }
346    }
347
348    /// Serve the `PushSource` service to a client.
349    fn serve_client(&self, server_end: ServerEnd<PushSourceMarker>) {
350        log::debug!("serve_client entry");
351        let mut inner = self.inner.lock();
352        // Timekeeper should only need to connect to a push source once, except when it is
353        // restarting a time source. This case appears to the test as a second connection to the
354        // puppet. Since the puppet is restarted, all its state should be cleared as well.
355        if inner.served_client() {
356            *inner = PushSourcePuppetInner::new();
357        }
358        inner.serve_client(server_end);
359        *self.cumulative_clients.lock() += 1;
360    }
361
362    /// Set the next sample reported by the time source.
363    pub async fn set_sample(&self, sample: TimeSample) {
364        let mut sink = self.inner.lock().get_sink();
365        sink.send(sample.into()).await.unwrap();
366    }
367
368    /// Set the next status reported by the time source.
369    pub async fn set_status(&self, status: Status) {
370        let mut sink = self.inner.lock().get_sink();
371        sink.send(status.into()).await.unwrap();
372    }
373
374    /// Simulate a crash by closing client channels and wiping state.
375    pub fn simulate_crash(&self) {
376        *self.inner.lock() = PushSourcePuppetInner::new();
377        // This drops the old inner and cleans up any tasks it owns.
378    }
379
380    /// Returns the number of cumulative connections served. This allows asserting
381    /// behavior such as whether Timekeeper has restarted a connection.
382    pub fn lifetime_served_connections(&self) -> u32 {
383        *self.cumulative_clients.lock()
384    }
385}
386
387/// Internal state for a PushSourcePuppet. This struct contains a PushSource and
388/// all Tasks needed for it to serve requests,
389struct PushSourcePuppetInner {
390    push_source: Arc<PushSource<TestUpdateAlgorithm>>,
391    /// Tasks serving PushSource clients.
392    tasks: Vec<fasync::Task<()>>,
393    /// Sink through which updates are passed to the PushSource.
394    update_sink: Sender<Update>,
395}
396
397impl PushSourcePuppetInner {
398    fn new() -> Self {
399        let (update_algorithm, update_sink) = TestUpdateAlgorithm::new();
400        let push_source = Arc::new(PushSource::new(update_algorithm, Status::Ok).unwrap());
401        let push_source_clone = Arc::clone(&push_source);
402        let tasks = vec![fasync::Task::spawn(async move {
403            push_source_clone.poll_updates().await.unwrap();
404        })];
405        Self { push_source, tasks, update_sink }
406    }
407
408    /// Returns true if this puppet has or is currently serving a client.
409    fn served_client(&self) -> bool {
410        self.tasks.len() > 1
411    }
412
413    /// Serve the `PushSource` service to a client.
414    fn serve_client(&mut self, server_end: ServerEnd<PushSourceMarker>) {
415        let push_source_clone = Arc::clone(&self.push_source);
416        self.tasks.push(fasync::Task::spawn(async move {
417            push_source_clone.handle_requests_for_stream(server_end.into_stream()).await.unwrap();
418        }));
419    }
420
421    /// Obtains the sink used to send commands to the push source puppet.
422    ///
423    /// The sink is detached from the puppet, so can be used whenever needed
424    /// without locking.
425    fn get_sink(&self) -> Sender<Update> {
426        self.update_sink.clone()
427    }
428}
429
430/// The list of RTC update requests received by a `NestedTimekeeper`.
431#[derive(Clone, Debug)]
432pub struct RtcUpdates(Arc<Mutex<Vec<fidl_fuchsia_hardware_rtc::Time>>>);
433
434impl RtcUpdates {
435    /// Get all received RTC times as a vec.
436    pub fn to_vec(&self) -> Vec<fidl_fuchsia_hardware_rtc::Time> {
437        self.0.lock().clone()
438    }
439}
440
441/// Remote RTC updates - peek into the life of the RTC on the other side of a
442/// RTC connection.
443pub struct RemoteRtcUpdates {
444    proxy: fidl_test_time_realm::RtcUpdatesProxy,
445}
446
447impl RemoteRtcUpdates {
448    pub async fn to_vec(&self) -> Vec<fidl_fuchsia_hardware_rtc::Time> {
449        self.proxy
450            .get(fidl_test_time_realm::GetRequest::default())
451            .await
452            .expect("no errors or overflows") // Original API was infallible.
453            .unwrap()
454            .0
455    }
456    pub fn new(proxy: fidl_test_time_realm::RtcUpdatesProxy) -> Self {
457        RemoteRtcUpdates { proxy }
458    }
459}
460
461/// A wrapper around a `FakeClockControlProxy` that also allows a client to read
462/// the current fake time.
463pub struct FakeClockController {
464    control_proxy: FakeClockControlProxy,
465    clock_proxy: FakeClockProxy,
466}
467
468impl Deref for FakeClockController {
469    type Target = FakeClockControlProxy;
470
471    fn deref(&self) -> &Self::Target {
472        &self.control_proxy
473    }
474}
475
476impl FakeClockController {
477    /// Re-constructs FakeClockController from the constituents.
478    pub fn new(control_proxy: FakeClockControlProxy, clock_proxy: FakeClockProxy) -> Self {
479        FakeClockController { control_proxy, clock_proxy }
480    }
481
482    /// Deconstructs [Self] into fake clock proxies.
483    pub fn into_components(self) -> (FakeClockControlProxy, FakeClockProxy) {
484        (self.control_proxy, self.clock_proxy)
485    }
486
487    pub async fn get_monotonic(&self) -> Result<i64, fidl::Error> {
488        self.clock_proxy.get().await.map(|(_boot, mono)| mono.into_nanos())
489    }
490
491    /// Returns the current fake instant on the reference timeline.
492    pub async fn get_reference(&self) -> Result<zx::BootInstant, fidl::Error> {
493        self.get_monotonic().await.map(|v| zx::BootInstant::from_nanos(v))
494    }
495}
496
497/// The RTC configuration options.
498pub enum RtcOptions {
499    /// No real-time clock available. This configuration simulates a system that
500    /// does not have a RTC circuit available.
501    None,
502    /// Fake real-time clock. Supplied initial RTC time to report.
503    InitialRtcTime(zx::SyntheticInstant),
504    /// Injected real-time clock.
505    ///
506    /// This is the handle that will appear as the directory
507    /// `/dev/class/rtc` in the Timekeeper's namespace.
508    ///
509    /// The caller must set this directory up so that it serves
510    /// a RTC device (e.g. named `/dev/class/rtc/000`, and serving
511    /// the FIDL `fuchsia.hardware.rtc/Device`) from this directory.
512    ///
513    /// It is also possible to serve more RTCs from the directory, or
514    /// other files and file types at the caller's option.
515    ///
516    /// Use this option if you need to implement corner cases, or
517    /// very specific RTC behavior, such as abnormal configuration
518    /// or anomalous behavior.
519    InjectedRtc(fio::DirectoryProxy),
520}
521
522impl From<fidl_test_time_realm::RtcOptions> for RtcOptions {
523    fn from(value: fidl_test_time_realm::RtcOptions) -> Self {
524        match value {
525            fidl_test_time_realm::RtcOptions::DevClassRtc(h) => {
526                RtcOptions::InjectedRtc(h.into_proxy())
527            }
528            fidl_test_time_realm::RtcOptions::InitialRtcTime(t) => {
529                RtcOptions::InitialRtcTime(zx::SyntheticInstant::from_nanos(t))
530            }
531            _ => unimplemented!(),
532        }
533    }
534}
535
536impl From<zx::SyntheticInstant> for RtcOptions {
537    fn from(value: zx::SyntheticInstant) -> Self {
538        RtcOptions::InitialRtcTime(value)
539    }
540}
541
542impl From<Option<zx::SyntheticInstant>> for RtcOptions {
543    fn from(value: Option<zx::SyntheticInstant>) -> Self {
544        value.map(|t| t.into()).unwrap_or(Self::None)
545    }
546}
547
548/// Sets up the RTC serving.
549///
550/// Args:
551/// - `rtc_options`: options for RTC setup.
552/// - `build`: the `RealmBuilder` that will construct the realm.
553/// - `timekeeper`: the Timekeeper component instance.
554///
555/// Returns:
556/// - `RtcUpdates`: A vector of RTC updates received from a fake RTC. If the
557///   client serves the RTC directory, then the return value is useless.
558async fn setup_rtc(
559    rtc_options: RtcOptions,
560    builder: &RealmBuilder,
561    timekeeper: &ChildRef,
562) -> RtcUpdates {
563    let rtc_updates = RtcUpdates(Arc::new(Mutex::new(vec![])));
564
565    let rtc_dir = match rtc_options {
566        RtcOptions::InitialRtcTime(initial_time) => {
567            log::debug!("using fake fuchsia.hardware.rtc.Service");
568            pseudo_directory! {
569                "fuchsia.hardware.rtc.Service" => pseudo_directory! {
570                    "default" => pseudo_directory! {
571                        "device" => vfs::service::host({
572                            let rtc_updates = rtc_updates.clone();
573                            move |stream| {
574                                serve_fake_rtc(initial_time, rtc_updates.clone(), stream)
575                            }
576                        })
577                    }
578                }
579            }
580        }
581        RtcOptions::None => {
582            log::debug!("using an empty fuchsia.hardware.rtc.Service directory");
583            pseudo_directory! {
584                "fuchsia.hardware.rtc.Service" => pseudo_directory! {
585                }
586            }
587        }
588        RtcOptions::InjectedRtc(h) => {
589            log::debug!("using RTC provided by client");
590            pseudo_directory! {
591                "fuchsia.hardware.rtc.Service" => vfs::remote::remote_dir(h)
592            }
593        }
594    };
595
596    let fake_rtc_server = builder
597        .add_local_child(
598            "fake_rtc",
599            {
600                move |handles| {
601                    let rtc_dir = rtc_dir.clone();
602                    async move {
603                        let mut fs = ServiceFs::new();
604                        fs.add_remote(
605                            "svc",
606                            vfs::directory::serve_read_only(
607                                rtc_dir,
608                                vfs::execution_scope::ExecutionScope::new(),
609                            ),
610                        );
611                        fs.serve_connection(handles.outgoing_dir)
612                            .expect("failed to serve fake RTC ServiceFs");
613                        fs.collect::<()>().await;
614                        Ok(())
615                    }
616                    .boxed()
617                }
618            },
619            ChildOptions::new().eager(),
620        )
621        .await
622        .unwrap();
623
624    builder
625        .add_route(
626            Route::new()
627                .capability(Capability::service::<fidl_fuchsia_hardware_rtc::ServiceMarker>())
628                .from(&fake_rtc_server)
629                .to(&*timekeeper),
630        )
631        .await
632        .unwrap();
633
634    rtc_updates
635}
636
637// Emulates the low-level RTC device driver with a fake which gets given the initial
638// reading, and can be set externally.
639async fn serve_fake_rtc(
640    initial_time: zx::SyntheticInstant,
641    rtc_updates: RtcUpdates,
642    mut stream: DeviceRequestStream,
643) {
644    let mut current = zx_time_to_rtc_time(initial_time);
645    while let Some(req) = stream.try_next().await.unwrap() {
646        match req {
647            DeviceRequest::Get { responder } => {
648                log::debug!("serve_fake_rtc: DeviceRequest::Get: {current:?}");
649                responder.send(Ok(&current)).unwrap();
650            }
651            DeviceRequest::Set2 { rtc, responder } => {
652                log::debug!("serve_fake_rtc: DeviceRequest::Set2: {rtc:?}");
653                rtc_updates.0.lock().push(rtc);
654                current = rtc;
655                responder.send(Ok(())).unwrap();
656            }
657            DeviceRequest::_UnknownMethod { .. } => {}
658        }
659    }
660}
661
662async fn serve_test_control(puppet: &PushSourcePuppet, stream: TimeSourceControlRequestStream) {
663    stream
664        .try_for_each_concurrent(None, |req| async {
665            let _ = &req;
666            let TimeSourceControlRequest::ConnectPushSource { push_source, .. } = req;
667            puppet.serve_client(push_source);
668            Ok(())
669        })
670        .await
671        .unwrap();
672}
673
674async fn serve_maintenance(clock_handle: Arc<zx::Clock>, mut stream: MaintenanceRequestStream) {
675    while let Some(req) = stream.try_next().await.unwrap() {
676        let MaintenanceRequest::GetWritableUtcClock { responder } = req;
677        responder.send(clock_handle.duplicate_handle(Rights::SAME_RIGHTS).unwrap()).unwrap();
678    }
679}
680
681async fn timesource_mock_server(
682    handles: LocalComponentHandles,
683    push_source_puppet: Arc<PushSourcePuppet>,
684) -> Result<(), anyhow::Error> {
685    let mut fs = ServiceFs::new();
686    let mut tasks = vec![];
687
688    fs.dir("svc").add_fidl_service(move |stream: TimeSourceControlRequestStream| {
689        let puppet_clone = Arc::clone(&push_source_puppet);
690
691        tasks.push(fasync::Task::local(async move {
692            serve_test_control(&*puppet_clone, stream).await;
693        }));
694    });
695
696    fs.serve_connection(handles.outgoing_dir)?;
697    fs.collect::<()>().await;
698
699    Ok(())
700}
701
702async fn maintenance_mock_server(
703    handles: LocalComponentHandles,
704    clock: Arc<zx::Clock>,
705) -> Result<(), anyhow::Error> {
706    let mut fs = ServiceFs::new();
707    let mut tasks = vec![];
708
709    fs.dir("svc").add_fidl_service(move |stream: MaintenanceRequestStream| {
710        let clock_clone = Arc::clone(&clock);
711
712        tasks.push(fasync::Task::local(async move {
713            serve_maintenance(clock_clone, stream).await;
714        }));
715    });
716
717    fs.serve_connection(handles.outgoing_dir)?;
718    fs.collect::<()>().await;
719
720    Ok(())
721}
722
723fn from_rfc2822(date: &str) -> zx::SyntheticInstant {
724    zx::SyntheticInstant::from_nanos(
725        chrono::DateTime::parse_from_rfc2822(date).unwrap().timestamp_nanos_opt().unwrap(),
726    )
727}
728
729pub static BACKSTOP_TIME: LazyLock<zx::SyntheticInstant> =
730    LazyLock::new(|| from_rfc2822("Sun, 20 Sep 2020 01:01:01 GMT"));
731pub static VALID_RTC_TIME: LazyLock<zx::SyntheticInstant> =
732    LazyLock::new(|| from_rfc2822("Sun, 20 Sep 2020 02:02:02 GMT"));
733pub static BEFORE_BACKSTOP_TIME: LazyLock<zx::SyntheticInstant> =
734    LazyLock::new(|| from_rfc2822("Fri, 06 Mar 2020 04:04:04 GMT"));
735pub static VALID_TIME: LazyLock<zx::SyntheticInstant> =
736    LazyLock::new(|| from_rfc2822("Tue, 29 Sep 2020 02:19:01 GMT"));
737pub static VALID_TIME_2: LazyLock<zx::SyntheticInstant> =
738    LazyLock::new(|| from_rfc2822("Wed, 30 Sep 2020 14:59:59 GMT"));
739
740/// Time between each reported sample.
741pub const BETWEEN_SAMPLES: zx::BootDuration = zx::BootDuration::from_seconds(5);
742
743/// The standard deviation to report on valid time samples.
744pub const STD_DEV: zx::BootDuration = zx::BootDuration::from_millis(50);
745
746/// Create a new clock with backstop time set to `BACKSTOP_TIME`.
747// TODO: b/306024715 - To be removed once all tests are migrated to TTRF.
748pub fn new_clock() -> Arc<zx::SyntheticClock> {
749    Arc::new(new_nonshareable_clock())
750}
751
752/// Create a new clock with backstop time set to `BACKSTOP_TIME`.
753pub fn new_nonshareable_clock() -> zx::SyntheticClock {
754    zx::SyntheticClock::create(zx::ClockOpts::MAPPABLE, Some(*BACKSTOP_TIME)).unwrap()
755}
756
757fn zx_time_to_rtc_time(zx_time: zx::SyntheticInstant) -> fidl_fuchsia_hardware_rtc::Time {
758    let date = chrono::Utc.timestamp_nanos(zx_time.into_nanos());
759    fidl_fuchsia_hardware_rtc::Time {
760        seconds: date.second() as u8,
761        minutes: date.minute() as u8,
762        hours: date.hour() as u8,
763        day: date.day() as u8,
764        month: date.month() as u8,
765        year: date.year() as u16,
766    }
767}
768
769pub fn rtc_time_to_zx_time(rtc_time: fidl_fuchsia_hardware_rtc::Time) -> zx::SyntheticInstant {
770    let date = chrono::Utc
771        .with_ymd_and_hms(
772            rtc_time.year as i32,
773            rtc_time.month as u32,
774            rtc_time.day as u32,
775            rtc_time.hours as u32,
776            rtc_time.minutes as u32,
777            rtc_time.seconds as u32,
778        )
779        .unwrap();
780    zx::SyntheticInstant::from_nanos(date.timestamp_nanos_opt().unwrap())
781}
782
783/// Create a stream of MetricEvents from a proxy.
784pub fn create_cobalt_event_stream(
785    proxy: Arc<MetricEventLoggerQuerierProxy>,
786    log_method: LogMethod,
787) -> std::pin::Pin<Box<dyn Stream<Item = MetricEvent>>> {
788    async_utils::hanging_get::client::HangingGetStream::new(proxy, move |p| {
789        p.watch_logs(PROJECT_ID, log_method)
790    })
791    .map(|res| futures::stream::iter(res.expect("there should be a valid result here").0))
792    .flatten()
793    .boxed()
794}
795
796/// Repeatedly evaluates `condition` until it returns `Some(v)`. Returns `v`.
797#[macro_export]
798macro_rules! poll_until_some {
799    ($condition:expr) => {
800        $crate::poll_until_some_impl(
801            $condition,
802            &$crate::SourceLocation::new(file!(), line!(), column!()),
803        )
804    };
805}
806
807/// Repeatedly evaluates an async `condition` until it returns `Some(v)`. Returns `v`.
808/// Use if your condition is an async fn.
809#[macro_export]
810macro_rules! poll_until_some_async {
811    ($condition:expr) => {{
812        let loc = $crate::SourceLocation::new(file!(), line!(), column!());
813        log::info!("=> poll_until_some_async() for {}", &loc);
814        let mut result = None;
815        loop {
816            result = $condition.await;
817            if result.is_some() {
818                break;
819            }
820            fasync::Timer::new(fasync::MonotonicInstant::after($crate::RETRY_WAIT_DURATION)).await;
821        }
822        log::info!("=> poll_until_some_async() done for {}", &loc);
823        result.expect("we loop around while result is None")
824    }};
825}
826
827/// Repeatedly evaluates `condition` to create a `Future`, and then awaits the `Future`.
828/// Returns `()` when the (most recently created) `Future` resolves to `true`.
829#[macro_export]
830macro_rules! poll_until_async {
831    ($condition:expr) => {
832        $crate::poll_until_async_impl(
833            $condition,
834            &$crate::SourceLocation::new(file!(), line!(), column!()),
835        )
836    };
837}
838
839/// A reimplementation of the above, which deals better with borrows.
840#[macro_export]
841macro_rules! poll_until_async_2 {
842    ($condition:expr) => {{
843        let loc = $crate::SourceLocation::new(file!(), line!(), column!());
844        log::info!("=> poll_until_async() for {}", &loc);
845        let mut result = true;
846        loop {
847            result = $condition.await;
848            if result {
849                break;
850            }
851            fasync::Timer::new(fasync::MonotonicInstant::after($crate::RETRY_WAIT_DURATION)).await;
852        }
853        log::info!("=> poll_until_async_2() done for {}", &loc);
854        result
855    }};
856}
857
858/// Repeatedly evaluates `condition` until it returns `true`. Returns `()`.
859#[macro_export]
860macro_rules! poll_until {
861    ($condition:expr) => {
862        $crate::poll_until_impl(
863            $condition,
864            &$crate::SourceLocation::new(file!(), line!(), column!()),
865        )
866    };
867}
868
869/// Wait duration for polling.
870pub const RETRY_WAIT_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(10);
871
872pub struct SourceLocation {
873    file: &'static str,
874    line: u32,
875    column: u32,
876}
877
878impl std::fmt::Display for SourceLocation {
879    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
880        write!(f, "(file: {}, line: {}, column: {})", self.file, self.line, self.column)
881    }
882}
883
884impl SourceLocation {
885    pub fn new(file: &'static str, line: u32, column: u32) -> Self {
886        Self { file, line, column }
887    }
888}
889
890/// Use `poll_until_some!()` instead.
891pub async fn poll_until_some_impl<T, F>(poll_fn: F, loc: &SourceLocation) -> T
892where
893    F: Fn() -> Option<T>,
894{
895    log::info!("=> poll_until_some() for {}", loc);
896    loop {
897        match poll_fn() {
898            Some(value) => {
899                log::info!("<= poll_until_some() for {}", loc);
900                return value;
901            }
902            None => fasync::Timer::new(fasync::MonotonicInstant::after(RETRY_WAIT_DURATION)).await,
903        }
904    }
905}
906
907/// Use `poll_until_async!()` instead.
908pub async fn poll_until_async_impl<F, Fut>(poll_fn: F, loc: &SourceLocation)
909where
910    F: Fn() -> Fut,
911    Fut: Future<Output = bool>,
912{
913    log::info!("=> poll_until_async() for {}", loc);
914    while !poll_fn().await {
915        fasync::Timer::new(fasync::MonotonicInstant::after(RETRY_WAIT_DURATION)).await
916    }
917    log::info!("<= poll_until_async() for {}", loc);
918}
919
920/// Use `poll_until!()` instead.
921pub async fn poll_until_impl<F: Fn() -> bool>(poll_fn: F, loc: &SourceLocation) {
922    log::info!("=> poll_until() for {}", loc);
923    while !poll_fn() {
924        fasync::Timer::new(fasync::MonotonicInstant::after(RETRY_WAIT_DURATION)).await
925    }
926    log::info!("<= poll_until() for {}", loc);
927}