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 Status::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) -> 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                // Ignore errors from peers closing the channel early
193                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                // Ignore errors from peers closing the channel early
240                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    /// A Hook for responding to `QueryConfigurationStatus` calls.
261    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                    // Ignore errors from peers closing the channel early
286                    let _ = responder.send(result);
287                    None
288                }
289                request => Some(request),
290            }
291        }
292    }
293
294    /// A Hook for responding to `QueryConfigurationStatusAndBootAttempts` calls.
295    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                    // Ignore errors from peers closing the channel early
332                    let _ = responder.send(result);
333                    None
334                }
335                request => Some(request),
336            }
337        }
338    }
339
340    /// A Hook for responding to `WriteFirmware` calls.
341    pub fn write_firmware<F>(callback: F) -> WriteFirmware<F>
342    where
343        F: Fn(
344            paver::Configuration,
345            /* firmware_type */ String,
346            /* payload */ 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                /* firmware_type */ String,
360                /* payload */ 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                    // Ignore errors from peers closing the channel early
377                    let _ = responder.send(&result);
378                    None
379                }
380                request => Some(request),
381            }
382        }
383    }
384
385    /// A Hook for responding to `ReadFirmware` calls.
386    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                    // Ignore errors from peers closing the channel early
410                    let _ = responder.send(result);
411                    None
412                }
413                request => Some(request),
414            }
415        }
416    }
417
418    /// A Hook for responding to `ReadFirmware` calls that sets the size field of the returned
419    /// fuchsia.mem.Buffer independently from the content of the VMO.
420    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                    // Ignore errors from peers closing the channel early
444                    let _ = responder.send(result);
445                    None
446                }
447                request => Some(request),
448            }
449        }
450    }
451
452    /// A Hook for responding to `ReadAsset` calls.
453    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                    // Ignore errors from peers closing the channel early
477                    let _ = responder.send(result);
478                    None
479                }
480                request => Some(request),
481            }
482        }
483    }
484
485    /// A Hook for responding to `ReadAsset` calls that sets the size field of the returned
486    /// fuchsia.mem.Buffer independently from the content of the VMO.
487    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                    // Ignore errors from peers closing the channel early
511                    let _ = responder.send(result);
512                    None
513                }
514                request => Some(request),
515            }
516        }
517    }
518
519    /// A Hook for the specific case where you want to control when each `PaverEvent` is emitted.
520    pub fn throttle() -> (ThrottleHook, Throttle) {
521        let (send, recv) = mpsc::unbounded();
522        (ThrottleHook(AsyncMutex::new(Some(recv))), Throttle(send))
523    }
524
525    /// Wrapper type to control how many `PaverEvent`s are unblocked. Dropping the `Throttle` will
526    /// permanently release all subsequent `PaverEvent`s.
527    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    /// Adds a Hook. Hooks are called in order of insertion.
599    pub fn insert_hook(mut self, hook: impl Hook + Send + 'static) -> Self {
600        self.hooks.push(Box::new(hook));
601        self
602    }
603
604    // Provide a callback which will be called for every paver event.
605    // Useful for logging or interaction assertions.
606    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    /// Spawns a new task to serve the data sink protocol.
656    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    /// Spawns a new task to serve the boot manager protocol.
670    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    /// Spawns a new task to serve the paver protocol.
684    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            // Ignore errors from peers closing the channel early
717            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                    // 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                paver::DataSinkRequest::ReadFirmware { responder, .. } => {
736                    // In normal operation the paver will return a VMO large enough to contain
737                    // whatever image we happen to be looking for (the "images" used in tests are
738                    // small).
739                    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            // Ignore errors from peers closing the channel early
770            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                    // TODO(zyecheng): Implement the mock logic for this API and add tests.
781                    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                    // Return an error if the given configuration is `Recovery`.
804                    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        // Both events are blocked.
990        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        // Since we called query_configuration_status first, the boot_manager method has the lock on
996        // the `ThrottleHook`. Therefore, when we unblock the next event, we'll observe that
997        // query_configuration_status is unblocked first.
998        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        // Unblock the remaining event.
1008        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        // Detach the throttler and observe subsequent requests are unblocked.
1015        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}