builtins/
factory_items.rs

1// Copyright 2022 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
5use anyhow::{anyhow, Error};
6use fidl_fuchsia_boot as fboot;
7use fuchsia_zbi::ZbiType::StorageBootfsFactory;
8use fuchsia_zbi::{ZbiParser, ZbiParserError, ZbiResult};
9use futures::prelude::*;
10use lazy_static::lazy_static;
11use std::collections::HashMap;
12use std::sync::Arc;
13use zx::{self as zx, HandleBased};
14
15lazy_static! {
16    // The default rights for an immutable VMO. For details see
17    // https://fuchsia.dev/fuchsia-src/reference/syscalls/vmo_create#description.
18    static ref IMMUTABLE_VMO_RIGHTS: zx::Rights = zx::Rights::DUPLICATE
19        | zx::Rights::TRANSFER
20        | zx::Rights::READ
21        | zx::Rights::MAP
22        | zx::Rights::GET_PROPERTY;
23}
24
25#[derive(Debug)]
26struct FactoryItem {
27    vmo: zx::Vmo,
28    length: u32,
29}
30
31#[derive(Debug)]
32pub struct FactoryItems {
33    // The key of this HashMap is the "extra" field of the zbi_header_t.
34    items: HashMap<u32, FactoryItem>,
35}
36
37impl FactoryItems {
38    pub fn new(parser: &mut ZbiParser) -> Result<Arc<Self>, Error> {
39        match parser.try_get_item(StorageBootfsFactory.into_raw(), None) {
40            Ok(result) => {
41                parser.release_item(StorageBootfsFactory)?;
42                FactoryItems::from_parsed_zbi(result)
43            }
44            Err(err) if err == ZbiParserError::ItemNotFound { zbi_type: StorageBootfsFactory } => {
45                // It's not an unexpected error to not have any StorageBootfsFactory items in the
46                // ZBI. This service will just return None to any queries.
47                Ok(Arc::new(FactoryItems { items: HashMap::new() }))
48            }
49            Err(err) => {
50                // Any error besides ItemNotFound is unexpected, and fatal.
51                Err(anyhow!(
52                    "Failed to retrieve StorageBootfsFactory item with unexpected error: {}",
53                    err
54                ))
55            }
56        }
57    }
58
59    fn from_parsed_zbi(items: Vec<ZbiResult>) -> Result<Arc<Self>, Error> {
60        let mut parsed_items = HashMap::new();
61        for item in items {
62            // The factory items service uses the ZBI extra field as a lookup key. There can
63            // be many factory items in the ZBI, but each extra field must be unique.
64            if parsed_items.contains_key(&item.extra) {
65                return Err(anyhow!("Duplicate factory item found in ZBI: {}", item.extra));
66            }
67
68            let vmo = zx::Vmo::create(item.bytes.len().try_into()?)?;
69            vmo.write(&item.bytes, 0)?;
70            parsed_items
71                .insert(item.extra, FactoryItem { vmo, length: item.bytes.len().try_into()? });
72        }
73
74        Ok(Arc::new(FactoryItems { items: parsed_items }))
75    }
76
77    pub async fn serve(
78        self: Arc<Self>,
79        mut stream: fboot::FactoryItemsRequestStream,
80    ) -> Result<(), Error> {
81        while let Some(fboot::FactoryItemsRequest::Get { extra, responder }) =
82            stream.try_next().await?
83        {
84            match self.items.get(&extra) {
85                Some(item) => responder
86                    .send(Some(item.vmo.duplicate_handle(*IMMUTABLE_VMO_RIGHTS)?), item.length)?,
87                None => responder.send(None, 0)?,
88            };
89        }
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use fidl::AsHandleRef;
98    use fuchsia_async as fasync;
99
100    fn serve_factory_items(items: Vec<ZbiResult>) -> Result<fboot::FactoryItemsProxy, Error> {
101        let (proxy, stream) =
102            fidl::endpoints::create_proxy_and_stream::<fboot::FactoryItemsMarker>();
103        fasync::Task::local(
104            FactoryItems::from_parsed_zbi(items)?
105                .serve(stream)
106                .unwrap_or_else(|e| panic!("Error while serving factory items service: {}", e)),
107        )
108        .detach();
109        Ok(proxy)
110    }
111
112    #[fuchsia::test]
113    async fn no_factory_items_in_zbi() {
114        let mut parser = ZbiParser::new(zx::Vmo::create(0).expect("Failed to create empty VMO"));
115        let items = FactoryItems::new(&mut parser);
116
117        // It's not an error for there to be no factory items in the ZBI.
118        assert!(items.is_ok());
119        assert_eq!(items.unwrap().items.len(), 0);
120    }
121
122    #[fuchsia::test]
123    async fn duplicate_factory_item() {
124        // Note that two ZbiResults have the same 'extra' value, and this value is what's used
125        // for ths service's key.
126        let mock_results = vec![
127            ZbiResult { bytes: b"abc".to_vec(), extra: 12345 },
128            ZbiResult { bytes: b"def".to_vec(), extra: 12345 },
129            ZbiResult { bytes: b"ghi".to_vec(), extra: 54321 },
130        ];
131
132        let factory_items = serve_factory_items(mock_results);
133        assert!(factory_items.is_err());
134    }
135
136    #[fuchsia::test]
137    async fn no_matching_factory_item() {
138        let mock_results = vec![
139            ZbiResult { bytes: b"abc".to_vec(), extra: 123 },
140            ZbiResult { bytes: b"def".to_vec(), extra: 456 },
141            ZbiResult { bytes: b"ghi".to_vec(), extra: 789 },
142        ];
143
144        let factory_items = serve_factory_items(mock_results).unwrap();
145        let (vmo, length) =
146            factory_items.get(314159).await.expect("Failed to query factory item service");
147
148        assert!(vmo.is_none());
149        assert_eq!(length, 0);
150    }
151
152    #[fuchsia::test]
153    async fn get_factory_items_success() {
154        let mock_results = vec![
155            ZbiResult { bytes: b"abc".to_vec(), extra: 123 },
156            ZbiResult { bytes: b"def".to_vec(), extra: 456 },
157            ZbiResult { bytes: b"ghi".to_vec(), extra: 789 },
158        ];
159
160        let factory_items = serve_factory_items(mock_results).unwrap();
161        let (vmo, length) =
162            factory_items.get(456).await.expect("Failed to query factory item service");
163
164        let mut bytes = [0; b"def".len()];
165        assert_eq!(length, bytes.len() as u32);
166
167        assert!(vmo.is_some());
168        let vmo = vmo.unwrap();
169        vmo.read(&mut bytes, 0).unwrap();
170        assert_eq!(&bytes, b"def");
171
172        let rights = vmo.basic_info().unwrap().rights;
173
174        // VMO has default immutable rights.
175        assert!(rights.contains(zx::Rights::DUPLICATE));
176        assert!(rights.contains(zx::Rights::TRANSFER));
177        assert!(rights.contains(zx::Rights::READ));
178        assert!(rights.contains(zx::Rights::MAP));
179        assert!(rights.contains(zx::Rights::GET_PROPERTY));
180
181        // VMO does not have any mutable rights.
182        assert!(!rights.contains(zx::Rights::WRITE));
183        assert!(!rights.contains(zx::Rights::SET_PROPERTY));
184    }
185}