fake_wake_alarms/
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//! A fake server library for the protocol `fidl.fuchsia.time.alarms/WakeAlarms`, for reuse in the
6//! unit tests of clients of `WakeAlarm`.
7//!
8//! It serves a simplified but somewhat functional flavor of the `WakeAlarms` FIDL API, which
9//! covers the needs of unit tests we identified so far. This is not a fully-fledged
10//! implementation, so be sure to verify that what it does is enough for your purposes.
11//!
12//! If you need a fully capable server component for tests, see
13//! `//src/sys/time/testing/wake-alarms` instead.
14//!
15//! Include the library as follows. In `BUILD.gn`:
16//! ```ignore
17//! deps = [
18//!     "//src/sys/time/testing/fake-wake-alarms:lib",
19//! ]
20//! ```
21//!
22//! In rust code:
23//! ```ignore
24//! use fake_wake_alarms::*; // Or use items by name.
25//! ```
26
27use futures::stream::StreamExt;
28use scopeguard::defer;
29use std::collections::HashMap;
30use zx::{self as zx, HandleBased};
31use {fidl_fuchsia_time_alarms as fta, fuchsia_async as fasync, fuchsia_runtime as fxr};
32
33/// Configure the response of [serve_fake_wake_alarms].
34#[derive(Debug, Copy, Clone)]
35pub enum Response {
36    /// Respond immediately, without waiting.
37    Immediate,
38    /// Delay a response, sometimes controlled by additional values.
39    Delayed,
40    /// Respond with an error.
41    Error,
42}
43
44/// Scheduling a hrtimer with this deadline will expire it immediately.
45pub const MAGIC_EXPIRE_DEADLINE: i64 = 424242;
46
47/// Makes sure that a dropped responder is properly responded to.
48#[derive(Debug)]
49pub struct ResponderCleanup {
50    alarm_id: String,
51    responder: Option<fta::WakeAlarmsSetAndWaitResponder>,
52    responder_utc: Option<fta::WakeAlarmsSetAndWaitUtcResponder>,
53    notifier: Option<fidl::endpoints::ClientEnd<fta::NotifierMarker>>,
54}
55
56impl Drop for ResponderCleanup {
57    fn drop(&mut self) {
58        if let Some(responder) = self.responder.take() {
59            log::debug!("dropping responder: {responder:?}");
60            responder
61                .send(Err(fta::WakeAlarmsError::Dropped))
62                .expect("should be able to respond to a FIDL message")
63        }
64        if let Some(responder) = self.responder_utc.take() {
65            log::debug!("dropping responder: {responder:?}");
66            responder
67                .send(Err(fta::WakeAlarmsError::Dropped))
68                .expect("should be able to respond to a FIDL message")
69        }
70        if let Some(notifier) = self.notifier.take() {
71            log::debug!("dropping notifier: {notifier:?}");
72            notifier
73                .into_proxy()
74                .notify_error(&self.alarm_id, fta::WakeAlarmsError::Dropped)
75                .expect("should be able to respond to a FIDL message")
76        }
77    }
78}
79
80fn signal_handle<H: HandleBased>(
81    handle: &H,
82    clear_mask: zx::Signals,
83    set_mask: zx::Signals,
84) -> Result<(), zx::Status> {
85    handle.signal_handle(clear_mask, set_mask).map_err(|err| {
86        log::error!("while signaling handle: {err:?}: clear: {clear_mask:?}, set: {set_mask:?}");
87        err
88    })
89}
90
91/// A representation of deadlines with different reference points.
92#[derive(Debug, Clone, Copy)]
93enum Deadline {
94    Boot(fasync::BootInstant),
95    Utc(fxr::UtcInstant),
96}
97
98impl Deadline {
99    /// Converts to boot instant.  The results are faked for UTC timestamps.
100    fn into_boot(self) -> fasync::BootInstant {
101        match self {
102            Deadline::Boot(deadline) => deadline,
103            // This is wrong, but good enough for tests.
104            Deadline::Utc(deadline) => fasync::BootInstant::from_nanos(deadline.into_nanos()),
105        }
106    }
107}
108
109// Describes specific handler variants. Variants are only slightly different, which makes it
110// sensible to unify.
111enum HandlerVariant {
112    SetAndWait {
113        responder: fta::WakeAlarmsSetAndWaitResponder,
114    },
115    SetAndWaitUtc {
116        responder: fta::WakeAlarmsSetAndWaitUtcResponder,
117    },
118    Set {
119        responder: fta::WakeAlarmsSetResponder,
120        notifier: fidl::endpoints::ClientEnd<fta::NotifierMarker>,
121    },
122}
123
124async fn handle_set_like_method(
125    method_name: &str,
126    message_counter: &zx::Counter,
127    responders: &mut HashMap<String, ResponderCleanup>,
128    deadline: Deadline,
129    mode: fta::SetMode,
130    alarm_id: String,
131    response_type: &Response,
132    handler_variant: HandlerVariant,
133) {
134    log::debug!(
135        "serve_fake_wake_alarms: {}: alarm_id: {:?}: deadline: {:?}",
136        method_name,
137        alarm_id,
138        deadline
139    );
140    defer! {
141        if let fta::SetMode::NotifySetupDone(setup_done) = mode {
142            // Caller blocks until this event is signaled.
143            signal_handle(&setup_done, zx::Signals::NONE, zx::Signals::EVENT_SIGNALED).unwrap();
144        }
145    };
146    match response_type {
147        // Two possibilities: a "magic" expire deadline, which expires right away, or
148        // a "never" expiring deadline, regardless of the actual specified deadline.
149        Response::Delayed => {
150            if deadline.into_boot().into_nanos() == MAGIC_EXPIRE_DEADLINE {
151                log::debug!(
152                    "serve_fake_wake_alarms: {method_name}: responding immediately to magic deadline"
153                );
154                // If any responders are removed, then add one return
155                // message for each.
156                let r_count_before = responders.len();
157                responders.retain(|k, _| *k != alarm_id);
158                let r_count_after = responders.len();
159
160                message_counter
161                    .add(
162                        (r_count_before - r_count_after).try_into().expect("should be convertible"),
163                    )
164                    .expect("add to message_counter");
165                let (_, peer) = zx::EventPair::create();
166                message_counter.add(1).expect("add 1 to message counter");
167                match handler_variant {
168                    HandlerVariant::SetAndWait { responder } => {
169                        responder.send(Ok(peer)).expect("send FIDL response");
170                    }
171                    HandlerVariant::SetAndWaitUtc { responder } => {
172                        responder.send(Ok(peer)).expect("send FIDL response");
173                    }
174                    HandlerVariant::Set { responder, notifier } => {
175                        responder.send(Ok(())).unwrap();
176                        notifier
177                            .into_proxy()
178                            .notify(&alarm_id, peer)
179                            .expect("send Notify FIDL request");
180                    }
181                }
182            } else {
183                log::debug!("serve_fake_wake_alarms: {method_name}: will not respond");
184                let removed = match handler_variant {
185                    HandlerVariant::SetAndWait { responder } => responders.insert(
186                        alarm_id.clone(),
187                        ResponderCleanup {
188                            alarm_id,
189                            responder: Some(responder),
190                            responder_utc: None,
191                            notifier: None,
192                        },
193                    ),
194                    HandlerVariant::SetAndWaitUtc { responder } => responders.insert(
195                        alarm_id.clone(),
196                        ResponderCleanup {
197                            alarm_id,
198                            notifier: None,
199                            responder: None,
200                            responder_utc: Some(responder),
201                        },
202                    ),
203                    HandlerVariant::Set { responder, notifier } => {
204                        responder.send(Ok(())).unwrap();
205                        responders.insert(
206                            alarm_id.clone(),
207                            ResponderCleanup {
208                                alarm_id,
209                                notifier: Some(notifier),
210                                responder_utc: None,
211                                responder: None,
212                            },
213                        )
214                    }
215                };
216                // If some responder was removed, add a return message for it to the message
217                // counter.
218                if let Some(_) = removed {
219                    message_counter.add(1).unwrap();
220                }
221            }
222        }
223        Response::Immediate => {
224            // Manufacture a token to return, not relevant for the unit tests,
225            // so no functionality attributed to it.
226            let (_ignored, fake_lease) = zx::EventPair::create();
227            message_counter.add(1).unwrap();
228
229            match handler_variant {
230                HandlerVariant::SetAndWait { responder } => {
231                    responder.send(Ok(fake_lease)).expect("infallible");
232                }
233                HandlerVariant::SetAndWaitUtc { responder } => {
234                    responder.send(Ok(fake_lease)).expect("infallible");
235                }
236                HandlerVariant::Set { responder, notifier } => {
237                    responder.send(Ok(())).expect("send FIDL response");
238                    notifier
239                        .into_proxy()
240                        .notify(&alarm_id, fake_lease)
241                        .expect("send Notify FIDL request");
242                }
243            }
244            log::debug!("serve_fake_wake_alarms: {method_name}: test fake responded immediately");
245        }
246        Response::Error => {
247            message_counter.add(1).unwrap();
248            match handler_variant {
249                HandlerVariant::SetAndWait { responder } => {
250                    responder.send(Err(fta::WakeAlarmsError::Unspecified)).expect("infallible");
251                }
252                HandlerVariant::SetAndWaitUtc { responder } => {
253                    responder.send(Err(fta::WakeAlarmsError::Unspecified)).expect("infallible");
254                }
255                HandlerVariant::Set { responder, notifier } => {
256                    // Even if the end result is an error, the responder gets an OK, but
257                    // the notifier is told there was an error.
258                    responder.send(Ok(())).expect("send FIDL response");
259                    notifier
260                        .into_proxy()
261                        .notify_error(&alarm_id, fta::WakeAlarmsError::Unspecified)
262                        .expect("infallible");
263                }
264            }
265            log::debug!("serve_fake_wake_alarms: {method_name}: Responded with error");
266        }
267    }
268}
269
270/// Serves a fake `fuchsia.time.alarms/Wake` API. The behavior is simplistic when compared to
271/// the "real" implementation in that it never actually expires alarms on its own, and has
272/// fixed behavior for each scheduled alarm, which is selected at the beginning of the test.
273///
274/// Despite this, we can use it to check a number of correctness scenarios with unit tests.
275///
276/// This allows us to remove the flakiness that may arise from the use of real time, and also
277/// avoid the complications of fake time.  If you want an alarm that expires, schedule it with
278/// a deadline of `MAGIC_EXPIRE_DEADLINE` above, and call this with `response_type ==
279/// Response::Delayed`.
280pub async fn serve_fake_wake_alarms(
281    message_counter: zx::Counter,
282    response_type: Response,
283    mut stream: fta::WakeAlarmsRequestStream,
284    once: bool,
285) {
286    log::warn!("serve_fake_wake_alarms: serving loop entry. response_type={:?}", response_type);
287    let mut responders: HashMap<String, ResponderCleanup> = HashMap::new();
288    if once {
289        return;
290    }
291
292    while let Some(maybe_request) = stream.next().await {
293        match maybe_request {
294            Ok(request) => {
295                log::debug!(
296                    "serve_fake_wake_alarms: request: {:?}; response_type: {:?}",
297                    request,
298                    response_type
299                );
300
301                match request {
302                    fta::WakeAlarmsRequest::SetAndWaitUtc {
303                        mode,
304                        responder,
305                        alarm_id,
306                        deadline,
307                    } => {
308                        handle_set_like_method(
309                            "set_and_wait",
310                            &message_counter,
311                            &mut responders,
312                            Deadline::Utc(fxr::UtcInstant::from_nanos(deadline.timestamp_utc)),
313                            mode,
314                            alarm_id,
315                            &response_type,
316                            HandlerVariant::SetAndWaitUtc { responder },
317                        )
318                        .await;
319                    }
320                    fta::WakeAlarmsRequest::SetAndWait { mode, responder, alarm_id, deadline } => {
321                        handle_set_like_method(
322                            "set_and_wait",
323                            &message_counter,
324                            &mut responders,
325                            Deadline::Boot(deadline.into()),
326                            mode,
327                            alarm_id,
328                            &response_type,
329                            HandlerVariant::SetAndWait { responder },
330                        )
331                        .await;
332                    }
333                    fta::WakeAlarmsRequest::Set {
334                        notifier,
335                        deadline,
336                        mode,
337                        alarm_id,
338                        responder,
339                    } => {
340                        handle_set_like_method(
341                            "set",
342                            &message_counter,
343                            &mut responders,
344                            Deadline::Boot(deadline.into()),
345                            mode,
346                            alarm_id.clone(),
347                            &response_type,
348                            HandlerVariant::Set { responder, notifier },
349                        )
350                        .await;
351                    }
352                    fta::WakeAlarmsRequest::Cancel { alarm_id, .. } => {
353                        let r_count_before = responders.len();
354                        responders.retain(|k, _| *k != alarm_id);
355                        let r_count_after = responders.len();
356                        message_counter
357                            .add((r_count_before - r_count_after).try_into().unwrap())
358                            .unwrap();
359
360                        log::debug!("serve_fake_wake_alarms: Cancel: {}", alarm_id);
361                    }
362                    fta::WakeAlarmsRequest::_UnknownMethod { .. } => unreachable!(),
363                }
364            }
365            Err(e) => {
366                // This may or may not be an error, depending on what you wanted
367                // to test.
368                log::warn!("alarms::serve: error in request: {:?}", e);
369            }
370        }
371    }
372    log::warn!("serve_fake_wake_alarms: exiting");
373}