1use anyhow::Error;
6use async_trait::async_trait;
7use fuchsia_component::client::connect_to_protocol;
8#[cfg(test)]
9use mockall::automock;
10use zx::{MonotonicDuration, Status as zx_status};
11use {fidl_fuchsia_hardware_power_statecontrol as powercontrol, fuchsia_async as fasync};
12
13#[cfg_attr(test, automock)]
14#[async_trait(?Send)]
15pub trait RebootHandler {
16 async fn reboot(&self, delay_seconds: Option<u64>) -> Result<(), Error>;
19}
20
21#[derive(Default)]
22pub struct RebootImpl;
23
24impl RebootImpl {
25 async fn request_reboot_with_proxy(
26 &self,
27 delay_seconds: Option<u64>,
28 proxy: powercontrol::AdminProxy,
29 ) -> Result<(), Error> {
30 println!("Rebooting after {:?} seconds...", delay_seconds.unwrap_or(0));
31
32 if let Some(delay) = delay_seconds {
33 fasync::Timer::new(fasync::MonotonicInstant::after(MonotonicDuration::from_seconds(
34 delay.try_into()?,
35 )))
36 .await;
37 }
38
39 proxy
41 .shutdown(&powercontrol::ShutdownOptions {
42 action: Some(powercontrol::ShutdownAction::Reboot),
43 reasons: Some(vec![powercontrol::ShutdownReason::FactoryDataReset]),
44 ..Default::default()
45 })
46 .await?
47 .map_err(|e| zx_status::from_raw(e))?;
48 Ok(())
49 }
50}
51
52#[async_trait(?Send)]
53impl RebootHandler for RebootImpl {
54 async fn reboot(&self, delay_seconds: Option<u64>) -> Result<(), Error> {
55 let proxy = connect_to_protocol::<powercontrol::AdminMarker>()?;
56 self.request_reboot_with_proxy(delay_seconds, proxy).await
57 }
58}
59
60#[cfg(test)]
61mod test {
62 use super::*;
63 use fidl_fuchsia_hardware_power_statecontrol::{
64 ShutdownAction, ShutdownOptions, ShutdownReason,
65 };
66 use fuchsia_async::TimeoutExt;
67 use futures::channel::mpsc;
68 use futures::{StreamExt, TryStreamExt};
69 use {fidl_fuchsia_hardware_power_statecontrol as powercontrol, fuchsia_async as fasync};
70
71 fn create_mock_powercontrol_server()
73 -> Result<(powercontrol::AdminProxy, mpsc::Receiver<powercontrol::ShutdownOptions>), Error>
74 {
75 let (mut sender, receiver) = mpsc::channel(1);
76 let (proxy, mut request_stream) =
77 fidl::endpoints::create_proxy_and_stream::<powercontrol::AdminMarker>();
78
79 fasync::Task::local(async move {
80 while let Some(request) =
81 request_stream.try_next().await.expect("failed to read mock request")
82 {
83 match request {
84 powercontrol::AdminRequest::Shutdown { options, responder } => {
85 sender.start_send(options).unwrap();
86 let result: powercontrol::AdminShutdownResult = { Ok(()) };
87 responder.send(result).ok();
88 }
89 _ => {
90 panic!("Mock server not configured to handle request");
91 }
92 }
93 }
94 })
95 .detach();
96
97 Ok((proxy, receiver))
98 }
99
100 #[fuchsia::test]
101 async fn test_reboot_reason_no_delay() {
102 let (proxy, mut receiver) = create_mock_powercontrol_server().unwrap();
103
104 let reboot = RebootImpl::default();
105 reboot.request_reboot_with_proxy(None, proxy).await.unwrap();
106
107 let options =
108 receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
109
110 assert_eq!(
111 options,
112 ShutdownOptions {
113 action: Some(ShutdownAction::Reboot),
114 reasons: Some(vec![ShutdownReason::FactoryDataReset]),
115 ..Default::default()
116 }
117 );
118 }
119
120 #[fuchsia::test]
121 async fn test_reboot_with_delay() {
122 let delay_seconds = 1;
123 let (proxy, mut receiver) = create_mock_powercontrol_server().unwrap();
124
125 let start_time = fasync::MonotonicInstant::now();
126 let reboot = RebootImpl::default();
127 reboot.request_reboot_with_proxy(Some(delay_seconds), proxy).await.unwrap();
128
129 let options =
130 receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
131
132 let end_time = fasync::MonotonicInstant::now();
133
134 assert!((end_time - start_time).into_seconds() >= delay_seconds.try_into().unwrap());
135 assert_eq!(
136 options,
137 ShutdownOptions {
138 action: Some(ShutdownAction::Reboot),
139 reasons: Some(vec![ShutdownReason::FactoryDataReset]),
140 ..Default::default()
141 }
142 );
143 }
144}