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