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