1use anyhow::Error;
6use fidl_fuchsia_hardware_power_statecontrol::{
7 AdminPerformRebootResult, AdminProxy, AdminRequest, AdminRequestStream, RebootOptions,
8 RebootReason2, ShutdownOptions, ShutdownReason,
9};
10use fuchsia_async as fasync;
11use futures::{TryFutureExt, TryStreamExt};
12use std::sync::Arc;
13
14fn shutdown_reason_to_reboot_reason(reason: ShutdownReason) -> Option<RebootReason2> {
17 match reason {
18 ShutdownReason::UserRequest => Some(RebootReason2::UserRequest),
19 ShutdownReason::DeveloperRequest => Some(RebootReason2::DeveloperRequest),
20 ShutdownReason::SystemUpdate => Some(RebootReason2::SystemUpdate),
21 ShutdownReason::RetrySystemUpdate => Some(RebootReason2::RetrySystemUpdate),
22 ShutdownReason::HighTemperature => Some(RebootReason2::HighTemperature),
23 ShutdownReason::FactoryDataReset => Some(RebootReason2::FactoryDataReset),
24 ShutdownReason::SessionFailure => Some(RebootReason2::SessionFailure),
25 ShutdownReason::CriticalComponentFailure => Some(RebootReason2::CriticalComponentFailure),
26 ShutdownReason::ZbiSwap => Some(RebootReason2::ZbiSwap),
27 ShutdownReason::OutOfMemory => Some(RebootReason2::OutOfMemory),
28 ShutdownReason::NetstackMigration => Some(RebootReason2::NetstackMigration),
29 ShutdownReason::AndroidUnexpectedReason => Some(RebootReason2::AndroidUnexpectedReason),
30 ShutdownReason::AndroidRescueParty => Some(RebootReason2::AndroidRescueParty),
31 ShutdownReason::AndroidCriticalProcessFailure => {
32 Some(RebootReason2::AndroidCriticalProcessFailure)
33 }
34 _ => None,
35 }
36}
37
38fn shutdown_options_to_reboot_options(options: ShutdownOptions) -> RebootOptions {
39 let reasons = options
40 .reasons
41 .map(|reasons| reasons.into_iter().filter_map(shutdown_reason_to_reboot_reason).collect());
42 RebootOptions { reasons, ..Default::default() }
43}
44
45pub struct MockRebootService {
46 call_hook: Box<dyn Fn(RebootOptions) -> AdminPerformRebootResult + Send + Sync>,
47}
48
49impl MockRebootService {
50 pub fn new(
54 call_hook: Box<dyn Fn(RebootOptions) -> AdminPerformRebootResult + Send + Sync>,
55 ) -> Self {
56 Self { call_hook }
57 }
58
59 pub async fn run_reboot_service(
62 self: Arc<Self>,
63 mut stream: AdminRequestStream,
64 ) -> Result<(), Error> {
65 while let Some(event) = stream.try_next().await.expect("received request") {
66 match event {
67 AdminRequest::PerformReboot { options, responder } => {
68 let result = (self.call_hook)(options);
69 responder.send(result)?;
70 }
71 AdminRequest::Shutdown { options, responder } => {
72 let result = (self.call_hook)(shutdown_options_to_reboot_options(options));
73 responder.send(result)?;
74 }
75 _ => {
76 panic!("unhandled RebootService method {event:?}");
77 }
78 }
79 }
80 Ok(())
81 }
82
83 pub fn spawn_reboot_service(self: Arc<Self>) -> AdminProxy {
86 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<
87 fidl_fuchsia_hardware_power_statecontrol::AdminMarker,
88 >();
89
90 fasync::Task::spawn(
91 self.run_reboot_service(stream)
92 .unwrap_or_else(|e| panic!("error running reboot service: {e:?}")),
93 )
94 .detach();
95
96 proxy
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use fidl_fuchsia_hardware_power_statecontrol::RebootReason2;
104 use fuchsia_async as fasync;
105 use std::sync::atomic::{AtomicU32, Ordering};
106
107 #[fasync::run_singlethreaded(test)]
108 async fn test_mock_reboot() {
109 let reboot_service = Arc::new(MockRebootService::new(Box::new(|_| Ok(()))));
110
111 let reboot_service_clone = Arc::clone(&reboot_service);
112 let proxy = reboot_service_clone.spawn_reboot_service();
113
114 proxy
115 .perform_reboot(&RebootOptions {
116 reasons: Some(vec![RebootReason2::SystemUpdate]),
117 ..Default::default()
118 })
119 .await
120 .expect("made reboot call")
121 .expect("reboot call succeeded");
122 }
123
124 #[fasync::run_singlethreaded(test)]
125 async fn test_mock_reboot_fails() {
126 let reboot_service =
127 Arc::new(MockRebootService::new(Box::new(|_| Err(zx::Status::INTERNAL.into_raw()))));
128
129 let reboot_service_clone = Arc::clone(&reboot_service);
130 let proxy = reboot_service_clone.spawn_reboot_service();
131
132 let reboot_result = proxy
133 .perform_reboot(&RebootOptions {
134 reasons: Some(vec![RebootReason2::SystemUpdate]),
135 ..Default::default()
136 })
137 .await
138 .expect("made reboot call");
139 assert_eq!(reboot_result, Err(zx::Status::INTERNAL.into_raw()));
140 }
141
142 #[fasync::run_singlethreaded(test)]
143 async fn test_mock_reboot_call_hook() {
144 let reboot_service = Arc::new(MockRebootService::new(Box::new(|options| {
145 if let Some(reasons) = options.reasons {
146 match &reasons[..] {
147 [RebootReason2::DeveloperRequest] => Ok(()),
148 _ => Err(zx::Status::NOT_SUPPORTED.into_raw()),
149 }
150 } else {
151 Err(zx::Status::NOT_SUPPORTED.into_raw())
152 }
153 })));
154
155 let reboot_service_clone = Arc::clone(&reboot_service);
156 let proxy = reboot_service_clone.spawn_reboot_service();
157
158 let () = proxy
160 .perform_reboot(&RebootOptions {
161 reasons: Some(vec![RebootReason2::DeveloperRequest]),
162 ..Default::default()
163 })
164 .await
165 .expect("made reboot call")
166 .expect("reboot call succeeded");
167
168 let error_reboot_result = proxy
170 .perform_reboot(&RebootOptions {
171 reasons: Some(vec![RebootReason2::SystemUpdate]),
172 ..Default::default()
173 })
174 .await
175 .expect("made reboot call");
176 assert_eq!(error_reboot_result, Err(zx::Status::NOT_SUPPORTED.into_raw()));
177 }
178
179 #[fasync::run_singlethreaded(test)]
180 async fn test_mock_reboot_with_external_state() {
181 let called = Arc::new(AtomicU32::new(0));
182 let called_clone = Arc::clone(&called);
183 let reboot_service = Arc::new(MockRebootService::new(Box::new(move |_| {
184 called_clone.fetch_add(1, Ordering::SeqCst);
185 Ok(())
186 })));
187
188 let reboot_service_clone = Arc::clone(&reboot_service);
189 let proxy = reboot_service_clone.spawn_reboot_service();
190
191 proxy
192 .perform_reboot(&RebootOptions {
193 reasons: Some(vec![RebootReason2::SystemUpdate]),
194 ..Default::default()
195 })
196 .await
197 .expect("made reboot call")
198 .expect("reboot call succeeded");
199 assert_eq!(called.load(Ordering::SeqCst), 1);
200 }
201}