1#![allow(clippy::let_unit_value)]
6
7use anyhow::{Error, anyhow};
8use async_trait::async_trait;
9use fidl_fuchsia_mem::Buffer;
10use fidl_fuchsia_paver as paver;
11use fuchsia_async as fasync;
12use fuchsia_sync::Mutex;
13use futures::channel::mpsc;
14use futures::lock::Mutex as AsyncMutex;
15use futures::prelude::*;
16use std::sync::Arc;
17use zx::{Status, Vmo, VmoOptions};
18
19fn verify_buffer(buffer: &mut Buffer) {
20 let size = buffer.vmo.get_size().expect("vmo size query to succeed");
23 buffer.vmo.set_size(size * 2).expect("vmo must be resizable");
24}
25
26fn read_mem_buffer(buffer: &Buffer) -> Vec<u8> {
27 let mut res = vec![0; buffer.size.try_into().expect("usize")];
28 buffer.vmo.read(&mut res[..], 0).expect("vmo read to succeed");
29 res
30}
31
32fn write_mem_buffer(payload: Vec<u8>) -> Buffer {
33 let vmo =
34 Vmo::create_with_opts(VmoOptions::RESIZABLE, payload.len() as u64).expect("Creating VMO");
35 vmo.write(&payload, 0).expect("writing to VMO");
36 Buffer { vmo, size: payload.len() as u64 }
37}
38
39fn write_mem_buffer_custom_buffer_size((payload, size): (Vec<u8>, u64)) -> Buffer {
40 let vmo =
41 Vmo::create_with_opts(VmoOptions::RESIZABLE, payload.len() as u64).expect("Creating VMO");
42 let () = vmo.write(&payload, 0).expect("writing to VMO");
43 Buffer { vmo, size }
44}
45
46#[derive(Debug, PartialEq, Eq, Clone)]
47pub enum PaverEvent {
48 ReadAsset { configuration: paver::Configuration, asset: paver::Asset },
49 WriteAsset { configuration: paver::Configuration, asset: paver::Asset, payload: Vec<u8> },
50 ReadFirmware { configuration: paver::Configuration, firmware_type: String },
51 WriteFirmware { configuration: paver::Configuration, firmware_type: String, payload: Vec<u8> },
52 QueryActiveConfiguration,
53 QueryConfigurationLastSetActive,
54 QueryCurrentConfiguration,
55 QueryConfigurationStatus { configuration: paver::Configuration },
56 QueryConfigurationStatusAndBootAttempts { configuration: paver::Configuration },
57 SetConfigurationHealthy { configuration: paver::Configuration },
58 SetConfigurationActive { configuration: paver::Configuration },
59 SetConfigurationUnbootable { configuration: paver::Configuration },
60 SetOneShotRecovery,
61 BootManagerFlush,
62 DataSinkFlush,
63}
64
65impl PaverEvent {
66 pub fn from_data_sink_request(request: &paver::DataSinkRequest) -> PaverEvent {
67 match request {
68 paver::DataSinkRequest::WriteAsset { configuration, asset, payload, .. } => {
69 PaverEvent::WriteAsset {
70 configuration: configuration.to_owned(),
71 asset: asset.to_owned(),
72 payload: read_mem_buffer(payload),
73 }
74 }
75 paver::DataSinkRequest::WriteFirmware {
76 configuration,
77 type_: firmware_type,
78 payload,
79 ..
80 } => PaverEvent::WriteFirmware {
81 configuration: configuration.to_owned(),
82 firmware_type: firmware_type.to_owned(),
83 payload: read_mem_buffer(payload),
84 },
85 paver::DataSinkRequest::Flush { .. } => PaverEvent::DataSinkFlush {},
86 paver::DataSinkRequest::ReadAsset { configuration, asset, .. } => {
87 PaverEvent::ReadAsset {
88 configuration: configuration.to_owned(),
89 asset: asset.to_owned(),
90 }
91 }
92 paver::DataSinkRequest::ReadFirmware { configuration, type_, .. } => {
93 PaverEvent::ReadFirmware {
94 configuration: configuration.to_owned(),
95 firmware_type: type_.to_owned(),
96 }
97 }
98 request => panic!("Unhandled method Paver::{}", request.method_name()),
99 }
100 }
101
102 pub fn from_boot_manager_request(request: &paver::BootManagerRequest) -> PaverEvent {
103 match request {
104 paver::BootManagerRequest::QueryActiveConfiguration { .. } => {
105 PaverEvent::QueryActiveConfiguration
106 }
107 paver::BootManagerRequest::QueryConfigurationLastSetActive { .. } => {
108 PaverEvent::QueryConfigurationLastSetActive
109 }
110 paver::BootManagerRequest::QueryCurrentConfiguration { .. } => {
111 PaverEvent::QueryCurrentConfiguration
112 }
113 paver::BootManagerRequest::QueryConfigurationStatus { configuration, .. } => {
114 PaverEvent::QueryConfigurationStatus { configuration: configuration.to_owned() }
115 }
116 paver::BootManagerRequest::QueryConfigurationStatusAndBootAttempts {
117 configuration,
118 ..
119 } => PaverEvent::QueryConfigurationStatusAndBootAttempts {
120 configuration: configuration.to_owned(),
121 },
122 paver::BootManagerRequest::SetConfigurationHealthy { configuration, .. } => {
123 PaverEvent::SetConfigurationHealthy { configuration: configuration.to_owned() }
124 }
125 paver::BootManagerRequest::SetConfigurationActive { configuration, .. } => {
126 PaverEvent::SetConfigurationActive { configuration: configuration.to_owned() }
127 }
128 paver::BootManagerRequest::SetConfigurationUnbootable { configuration, .. } => {
129 PaverEvent::SetConfigurationUnbootable { configuration: configuration.to_owned() }
130 }
131 paver::BootManagerRequest::SetOneShotRecovery { .. } => PaverEvent::SetOneShotRecovery,
132 paver::BootManagerRequest::Flush { .. } => PaverEvent::BootManagerFlush,
133 }
134 }
135}
136
137#[async_trait]
149pub trait Hook: Sync {
150 async fn boot_manager(
151 &self,
152 request: paver::BootManagerRequest,
153 ) -> Option<paver::BootManagerRequest> {
154 Some(request)
155 }
156
157 async fn data_sink(&self, request: paver::DataSinkRequest) -> Option<paver::DataSinkRequest> {
158 Some(request)
159 }
160}
161
162pub mod hooks {
163 use super::*;
164
165 pub fn return_error<F>(callback: F) -> ReturnError<F>
171 where
172 F: Fn(&PaverEvent) -> Status,
173 {
174 ReturnError(callback)
175 }
176
177 pub struct ReturnError<F>(F);
178
179 #[async_trait]
180 impl<F> Hook for ReturnError<F>
181 where
182 F: Fn(&PaverEvent) -> Status + Sync,
183 {
184 async fn boot_manager(
185 &self,
186 request: paver::BootManagerRequest,
187 ) -> Option<paver::BootManagerRequest> {
188 let status = (self.0)(&PaverEvent::from_boot_manager_request(&request));
189 if status == Status::OK {
190 Some(request)
191 } else {
192 let _ = match request {
194 paver::BootManagerRequest::QueryActiveConfiguration { responder, .. } => {
195 responder.send(Err(status.into_raw()))
196 }
197 paver::BootManagerRequest::QueryConfigurationLastSetActive {
198 responder,
199 ..
200 } => responder.send(Err(status.into_raw())),
201 paver::BootManagerRequest::QueryCurrentConfiguration { responder, .. } => {
202 responder.send(Err(status.into_raw()))
203 }
204 paver::BootManagerRequest::QueryConfigurationStatus { responder, .. } => {
205 responder.send(Err(status.into_raw()))
206 }
207 paver::BootManagerRequest::QueryConfigurationStatusAndBootAttempts {
208 responder,
209 ..
210 } => responder.send(Err(status.into_raw())),
211 paver::BootManagerRequest::SetConfigurationHealthy { responder, .. } => {
212 responder.send(status.into_raw())
213 }
214 paver::BootManagerRequest::SetConfigurationActive { responder, .. } => {
215 responder.send(status.into_raw())
216 }
217 paver::BootManagerRequest::SetConfigurationUnbootable { responder, .. } => {
218 responder.send(status.into_raw())
219 }
220 paver::BootManagerRequest::SetOneShotRecovery { responder, .. } => {
221 responder.send(Ok(()))
222 }
223 paver::BootManagerRequest::Flush { responder } => {
224 responder.send(status.into_raw())
225 }
226 };
227 None
228 }
229 }
230
231 async fn data_sink(
232 &self,
233 request: paver::DataSinkRequest,
234 ) -> Option<paver::DataSinkRequest> {
235 let status = (self.0)(&PaverEvent::from_data_sink_request(&request));
236 if status == Status::OK {
237 Some(request)
238 } else {
239 let _ = match request {
241 paver::DataSinkRequest::WriteFirmware { responder, .. } => {
242 responder.send(&paver::WriteFirmwareResult::Status(status.into_raw()))
243 }
244 paver::DataSinkRequest::ReadAsset { responder, .. } => {
245 responder.send(Err(status.into_raw()))
246 }
247 paver::DataSinkRequest::WriteAsset { responder, .. } => {
248 responder.send(status.into_raw())
249 }
250 paver::DataSinkRequest::Flush { responder, .. } => {
251 responder.send(status.into_raw())
252 }
253 request => panic!("Unhandled method Paver::{}", request.method_name()),
254 };
255 None
256 }
257 }
258 }
259
260 pub fn config_status<F>(callback: F) -> ConfigStatus<F>
262 where
263 F: Fn(paver::Configuration) -> Result<paver::ConfigurationStatus, Status>,
264 {
265 ConfigStatus(callback)
266 }
267
268 pub struct ConfigStatus<F>(F);
269
270 #[async_trait]
271 impl<F> Hook for ConfigStatus<F>
272 where
273 F: Fn(paver::Configuration) -> Result<paver::ConfigurationStatus, Status> + Sync,
274 {
275 async fn boot_manager(
276 &self,
277 request: paver::BootManagerRequest,
278 ) -> Option<paver::BootManagerRequest> {
279 match request {
280 paver::BootManagerRequest::QueryConfigurationStatus {
281 configuration,
282 responder,
283 } => {
284 let result = (self.0)(configuration).map_err(Status::into_raw);
285 let _ = responder.send(result);
287 None
288 }
289 request => Some(request),
290 }
291 }
292 }
293
294 pub fn config_status_and_boot_attempts<F>(callback: F) -> ConfigStatusAndBootAttempts<F>
296 where
297 F: Fn(paver::Configuration) -> Result<(paver::ConfigurationStatus, Option<u8>), Status>,
298 {
299 ConfigStatusAndBootAttempts(callback)
300 }
301
302 pub struct ConfigStatusAndBootAttempts<F>(F);
303
304 #[async_trait]
305 impl<F> Hook for ConfigStatusAndBootAttempts<F>
306 where
307 F: Fn(paver::Configuration) -> Result<(paver::ConfigurationStatus, Option<u8>), Status>
308 + Sync,
309 {
310 async fn boot_manager(
311 &self,
312 request: paver::BootManagerRequest,
313 ) -> Option<paver::BootManagerRequest> {
314 let mut response_storage;
315 match request {
316 paver::BootManagerRequest::QueryConfigurationStatusAndBootAttempts {
317 configuration,
318 responder,
319 } => {
320 let raw_values = (self.0)(configuration).map_err(Status::into_raw);
321 let result = match raw_values {
322 Ok((status, boot_attempts)) => {
323 response_storage = paver::BootManagerQueryConfigurationStatusAndBootAttemptsResponse::default();
324 response_storage.status = Some(status);
325 response_storage.boot_attempts = boot_attempts;
326 Ok(&response_storage)
327 }
328 Err(e) => Err(e),
329 };
330
331 let _ = responder.send(result);
333 None
334 }
335 request => Some(request),
336 }
337 }
338 }
339
340 pub fn write_firmware<F>(callback: F) -> WriteFirmware<F>
342 where
343 F: Fn(
344 paver::Configuration,
345 String,
346 Vec<u8>,
347 ) -> paver::WriteFirmwareResult,
348 {
349 WriteFirmware(callback)
350 }
351
352 pub struct WriteFirmware<F>(F);
353
354 #[async_trait]
355 impl<F> Hook for WriteFirmware<F>
356 where
357 F: Fn(
358 paver::Configuration,
359 String,
360 Vec<u8>,
361 ) -> paver::WriteFirmwareResult
362 + Sync,
363 {
364 async fn data_sink(
365 &self,
366 request: paver::DataSinkRequest,
367 ) -> Option<paver::DataSinkRequest> {
368 match request {
369 paver::DataSinkRequest::WriteFirmware {
370 configuration,
371 type_: firmware_type,
372 payload,
373 responder,
374 } => {
375 let result = (self.0)(configuration, firmware_type, read_mem_buffer(&payload));
376 let _ = responder.send(&result);
378 None
379 }
380 request => Some(request),
381 }
382 }
383 }
384
385 pub fn read_firmware<F>(callback: F) -> ReadFirmware<F>
387 where
388 F: Fn(paver::Configuration, String) -> Result<Vec<u8>, Status>,
389 {
390 ReadFirmware(callback)
391 }
392
393 pub struct ReadFirmware<F>(F);
394
395 #[async_trait]
396 impl<F> Hook for ReadFirmware<F>
397 where
398 F: Fn(paver::Configuration, String) -> Result<Vec<u8>, Status> + Sync,
399 {
400 async fn data_sink(
401 &self,
402 request: paver::DataSinkRequest,
403 ) -> Option<paver::DataSinkRequest> {
404 match request {
405 paver::DataSinkRequest::ReadFirmware { configuration, type_, responder } => {
406 let result = (self.0)(configuration, type_)
407 .map(write_mem_buffer)
408 .map_err(Status::into_raw);
409 let _ = responder.send(result);
411 None
412 }
413 request => Some(request),
414 }
415 }
416 }
417
418 pub fn read_firmware_custom_buffer_size<F>(callback: F) -> ReadFirmwareCustomBufferSize<F>
421 where
422 F: Fn(paver::Configuration, String) -> Result<(Vec<u8>, u64), Status>,
423 {
424 ReadFirmwareCustomBufferSize(callback)
425 }
426
427 pub struct ReadFirmwareCustomBufferSize<F>(F);
428
429 #[async_trait]
430 impl<F> Hook for ReadFirmwareCustomBufferSize<F>
431 where
432 F: Fn(paver::Configuration, String) -> Result<(Vec<u8>, u64), Status> + Sync,
433 {
434 async fn data_sink(
435 &self,
436 request: paver::DataSinkRequest,
437 ) -> Option<paver::DataSinkRequest> {
438 match request {
439 paver::DataSinkRequest::ReadFirmware { configuration, type_, responder } => {
440 let result = (self.0)(configuration, type_)
441 .map(write_mem_buffer_custom_buffer_size)
442 .map_err(Status::into_raw);
443 let _ = responder.send(result);
445 None
446 }
447 request => Some(request),
448 }
449 }
450 }
451
452 pub fn read_asset<F>(callback: F) -> ReadAsset<F>
454 where
455 F: Fn(paver::Configuration, paver::Asset) -> Result<Vec<u8>, Status>,
456 {
457 ReadAsset(callback)
458 }
459
460 pub struct ReadAsset<F>(F);
461
462 #[async_trait]
463 impl<F> Hook for ReadAsset<F>
464 where
465 F: Fn(paver::Configuration, paver::Asset) -> Result<Vec<u8>, Status> + Sync,
466 {
467 async fn data_sink(
468 &self,
469 request: paver::DataSinkRequest,
470 ) -> Option<paver::DataSinkRequest> {
471 match request {
472 paver::DataSinkRequest::ReadAsset { configuration, asset, responder } => {
473 let result = (self.0)(configuration, asset)
474 .map(write_mem_buffer)
475 .map_err(Status::into_raw);
476 let _ = responder.send(result);
478 None
479 }
480 request => Some(request),
481 }
482 }
483 }
484
485 pub fn read_asset_custom_buffer_size<F>(callback: F) -> ReadAssetCustomBufferSize<F>
488 where
489 F: Fn(paver::Configuration, paver::Asset) -> Result<(Vec<u8>, u64), Status>,
490 {
491 ReadAssetCustomBufferSize(callback)
492 }
493
494 pub struct ReadAssetCustomBufferSize<F>(F);
495
496 #[async_trait]
497 impl<F> Hook for ReadAssetCustomBufferSize<F>
498 where
499 F: Fn(paver::Configuration, paver::Asset) -> Result<(Vec<u8>, u64), Status> + Sync,
500 {
501 async fn data_sink(
502 &self,
503 request: paver::DataSinkRequest,
504 ) -> Option<paver::DataSinkRequest> {
505 match request {
506 paver::DataSinkRequest::ReadAsset { configuration, asset, responder } => {
507 let result = (self.0)(configuration, asset)
508 .map(write_mem_buffer_custom_buffer_size)
509 .map_err(Status::into_raw);
510 let _ = responder.send(result);
512 None
513 }
514 request => Some(request),
515 }
516 }
517 }
518
519 pub fn throttle() -> (ThrottleHook, Throttle) {
521 let (send, recv) = mpsc::unbounded();
522 (ThrottleHook(AsyncMutex::new(Some(recv))), Throttle(send))
523 }
524
525 pub struct Throttle(mpsc::UnboundedSender<PaverEvent>);
528
529 impl Throttle {
530 pub fn emit_next_paver_event(&self, expected_event: &PaverEvent) {
531 self.0.unbounded_send(expected_event.clone()).expect("emit paver event");
532 }
533
534 pub fn emit_next_paver_events(&self, expected_events: &[PaverEvent]) {
535 for event in expected_events.iter() {
536 self.emit_next_paver_event(event);
537 }
538 }
539 }
540
541 pub struct ThrottleHook(AsyncMutex<Option<mpsc::UnboundedReceiver<PaverEvent>>>);
542
543 #[async_trait]
544 impl Hook for ThrottleHook {
545 async fn boot_manager(
546 &self,
547 request: paver::BootManagerRequest,
548 ) -> Option<paver::BootManagerRequest> {
549 let mut optional_recv = self.0.lock().await;
550 if let Some(recv) = optional_recv.as_mut() {
551 if let Some(expected_request) = recv.next().await {
552 assert_eq!(PaverEvent::from_boot_manager_request(&request), expected_request);
553 } else {
554 assert!(optional_recv.take().is_some());
555 }
556 }
557 Some(request)
558 }
559
560 async fn data_sink(
561 &self,
562 request: paver::DataSinkRequest,
563 ) -> Option<paver::DataSinkRequest> {
564 let mut optional_recv = self.0.lock().await;
565 if let Some(recv) = optional_recv.as_mut() {
566 if let Some(expected_request) = recv.next().await {
567 assert_eq!(PaverEvent::from_data_sink_request(&request), expected_request);
568 } else {
569 assert!(optional_recv.take().is_some());
570 }
571 }
572 Some(request)
573 }
574 }
575}
576
577#[allow(clippy::type_complexity)]
578pub struct MockPaverServiceBuilder {
579 hooks: Vec<Box<dyn Hook + Send + Sync>>,
580 event_hook: Option<Box<dyn Fn(&PaverEvent) + Send + Sync>>,
581 active_config: paver::Configuration,
582 current_config: paver::Configuration,
583 boot_manager_close_with_epitaph: Option<Status>,
584}
585
586impl MockPaverServiceBuilder {
587 #[allow(clippy::new_without_default)]
588 pub fn new() -> Self {
589 Self {
590 hooks: vec![],
591 event_hook: None,
592 active_config: paver::Configuration::A,
593 current_config: paver::Configuration::A,
594 boot_manager_close_with_epitaph: None,
595 }
596 }
597
598 pub fn insert_hook(mut self, hook: impl Hook + Send + 'static) -> Self {
600 self.hooks.push(Box::new(hook));
601 self
602 }
603
604 pub fn event_hook<F>(mut self, event_hook: F) -> Self
607 where
608 F: Fn(&PaverEvent) + Send + Sync + 'static,
609 {
610 self.event_hook = Some(Box::new(event_hook));
611 self
612 }
613
614 pub fn active_config(mut self, active_config: paver::Configuration) -> Self {
615 self.active_config = active_config;
616 self
617 }
618
619 pub fn current_config(mut self, current_config: paver::Configuration) -> Self {
620 self.current_config = current_config;
621 self
622 }
623
624 pub fn boot_manager_close_with_epitaph(mut self, status: Status) -> Self {
625 self.boot_manager_close_with_epitaph = Some(status);
626 self
627 }
628
629 pub fn build(self) -> MockPaverService {
630 MockPaverService {
631 hooks: self.hooks,
632 events: Mutex::new(vec![]),
633 event_hook: self.event_hook.unwrap_or_else(|| Box::new(|_| ())),
634 active_config: self.active_config,
635 current_config: self.current_config,
636 boot_manager_close_with_epitaph: self.boot_manager_close_with_epitaph,
637 }
638 }
639}
640
641pub struct MockPaverService {
642 hooks: Vec<Box<dyn Hook + Send + Sync>>,
643 events: Mutex<Vec<PaverEvent>>,
644 event_hook: Box<dyn Fn(&PaverEvent) + Send + Sync>,
645 active_config: paver::Configuration,
646 current_config: paver::Configuration,
647 boot_manager_close_with_epitaph: Option<Status>,
648}
649
650impl MockPaverService {
651 pub fn take_events(&self) -> Vec<PaverEvent> {
652 std::mem::take(&mut *self.events.lock())
653 }
654
655 pub fn spawn_data_sink_service(self: &Arc<Self>) -> paver::DataSinkProxy {
657 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<paver::DataSinkMarker>();
658
659 fasync::Task::spawn(
660 Arc::clone(self)
661 .run_data_sink_service(stream)
662 .unwrap_or_else(|e| panic!("error running data sink service: {:#}", anyhow!(e))),
663 )
664 .detach();
665
666 proxy
667 }
668
669 pub fn spawn_boot_manager_service(self: &Arc<Self>) -> paver::BootManagerProxy {
671 let (proxy, server_end) = fidl::endpoints::create_proxy::<paver::BootManagerMarker>();
672
673 fasync::Task::spawn(
674 Arc::clone(self)
675 .run_boot_manager_service(server_end)
676 .unwrap_or_else(|e| panic!("error running boot manager service: {:#}", anyhow!(e))),
677 )
678 .detach();
679
680 proxy
681 }
682
683 pub fn spawn_paver_service(self: &Arc<Self>) -> paver::PaverProxy {
685 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<paver::PaverMarker>();
686
687 fasync::Task::spawn(
688 Arc::clone(self)
689 .run_paver_service(stream)
690 .unwrap_or_else(|e| panic!("error running paver service: {:#}", anyhow!(e))),
691 )
692 .detach();
693
694 proxy
695 }
696
697 fn push_event(self: &Arc<Self>, event: PaverEvent) {
698 (*self.event_hook)(&event);
699 self.events.lock().push(event);
700 }
701
702 async fn run_data_sink_service(
703 self: Arc<Self>,
704 mut stream: paver::DataSinkRequestStream,
705 ) -> Result<(), Error> {
706 'req_stream: while let Some(mut request) = stream.try_next().await? {
707 self.push_event(PaverEvent::from_data_sink_request(&request));
708
709 for hook in self.hooks.iter() {
710 match hook.data_sink(request).await {
711 Some(r) => request = r,
712 None => continue 'req_stream,
713 }
714 }
715
716 let _ = match request {
718 paver::DataSinkRequest::WriteAsset { mut payload, responder, .. } => {
719 verify_buffer(&mut payload);
720 responder.send(Status::OK.into_raw())
721 }
722 paver::DataSinkRequest::WriteFirmware { mut payload, responder, .. } => {
723 verify_buffer(&mut payload);
724 responder.send(&paver::WriteFirmwareResult::Status(Status::OK.into_raw()))
725 }
726 paver::DataSinkRequest::Flush { responder } => {
727 responder.send(Status::OK.into_raw())
728 }
729 paver::DataSinkRequest::ReadAsset { responder, .. } => {
730 responder.send(Ok(write_mem_buffer(vec![0u8; 4096])))
734 }
735 paver::DataSinkRequest::ReadFirmware { responder, .. } => {
736 responder.send(Ok(write_mem_buffer(vec![0u8; 4096])))
740 }
741 request => panic!("Unhandled method Paver::{}", request.method_name()),
742 };
743 }
744
745 Ok(())
746 }
747
748 async fn run_boot_manager_service(
749 self: Arc<Self>,
750 boot_manager: fidl::endpoints::ServerEnd<paver::BootManagerMarker>,
751 ) -> Result<(), Error> {
752 if let Some(status) = self.boot_manager_close_with_epitaph {
753 boot_manager.close_with_epitaph(status)?;
754 return Ok(());
755 };
756
757 let mut stream = boot_manager.into_stream();
758
759 'req_stream: while let Some(mut request) = stream.try_next().await? {
760 self.push_event(PaverEvent::from_boot_manager_request(&request));
761
762 for hook in self.hooks.iter() {
763 match hook.boot_manager(request).await {
764 Some(r) => request = r,
765 None => continue 'req_stream,
766 }
767 }
768
769 let _ = match request {
771 paver::BootManagerRequest::QueryActiveConfiguration { responder } => {
772 let result = if self.active_config == paver::Configuration::Recovery {
773 Err(Status::NOT_SUPPORTED.into_raw())
774 } else {
775 Ok(self.active_config)
776 };
777 responder.send(result)
778 }
779 paver::BootManagerRequest::QueryConfigurationLastSetActive { responder } => {
780 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))
782 }
783 paver::BootManagerRequest::QueryCurrentConfiguration { responder } => {
784 responder.send(Ok(self.current_config))
785 }
786 paver::BootManagerRequest::QueryConfigurationStatus { responder, .. } => {
787 responder.send(Ok(paver::ConfigurationStatus::Healthy))
788 }
789 paver::BootManagerRequest::QueryConfigurationStatusAndBootAttempts {
790 responder,
791 ..
792 } => responder.send(Ok(
793 &paver::BootManagerQueryConfigurationStatusAndBootAttemptsResponse {
794 status: Some(paver::ConfigurationStatus::Healthy),
795 ..Default::default()
796 },
797 )),
798 paver::BootManagerRequest::SetConfigurationHealthy {
799 configuration,
800 responder,
801 ..
802 } => {
803 let status = if configuration == paver::Configuration::Recovery {
805 Status::INVALID_ARGS
806 } else {
807 Status::OK
808 };
809 responder.send(status.into_raw())
810 }
811 paver::BootManagerRequest::SetConfigurationActive { responder, .. } => {
812 responder.send(Status::OK.into_raw())
813 }
814 paver::BootManagerRequest::SetConfigurationUnbootable { responder, .. } => {
815 responder.send(Status::OK.into_raw())
816 }
817 paver::BootManagerRequest::SetOneShotRecovery { responder, .. } => {
818 responder.send(Ok(()))
819 }
820 paver::BootManagerRequest::Flush { responder } => {
821 responder.send(Status::OK.into_raw())
822 }
823 };
824 }
825
826 Ok(())
827 }
828
829 pub async fn run_paver_service(
830 self: Arc<Self>,
831 mut stream: paver::PaverRequestStream,
832 ) -> Result<(), Error> {
833 while let Some(request) = stream.try_next().await? {
834 match request {
835 paver::PaverRequest::FindDataSink { data_sink, .. } => {
836 let paver_service_clone = self.clone();
837 fasync::Task::spawn(
838 paver_service_clone
839 .run_data_sink_service(data_sink.into_stream())
840 .unwrap_or_else(|e| panic!("error running data sink service: {e:?}")),
841 )
842 .detach();
843 }
844 paver::PaverRequest::FindBootManager { boot_manager, .. } => {
845 let paver_service_clone = self.clone();
846 fasync::Task::spawn(
847 paver_service_clone.run_boot_manager_service(boot_manager).unwrap_or_else(
848 |e| panic!("error running boot manager service: {e:?}"),
849 ),
850 )
851 .detach();
852 }
853 request => panic!("Unhandled method Paver::{}", request.method_name()),
854 }
855 }
856
857 Ok(())
858 }
859}
860
861#[cfg(test)]
862pub mod tests {
863 use super::*;
864 use assert_matches::assert_matches;
865 use fidl_fuchsia_paver as paver;
866
867 use futures::task::Poll;
868
869 struct MockPaverForTest {
870 pub paver: Arc<MockPaverService>,
871 pub data_sink: paver::DataSinkProxy,
872 pub boot_manager: paver::BootManagerProxy,
873 }
874
875 impl MockPaverForTest {
876 pub fn new<F>(f: F) -> Self
877 where
878 F: FnOnce(MockPaverServiceBuilder) -> MockPaverServiceBuilder,
879 {
880 let paver = f(MockPaverServiceBuilder::new());
881 let paver = Arc::new(paver.build());
882 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<paver::PaverMarker>();
883
884 fasync::Task::spawn(
885 Arc::clone(&paver)
886 .run_paver_service(stream)
887 .unwrap_or_else(|_| panic!("Failed to run paver")),
888 )
889 .detach();
890
891 let (data_sink, server) = fidl::endpoints::create_proxy::<paver::DataSinkMarker>();
892 proxy.find_data_sink(server).expect("Finding data sink");
893 let (boot_manager, server) =
894 fidl::endpoints::create_proxy::<paver::BootManagerMarker>();
895 proxy.find_boot_manager(server).expect("Finding boot manager");
896
897 MockPaverForTest { paver, data_sink, boot_manager }
898 }
899 }
900
901 #[fuchsia::test]
902 pub async fn test_events() -> Result<(), Error> {
903 let paver = MockPaverForTest::new(|p| p);
904 let data = "hello there".as_bytes();
905 let vmo =
906 Vmo::create_with_opts(VmoOptions::RESIZABLE, data.len() as u64).expect("Creating VMO");
907 vmo.write(data, 0).expect("writing to VMO");
908 paver
909 .data_sink
910 .write_asset(
911 paver::Configuration::A,
912 paver::Asset::Kernel,
913 Buffer { vmo, size: data.len() as u64 },
914 )
915 .await
916 .expect("Writing asset");
917
918 let result = paver
919 .boot_manager
920 .query_active_configuration()
921 .await
922 .expect("Querying active configuration")
923 .expect("Querying active configuration (2)");
924 assert_eq!(result, paver::Configuration::A);
925 paver
926 .boot_manager
927 .set_configuration_active(paver::Configuration::B)
928 .await
929 .expect("Setting active configuration");
930
931 assert_eq!(
932 paver.paver.take_events(),
933 vec![
934 PaverEvent::WriteAsset {
935 configuration: paver::Configuration::A,
936 asset: paver::Asset::Kernel,
937 payload: data.to_vec()
938 },
939 PaverEvent::QueryActiveConfiguration,
940 PaverEvent::SetConfigurationActive { configuration: paver::Configuration::B },
941 ]
942 );
943
944 Ok(())
945 }
946
947 #[fuchsia::test]
948 pub async fn test_hook() -> Result<(), Error> {
949 let hook = |_: &PaverEvent| zx::Status::NOT_SUPPORTED;
950 let paver = MockPaverForTest::new(|p| p.insert_hook(hooks::return_error(hook)));
951
952 assert_eq!(
953 Err(zx::Status::NOT_SUPPORTED.into_raw()),
954 paver.boot_manager.query_active_configuration().await?
955 );
956
957 assert_eq!(paver.paver.take_events(), vec![PaverEvent::QueryActiveConfiguration]);
958
959 Ok(())
960 }
961
962 #[fuchsia::test]
963 pub async fn test_config_status_hook() -> Result<(), Error> {
964 let config_status_hook =
965 |_: paver::Configuration| Ok(paver::ConfigurationStatus::Unbootable);
966 let paver =
967 MockPaverForTest::new(|p| p.insert_hook(hooks::config_status(config_status_hook)));
968
969 assert_eq!(
970 Ok(paver::ConfigurationStatus::Unbootable),
971 paver.boot_manager.query_configuration_status(paver::Configuration::A).await?
972 );
973
974 assert_eq!(
975 paver.paver.take_events(),
976 vec![PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A }]
977 );
978
979 Ok(())
980 }
981
982 #[test]
983 pub fn test_throttle_hook() -> Result<(), Error> {
984 let mut executor = fasync::TestExecutor::new();
985
986 let (throttle_hook, throttler) = hooks::throttle();
987 let paver = MockPaverForTest::new(|p| p.insert_hook(throttle_hook));
988
989 let mut fut0 = paver.boot_manager.query_configuration_status(paver::Configuration::A);
991 assert_eq!(executor.run_until_stalled(&mut fut0).map(|fidl| fidl.unwrap()), Poll::Pending);
992 let mut fut1 = paver.data_sink.flush();
993 assert_eq!(executor.run_until_stalled(&mut fut1).map(|fidl| fidl.unwrap()), Poll::Pending);
994
995 let () = throttler.emit_next_paver_event(&PaverEvent::QueryConfigurationStatus {
999 configuration: paver::Configuration::A,
1000 });
1001 assert_eq!(
1002 executor.run_until_stalled(&mut fut0).map(|fidl| fidl.unwrap()),
1003 Poll::Ready(Ok(paver::ConfigurationStatus::Healthy))
1004 );
1005 assert_eq!(executor.run_until_stalled(&mut fut1).map(|fidl| fidl.unwrap()), Poll::Pending);
1006
1007 let () = throttler.emit_next_paver_event(&PaverEvent::DataSinkFlush);
1009 assert_eq!(
1010 executor.run_until_stalled(&mut fut1).map(|fidl| fidl.unwrap()),
1011 Poll::Ready(Status::OK.into_raw())
1012 );
1013
1014 drop(throttler);
1016 executor.run_singlethreaded(async {
1017 assert_eq!(
1018 paver.boot_manager.query_current_configuration().await.unwrap(),
1019 Ok(paver::Configuration::A)
1020 );
1021 assert_matches!(
1022 paver
1023 .data_sink
1024 .read_asset(paver::Configuration::A, paver::Asset::Kernel)
1025 .await
1026 .unwrap(),
1027 Ok(_)
1028 );
1029 });
1030
1031 Ok(())
1032 }
1033
1034 #[fuchsia::test]
1035 pub async fn test_active_config() -> Result<(), Error> {
1036 let paver = MockPaverForTest::new(|p| p.active_config(paver::Configuration::B));
1037 assert_eq!(
1038 Ok(paver::Configuration::B),
1039 paver.boot_manager.query_active_configuration().await?
1040 );
1041 assert_eq!(paver.paver.take_events(), vec![PaverEvent::QueryActiveConfiguration]);
1042 Ok(())
1043 }
1044
1045 #[fuchsia::test]
1046 pub async fn test_active_config_when_recovery() -> Result<(), Error> {
1047 let paver = MockPaverForTest::new(|p| p.active_config(paver::Configuration::Recovery));
1048 assert_eq!(
1049 Err(Status::NOT_SUPPORTED.into_raw()),
1050 paver.boot_manager.query_active_configuration().await?
1051 );
1052 assert_eq!(paver.paver.take_events(), vec![PaverEvent::QueryActiveConfiguration]);
1053 Ok(())
1054 }
1055
1056 #[fuchsia::test]
1057 pub async fn test_boot_manager_epitaph() -> Result<(), Error> {
1058 let paver =
1059 MockPaverForTest::new(|p| p.boot_manager_close_with_epitaph(zx::Status::NOT_SUPPORTED));
1060
1061 let result = paver.boot_manager.query_active_configuration().await;
1062 assert_matches!(
1063 result,
1064 Err(fidl::Error::ClientChannelClosed { epitaph, .. })
1065 if epitaph == zx::Status::NOT_SUPPORTED
1066 );
1067 Ok(())
1068 }
1069
1070 #[fuchsia::test]
1071 pub async fn test_set_config_a_healthy() -> Result<(), Error> {
1072 let paver = MockPaverForTest::new(|p| p);
1073 assert_eq!(
1074 Status::OK.into_raw(),
1075 paver.boot_manager.set_configuration_healthy(paver::Configuration::A).await?
1076 );
1077 assert_eq!(
1078 paver.paver.take_events(),
1079 vec![PaverEvent::SetConfigurationHealthy { configuration: paver::Configuration::A }]
1080 );
1081 Ok(())
1082 }
1083
1084 #[fuchsia::test]
1085 pub async fn test_set_recovery_config_healthy() -> Result<(), Error> {
1086 let paver = MockPaverForTest::new(|p| p);
1087 assert_eq!(
1088 Status::INVALID_ARGS.into_raw(),
1089 paver.boot_manager.set_configuration_healthy(paver::Configuration::Recovery).await?
1090 );
1091 assert_eq!(
1092 paver.paver.take_events(),
1093 vec![PaverEvent::SetConfigurationHealthy {
1094 configuration: paver::Configuration::Recovery
1095 }]
1096 );
1097 Ok(())
1098 }
1099}