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