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