Skip to main content

fdr_lib/
fdr.rs

1// Copyright 2020 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, Error, bail};
6use carnelian::input::consumer_control::Phase;
7use fidl_fuchsia_input::ConsumerControlButton;
8use fidl_fuchsia_paver::{BootManagerMarker, BootManagerProxy, Configuration, PaverMarker};
9use fidl_fuchsia_recovery::FactoryResetMarker;
10use fuchsia_component::client::connect_to_protocol;
11
12#[derive(Debug, PartialEq, Copy, Clone)]
13pub enum FactoryResetState {
14    Waiting,
15    AwaitingPolicy(usize),
16    StartCountdown,
17    CancelCountdown,
18    ExecuteReset,
19    AwaitingReset,
20}
21
22#[derive(Debug, PartialEq, Copy, Clone)]
23pub enum ResetEvent {
24    ButtonPress(ConsumerControlButton, Phase),
25    AwaitPolicyResult(usize, bool),
26    CountdownFinished,
27    CountdownCancelled,
28}
29
30pub struct FactoryResetStateMachine {
31    volume_up_phase: Phase,
32    volume_down_phase: Phase,
33    state: FactoryResetState,
34    last_policy_check_id: usize,
35}
36
37impl FactoryResetStateMachine {
38    pub fn new() -> FactoryResetStateMachine {
39        FactoryResetStateMachine {
40            volume_down_phase: Phase::Up,
41            volume_up_phase: Phase::Up,
42            state: FactoryResetState::Waiting,
43            last_policy_check_id: 0,
44        }
45    }
46
47    pub fn is_counting_down(&self) -> bool {
48        self.state == FactoryResetState::StartCountdown
49    }
50
51    fn update_button_state(&mut self, button: ConsumerControlButton, phase: Phase) {
52        match button {
53            ConsumerControlButton::VolumeUp => self.volume_up_phase = phase,
54            ConsumerControlButton::VolumeDown => self.volume_down_phase = phase,
55            _ => panic!("Invalid button provided {:?}", button),
56        };
57    }
58
59    fn check_buttons_pressed(&self) -> bool {
60        match (self.volume_up_phase, self.volume_down_phase) {
61            (Phase::Down, Phase::Down) => true,
62            _ => false,
63        }
64    }
65
66    /// Updates the state machine's button state on button presses and returns
67    /// the new state and a boolean indicating if that new state is changed from
68    /// the previous state.
69    pub fn handle_event(&mut self, event: ResetEvent) -> FactoryResetState {
70        if let ResetEvent::ButtonPress(button, phase) = event {
71            // Handle all volume button events here to make sure we don't miss anything in default matches
72            self.update_button_state(button, phase);
73        }
74        let new_state = match self.state {
75            FactoryResetState::Waiting => match event {
76                ResetEvent::ButtonPress(_, _) => {
77                    if self.check_buttons_pressed() {
78                        self.last_policy_check_id += 1;
79                        FactoryResetState::AwaitingPolicy(self.last_policy_check_id)
80                    } else {
81                        FactoryResetState::Waiting
82                    }
83                }
84                ResetEvent::CountdownFinished => {
85                    panic!("Not expecting timer updates when in waiting state")
86                }
87                ResetEvent::CountdownCancelled | ResetEvent::AwaitPolicyResult(_, _) => {
88                    FactoryResetState::Waiting
89                }
90            },
91            FactoryResetState::AwaitingPolicy(check_id) => match event {
92                ResetEvent::ButtonPress(_, _) => {
93                    if !self.check_buttons_pressed() {
94                        FactoryResetState::Waiting
95                    } else {
96                        FactoryResetState::AwaitingPolicy(check_id)
97                    }
98                }
99                ResetEvent::AwaitPolicyResult(check_id, fdr_enabled)
100                    if check_id == self.last_policy_check_id =>
101                {
102                    if fdr_enabled {
103                        println!("recovery: start reset countdown");
104                        FactoryResetState::StartCountdown
105                    } else {
106                        FactoryResetState::Waiting
107                    }
108                }
109                _ => FactoryResetState::Waiting,
110            },
111            FactoryResetState::StartCountdown => match event {
112                ResetEvent::ButtonPress(_, _) => {
113                    if self.check_buttons_pressed() {
114                        panic!(
115                            "Not expecting both buttons to be pressed while in StartCountdown state"
116                        );
117                    } else {
118                        println!("recovery: cancel reset countdown");
119                        FactoryResetState::CancelCountdown
120                    }
121                }
122                ResetEvent::CountdownCancelled => {
123                    panic!(
124                        "Not expecting CountdownCancelled here, expecting input event to \
125                                move to CancelCountdown state first."
126                    );
127                }
128                ResetEvent::CountdownFinished => {
129                    println!("recovery: execute factory reset");
130                    FactoryResetState::ExecuteReset
131                }
132                ResetEvent::AwaitPolicyResult(_, _) => FactoryResetState::StartCountdown,
133            },
134            FactoryResetState::CancelCountdown => match event {
135                ResetEvent::CountdownCancelled => FactoryResetState::Waiting,
136                ResetEvent::AwaitPolicyResult(_, _) | ResetEvent::ButtonPress(_, _) => {
137                    FactoryResetState::CancelCountdown
138                }
139                _ => panic!("Only expecting CountdownCancelled event in CancelCountdown state."),
140            },
141            FactoryResetState::ExecuteReset => match event {
142                ResetEvent::AwaitPolicyResult(_, _) => FactoryResetState::AwaitingReset,
143                // Subsequent button presses should not trigger additional reset calls.
144                ResetEvent::ButtonPress(_, _) => FactoryResetState::AwaitingReset,
145                _ => {
146                    panic!("Not expecting countdown events while in ExecuteReset state")
147                }
148            },
149            FactoryResetState::AwaitingReset => match event {
150                ResetEvent::AwaitPolicyResult(_, _) => FactoryResetState::AwaitingReset,
151                ResetEvent::ButtonPress(_, _) => FactoryResetState::AwaitingReset,
152                _ => panic!("Not expecting countdown events while in ExecuteReset state"),
153            },
154        };
155
156        self.state = new_state;
157        new_state
158    }
159
160    #[cfg(test)]
161    pub fn get_state(&self) -> FactoryResetState {
162        return self.state;
163    }
164}
165
166fn get_other_slot_config(config: Configuration) -> Configuration {
167    match config {
168        Configuration::A => Configuration::B,
169        Configuration::B => Configuration::A,
170        // A/B recovery is not supported currently.
171        Configuration::Recovery => Configuration::Recovery,
172    }
173}
174
175/// Uses fuchsia.paver FIDL's BootManager API to set the active slot using the following strategy:
176///
177/// 1. Query for slot-last-set-active
178/// 2. Set the last inactive slot (opposite of the result from 1) to active to reset retry count
179/// 3. Set the result from slot-last-set-active to active
180///
181/// This aims to recover from cases where both slots A and B are marked unbootable.
182/// Requires fuchsia.paver.Paver protocol capability.
183pub async fn reset_active_slot() -> Result<(), Error> {
184    let paver_proxy = connect_to_protocol::<PaverMarker>().context("failed to connect to paver")?;
185    let (boot_manager, server) = fidl::endpoints::create_proxy::<BootManagerMarker>();
186
187    paver_proxy.find_boot_manager(server).context("failed to find boot manager")?;
188
189    reset_active_slot_with_proxy(boot_manager).await
190}
191
192async fn reset_active_slot_with_proxy(boot_manager: BootManagerProxy) -> Result<(), Error> {
193    let last_active_config = match boot_manager.query_configuration_last_set_active().await {
194        Ok(Ok(config)) => config,
195        Ok(Err(err)) => bail!("Failure status querying last active config: {:?}", err),
196        Err(err) => bail!("Error querying last active configuration: {:?}", err),
197    };
198
199    if last_active_config == Configuration::Recovery {
200        eprintln!(
201            "Last active config is recovery: no information to decide which other config to set active. Leaving as is."
202        );
203        return Ok(());
204    }
205
206    // Set inactive config, then last active config to reset the boot attempt retry counters.
207    let inactive_config = get_other_slot_config(last_active_config);
208    boot_manager
209        .set_configuration_active(inactive_config)
210        .await
211        .context("failed to set inactive config")?;
212    boot_manager
213        .set_configuration_active(last_active_config)
214        .await
215        .context("failed to set last active config")?;
216
217    Ok(())
218}
219
220pub async fn execute_reset() -> Result<(), Error> {
221    let factory_reset_service = connect_to_protocol::<FactoryResetMarker>();
222    let proxy = match factory_reset_service {
223        Ok(marker) => marker.clone(),
224        Err(error) => {
225            bail!("Could not connect to factory_reset_service: {:?}", error);
226        }
227    };
228
229    println!("recovery: Executing factory reset command");
230
231    match proxy.reset().await {
232        Ok(_) => {}
233        Err(error) => {
234            bail!("Error executing factory reset command : {:?}", error);
235        }
236    };
237    Ok(())
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use fidl_fuchsia_paver::BootManagerRequest;
244    use fuchsia_async as fasync;
245    use futures::channel::mpsc;
246    use futures::{StreamExt, TryStreamExt};
247
248    // A mock BootManager service that stores an initial last_active_config and reports the queries
249    // it receives to mpsc::Receivers for the test to listen and consume.
250    fn create_mock_boot_manager(
251        last_active_config: Configuration,
252    ) -> Result<
253        (BootManagerProxy, mpsc::Receiver<Configuration>, mpsc::Receiver<Configuration>),
254        Error,
255    > {
256        let (mut query_last_active_sender, query_last_active_receiver) = mpsc::channel(10);
257        let (mut set_active_sender, set_active_receiver) = mpsc::channel(10);
258        let (proxy, mut request_stream) =
259            fidl::endpoints::create_proxy_and_stream::<BootManagerMarker>();
260
261        fasync::Task::local(async move {
262            while let Some(request) =
263                request_stream.try_next().await.expect("failed to read mock request")
264            {
265                match request {
266                    BootManagerRequest::QueryConfigurationLastSetActive { responder } => {
267                        query_last_active_sender.start_send(last_active_config.clone()).unwrap();
268                        responder.send(Ok(last_active_config)).unwrap();
269                    }
270                    BootManagerRequest::SetConfigurationActive { configuration, responder } => {
271                        set_active_sender.start_send(configuration.clone()).unwrap();
272                        responder.send(zx::Status::OK.into_raw()).unwrap();
273                    }
274                    _ => {
275                        panic!("Unexpected request sent to mock boot manager");
276                    }
277                }
278            }
279        })
280        .detach();
281
282        Ok((proxy, query_last_active_receiver, set_active_receiver))
283    }
284
285    #[fuchsia::test]
286    async fn test_set_last_config_a() {
287        let last_active_config = Configuration::A;
288        let (boot_manager_proxy, mut query_last_active_listener, mut set_active_listener) =
289            create_mock_boot_manager(last_active_config).unwrap();
290
291        let _res = reset_active_slot_with_proxy(boot_manager_proxy).await.unwrap();
292
293        // Read in order the requests sent to boot manager:
294        // 1. Reading the last active config.
295        // 2. Setting the last inactive config as active.
296        // 3. Setting the last active config as active.
297
298        assert_eq!(Configuration::A, query_last_active_listener.next().await.unwrap());
299        assert_eq!(Configuration::B, set_active_listener.next().await.unwrap());
300        assert_eq!(Configuration::A, set_active_listener.next().await.unwrap());
301    }
302
303    #[fuchsia::test]
304    async fn test_set_last_config_b() {
305        let last_active_config = Configuration::B;
306        let (boot_manager_proxy, mut query_last_active_listener, mut set_active_listener) =
307            create_mock_boot_manager(last_active_config).unwrap();
308
309        let _res = reset_active_slot_with_proxy(boot_manager_proxy).await.unwrap();
310
311        // Read in order the requests sent to boot manager:
312        // 1. Reading the last active config.
313        // 2. Setting the last inactive config as active.
314        // 3. Setting the last active config as active.
315
316        assert_eq!(Configuration::B, query_last_active_listener.next().await.unwrap());
317        assert_eq!(Configuration::A, set_active_listener.next().await.unwrap());
318        assert_eq!(Configuration::B, set_active_listener.next().await.unwrap());
319    }
320
321    #[fuchsia::test]
322    async fn test_set_last_config_recovery() {
323        let last_active_config = Configuration::Recovery;
324        let (boot_manager_proxy, mut query_last_active_listener, set_active_listener) =
325            create_mock_boot_manager(last_active_config).unwrap();
326
327        let _res = reset_active_slot_with_proxy(boot_manager_proxy).await;
328
329        // There should be no attempts to set active slot if the last active slot is recovery.
330        assert_eq!(Configuration::Recovery, query_last_active_listener.next().await.unwrap());
331        assert_eq!(0, set_active_listener.count().await);
332    }
333
334    #[test]
335    fn test_reset_complete() -> std::result::Result<(), anyhow::Error> {
336        let mut state_machine = FactoryResetStateMachine::new();
337        let state = state_machine.get_state();
338        assert_eq!(state, FactoryResetState::Waiting);
339        let state = state_machine
340            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
341        assert_eq!(state, FactoryResetState::Waiting);
342        let state = state_machine
343            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
344        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
345        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
346        assert_eq!(state, FactoryResetState::StartCountdown);
347        let state = state_machine.handle_event(ResetEvent::CountdownFinished);
348        assert_eq!(state, FactoryResetState::ExecuteReset);
349        Ok(())
350    }
351
352    #[test]
353    fn test_reset_complete_reverse() -> std::result::Result<(), anyhow::Error> {
354        let mut state_machine = FactoryResetStateMachine::new();
355        let state = state_machine.get_state();
356        assert_eq!(state, FactoryResetState::Waiting);
357        let state = state_machine
358            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
359        assert_eq!(state, FactoryResetState::Waiting);
360        let state = state_machine
361            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
362        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
363        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
364        assert_eq!(state, FactoryResetState::StartCountdown);
365        let state = state_machine.handle_event(ResetEvent::CountdownFinished);
366        assert_eq!(state, FactoryResetState::ExecuteReset);
367        Ok(())
368    }
369
370    #[test]
371    fn test_reset_cancelled() -> std::result::Result<(), anyhow::Error> {
372        test_reset_cancelled_button(ConsumerControlButton::VolumeUp);
373        test_reset_cancelled_button(ConsumerControlButton::VolumeDown);
374        Ok(())
375    }
376
377    fn test_reset_cancelled_button(button: ConsumerControlButton) {
378        let mut state_machine = FactoryResetStateMachine::new();
379        let state = state_machine
380            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
381        assert_eq!(state, FactoryResetState::Waiting);
382        let state = state_machine
383            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
384        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
385        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
386        assert_eq!(state, FactoryResetState::StartCountdown);
387        let state = state_machine.handle_event(ResetEvent::ButtonPress(button, Phase::Up));
388        assert_eq!(state, FactoryResetState::CancelCountdown);
389        let state = state_machine.handle_event(ResetEvent::CountdownCancelled);
390        assert_eq!(state, FactoryResetState::Waiting);
391        let state = state_machine.handle_event(ResetEvent::ButtonPress(button, Phase::Down));
392        assert_eq!(state, FactoryResetState::AwaitingPolicy(2));
393        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(2, true));
394        assert_eq!(state, FactoryResetState::StartCountdown);
395    }
396
397    #[test]
398    #[should_panic]
399    fn test_early_complete_countdown() {
400        let mut state_machine = FactoryResetStateMachine::new();
401        let state = state_machine
402            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
403        assert_eq!(state, FactoryResetState::Waiting);
404        let _state = state_machine.handle_event(ResetEvent::CountdownFinished);
405    }
406
407    #[test]
408    #[should_panic]
409    fn test_cancelled_countdown_not_complete() {
410        let mut state_machine = FactoryResetStateMachine::new();
411        let state = state_machine
412            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
413        assert_eq!(state, FactoryResetState::Waiting);
414        let state = state_machine
415            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
416        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
417        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
418        assert_eq!(state, FactoryResetState::StartCountdown);
419        let state = state_machine
420            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Up));
421        assert_eq!(state, FactoryResetState::CancelCountdown);
422        let _state = state_machine.handle_event(ResetEvent::CountdownFinished);
423    }
424
425    #[test]
426    fn test_cancelled_countdown_with_extra_button_press() {
427        let mut state_machine = FactoryResetStateMachine::new();
428        let state = state_machine
429            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
430        assert_eq!(state, FactoryResetState::Waiting);
431        let state = state_machine
432            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
433        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
434        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
435        assert_eq!(state, FactoryResetState::StartCountdown);
436        let state = state_machine
437            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Up));
438        assert_eq!(state, FactoryResetState::CancelCountdown);
439        let state = state_machine
440            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Up));
441        assert_eq!(state, FactoryResetState::CancelCountdown);
442    }
443
444    #[test]
445    fn test_reset_complete_button_released() -> std::result::Result<(), anyhow::Error> {
446        let mut state_machine = FactoryResetStateMachine::new();
447        let state = state_machine.get_state();
448        assert_eq!(state, FactoryResetState::Waiting);
449        let state = state_machine
450            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
451        assert_eq!(state, FactoryResetState::Waiting);
452        let state = state_machine
453            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
454        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
455        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
456        assert_eq!(state, FactoryResetState::StartCountdown);
457        let state = state_machine.handle_event(ResetEvent::CountdownFinished);
458        assert_eq!(state, FactoryResetState::ExecuteReset);
459        let state = state_machine
460            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Up));
461        assert_eq!(state, FactoryResetState::AwaitingReset);
462        Ok(())
463    }
464
465    #[test]
466    fn test_reset_complete_multiple_button_presses() -> std::result::Result<(), anyhow::Error> {
467        // Multiple button presses should leave the state machine in AwaitingReset.
468        let mut state_machine = FactoryResetStateMachine::new();
469        let state = state_machine.get_state();
470        assert_eq!(state, FactoryResetState::Waiting);
471        let state = state_machine
472            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
473        assert_eq!(state, FactoryResetState::Waiting);
474        let state = state_machine
475            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
476        assert_eq!(state, FactoryResetState::AwaitingPolicy(1));
477        let state = state_machine.handle_event(ResetEvent::AwaitPolicyResult(1, true));
478        assert_eq!(state, FactoryResetState::StartCountdown);
479        let state = state_machine.handle_event(ResetEvent::CountdownFinished);
480        assert_eq!(state, FactoryResetState::ExecuteReset);
481        let state = state_machine
482            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Up));
483        assert_eq!(state, FactoryResetState::AwaitingReset);
484        let state = state_machine
485            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeDown, Phase::Down));
486        assert_eq!(state, FactoryResetState::AwaitingReset);
487        let state = state_machine
488            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Up));
489        assert_eq!(state, FactoryResetState::AwaitingReset);
490        let state = state_machine
491            .handle_event(ResetEvent::ButtonPress(ConsumerControlButton::VolumeUp, Phase::Down));
492        assert_eq!(state, FactoryResetState::AwaitingReset);
493        Ok(())
494    }
495}