Skip to main content

mock_paver/
lib.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
5#![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    // The paver service requires VMOs to be resizable. Assert that the buffer provided by the
21    // system updater can be resized without error.
22    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/// A Hook gives tests the opportunity to directly respond to a request, mock-style.
138///
139/// If a Hook wants to respond to a request, it should send a response
140/// on the request's Responder, and then return None. If the Hook wants
141/// to pass the request on to the next Hook, it should do so by returning
142/// Some(the_request_it_got).
143///
144/// Unimplemented methods pass on all requests.
145///
146/// Responding to requests with the fidl bindings can be kind of unwieldy, so see the `hooks` module
147/// for tidier syntax in simpler cases.
148#[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    /// A Hook for the specific case where you want to return an error.
166    ///
167    /// If the callback returns ZX_OK, the Hook will pass the request
168    /// off to the next Hook. Responds to both BootManagerRequests and
169    /// DataSinkRequests.
170    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                // Ignore errors from peers closing the channel early
192                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                // Ignore errors from peers closing the channel early
238                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    /// A Hook for responding to `QueryConfigurationStatus` calls.
257    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                    // Ignore errors from peers closing the channel early
282                    let _ = responder.send(result);
283                    None
284                }
285                request => Some(request),
286            }
287        }
288    }
289
290    /// A Hook for responding to `QueryConfigurationStatusAndBootAttempts` calls.
291    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                    // Ignore errors from peers closing the channel early
328                    let _ = responder.send(result);
329                    None
330                }
331                request => Some(request),
332            }
333        }
334    }
335
336    /// A Hook for responding to `WriteFirmware` calls.
337    pub fn write_firmware<F>(callback: F) -> WriteFirmware<F>
338    where
339        F: Fn(
340            paver::Configuration,
341            /* firmware_type */ String,
342            /* payload */ 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                /* firmware_type */ String,
356                /* payload */ 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                    // Ignore errors from peers closing the channel early
373                    let _ = responder.send(&result);
374                    None
375                }
376                request => Some(request),
377            }
378        }
379    }
380
381    /// A Hook for responding to `ReadFirmware` calls.
382    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                    // Ignore errors from peers closing the channel early
406                    let _ = responder.send(result);
407                    None
408                }
409                request => Some(request),
410            }
411        }
412    }
413
414    /// A Hook for responding to `ReadFirmware` calls that sets the size field of the returned
415    /// fuchsia.mem.Buffer independently from the content of the VMO.
416    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                    // Ignore errors from peers closing the channel early
440                    let _ = responder.send(result);
441                    None
442                }
443                request => Some(request),
444            }
445        }
446    }
447
448    /// A Hook for responding to `ReadAsset` calls.
449    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                    // Ignore errors from peers closing the channel early
473                    let _ = responder.send(result);
474                    None
475                }
476                request => Some(request),
477            }
478        }
479    }
480
481    /// A Hook for responding to `ReadAsset` calls that sets the size field of the returned
482    /// fuchsia.mem.Buffer independently from the content of the VMO.
483    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                    // Ignore errors from peers closing the channel early
507                    let _ = responder.send(result);
508                    None
509                }
510                request => Some(request),
511            }
512        }
513    }
514
515    /// A Hook for the specific case where you want to control when each `PaverEvent` is emitted.
516    pub fn throttle() -> (ThrottleHook, Throttle) {
517        let (send, recv) = mpsc::unbounded();
518        (ThrottleHook(AsyncMutex::new(Some(recv))), Throttle(send))
519    }
520
521    /// Wrapper type to control how many `PaverEvent`s are unblocked. Dropping the `Throttle` will
522    /// permanently release all subsequent `PaverEvent`s.
523    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    /// Adds a Hook. Hooks are called in order of insertion.
595    pub fn insert_hook(mut self, hook: impl Hook + Send + 'static) -> Self {
596        self.hooks.push(Box::new(hook));
597        self
598    }
599
600    // Provide a callback which will be called for every paver event.
601    // Useful for logging or interaction assertions.
602    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    /// Spawns a new task to serve the data sink protocol.
652    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    /// Spawns a new task to serve the boot manager protocol.
666    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    /// Spawns a new task to serve the paver protocol.
680    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            // Ignore errors from peers closing the channel early
713            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                    // In normal operation the paver will return a VMO large enough to contain
725                    // whatever image we happen to be looking for (the "images" used in tests are
726                    // small).
727                    responder.send(Ok(write_mem_buffer(vec![0u8; 4096])))
728                }
729                paver::DataSinkRequest::ReadFirmware { responder, .. } => {
730                    // In normal operation the paver will return a VMO large enough to contain
731                    // whatever image we happen to be looking for (the "images" used in tests are
732                    // small).
733                    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            // Ignore errors from peers closing the channel early
764            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                    // TODO(zyecheng): Implement the mock logic for this API and add tests.
775                    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                    // Return an error if the given configuration is `Recovery`.
798                    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        // Both events are blocked.
982        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        // Since we called query_configuration_status first, the boot_manager method has the lock on
988        // the `ThrottleHook`. Therefore, when we unblock the next event, we'll observe that
989        // query_configuration_status is unblocked first.
990        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        // Unblock the remaining event.
1000        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        // Detach the throttler and observe subsequent requests are unblocked.
1007        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}