Skip to main content

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