sl4f_lib/factory_store/
facade.rs

1// Copyright 2019 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;
6
7use crate::factory_store::types::{FactoryStoreProvider, ListFilesRequest, ReadFileRequest};
8
9use base64::engine::Engine as _;
10use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
11use fidl::endpoints::create_proxy;
12use fidl_fuchsia_factory::{
13    AlphaFactoryStoreProviderMarker, CastCredentialsFactoryStoreProviderMarker,
14    MiscFactoryStoreProviderMarker, PlayReadyFactoryStoreProviderMarker,
15    WeaveFactoryStoreProviderMarker, WidevineFactoryStoreProviderMarker,
16};
17use fidl_fuchsia_io as fio;
18use fuchsia_component::client::connect_to_protocol;
19use fuchsia_fs::directory::{DirentKind, readdir_recursive};
20use futures::stream::TryStreamExt;
21use serde_json::{Value, from_value, to_value};
22
23/// Facade providing access to FactoryStoreProvider interfaces.
24#[derive(Debug)]
25pub struct FactoryStoreFacade;
26impl FactoryStoreFacade {
27    pub fn new() -> Self {
28        FactoryStoreFacade {}
29    }
30
31    /// Lists the files from a given provider.
32    ///
33    /// # Arguments
34    /// * `args`: A serde_json Value with the following format:
35    /// ```json
36    /// {
37    ///   "provider": string
38    /// }
39    /// ```
40    ///
41    /// The provider string is expected to be a value from
42    /// `types::FactoryStoreProvider`.
43    pub async fn list_files(&self, args: Value) -> Result<Value, Error> {
44        let req: ListFilesRequest = from_value(args)?;
45        let dir_proxy = self.get_directory_for_provider(req.provider)?;
46
47        let mut file_paths = Vec::new();
48        let mut stream = readdir_recursive(&dir_proxy, /*timeout=*/ None);
49        while let Some(entry) = stream.try_next().await? {
50            if entry.kind == DirentKind::File {
51                file_paths.push(entry.name);
52            }
53        }
54
55        Ok(to_value(file_paths)?)
56    }
57
58    /// Reads a file from the given provider.
59    ///
60    /// # Arguments
61    /// * `args`: A serde_json Value with the following format:
62    ///
63    /// ```json
64    /// {
65    ///   "provider": string,
66    ///   "filename": string
67    /// }
68    /// ```
69    ///
70    /// The provider string is expected to match the serialized string of a
71    /// value from `types::FactoryStoreProvider`. The filename string is
72    /// expected to be a relative file path.
73    pub async fn read_file(&self, args: Value) -> Result<Value, Error> {
74        let req: ReadFileRequest = from_value(args)?;
75        let dir_proxy = self.get_directory_for_provider(req.provider)?;
76        let contents = fuchsia_fs::directory::read_file(&dir_proxy, &req.filename).await?;
77        Ok(to_value(BASE64_STANDARD.encode(&contents))?)
78    }
79
80    /// Gets a `DirectoryProxy` that is connected to the given `provider`.
81    ///
82    /// # Arguments
83    /// * `provider`: The factory store provider that the directory connects to.
84    fn get_directory_for_provider(
85        &self,
86        provider: FactoryStoreProvider,
87    ) -> Result<fio::DirectoryProxy, Error> {
88        let (dir_proxy, dir_server_end) = create_proxy::<fio::DirectoryMarker>();
89
90        match provider {
91            FactoryStoreProvider::Alpha => {
92                let alpha_svc = connect_to_protocol::<AlphaFactoryStoreProviderMarker>()?;
93                alpha_svc.get_factory_store(dir_server_end)?;
94            }
95            FactoryStoreProvider::Cast => {
96                let cast_svc = connect_to_protocol::<CastCredentialsFactoryStoreProviderMarker>()?;
97                cast_svc.get_factory_store(dir_server_end)?;
98            }
99            FactoryStoreProvider::Misc => {
100                let misc_svc = connect_to_protocol::<MiscFactoryStoreProviderMarker>()?;
101                misc_svc.get_factory_store(dir_server_end)?;
102            }
103            FactoryStoreProvider::Playready => {
104                let playready_svc = connect_to_protocol::<PlayReadyFactoryStoreProviderMarker>()?;
105                playready_svc.get_factory_store(dir_server_end)?;
106            }
107            FactoryStoreProvider::Weave => {
108                let weave_svc = connect_to_protocol::<WeaveFactoryStoreProviderMarker>()?;
109                weave_svc.get_factory_store(dir_server_end)?;
110            }
111            FactoryStoreProvider::Widevine => {
112                let widevine_svc = connect_to_protocol::<WidevineFactoryStoreProviderMarker>()?;
113                widevine_svc.get_factory_store(dir_server_end)?;
114            }
115        }
116
117        Ok(dir_proxy)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use fuchsia_async as fasync;
125    use maplit::hashmap;
126    use serde_json::json;
127    use std::collections::HashMap;
128    use std::sync::LazyLock;
129
130    static GOLDEN_FILE_DATA: LazyLock<HashMap<&'static str, HashMap<&'static str, &'static str>>> =
131        LazyLock::new(|| {
132            hashmap! {
133                "alpha" => hashmap! {
134                    "alpha.file" => "alpha info",
135                    "alpha/data" => "alpha data"
136                },
137                "cast" => hashmap! {
138                    "txt/info.txt" => "cast info.txt",
139                    "more.extra" => "extra cast stuff",
140                },
141                "misc" => hashmap! {
142                    "info/misc" => "misc.info",
143                    "more.misc" => "more misc stuff"
144                },
145                "playready" => hashmap! {
146                    "pr/pr/prinfo.dat" => "playready info",
147                    "dat.stuff" => "playready stuff"
148                },
149                "weave" => hashmap! {
150                    "weave.file" => "weave info",
151                    "weave/data" => "weave data"
152                },
153                "widevine" => hashmap! {
154                    "stuff.log" => "widevine stuff",
155                    "wv/more_stuff" => "more_stuff from widevine",
156                }
157            }
158        });
159
160    #[fasync::run_singlethreaded(test)]
161    async fn list_files_with_no_message_fails() -> Result<(), Error> {
162        let factory_store_facade = FactoryStoreFacade::new();
163        factory_store_facade.list_files(json!("")).await.unwrap_err();
164        Ok(())
165    }
166
167    #[fasync::run_singlethreaded(test)]
168    async fn list_files_with_unknown_provider_fails() -> Result<(), Error> {
169        let factory_store_facade = FactoryStoreFacade::new();
170        factory_store_facade.list_files(json!({ "provider": "unknown" })).await.unwrap_err();
171        Ok(())
172    }
173
174    #[fasync::run_singlethreaded(test)]
175    async fn list_files() -> Result<(), Error> {
176        let factory_store_facade = FactoryStoreFacade::new();
177
178        for (provider, file_map) in GOLDEN_FILE_DATA.iter() {
179            let file_list_json_value =
180                factory_store_facade.list_files(json!({ "provider": provider })).await?;
181
182            let mut file_list: Vec<String> = from_value(file_list_json_value)?;
183            #[allow(suspicious_double_ref_op)] // TODO(https://fxbug.dev/42176996)
184            let mut expected_file_list: Vec<&str> =
185                file_map.keys().map(|entry| entry.clone()).collect();
186
187            expected_file_list.sort();
188            file_list.sort();
189            assert_eq!(expected_file_list, file_list);
190        }
191
192        Ok(())
193    }
194
195    #[fasync::run_singlethreaded(test)]
196    async fn read_file_with_unknown_provider_fails() -> Result<(), Error> {
197        let factory_store_facade = FactoryStoreFacade::new();
198        factory_store_facade
199            .read_file(json!({ "provider": "unknown", "filename": "missing_file"  }))
200            .await
201            .unwrap_err();
202        Ok(())
203    }
204
205    #[fasync::run_singlethreaded(test)]
206    async fn read_file_with_unknown_file_fails() -> Result<(), Error> {
207        let factory_store_facade = FactoryStoreFacade::new();
208        factory_store_facade
209            .read_file(json!({ "provider": "cast", "filename": "missing_file"  }))
210            .await
211            .unwrap_err();
212        Ok(())
213    }
214
215    #[fasync::run_singlethreaded(test)]
216    async fn read_files() -> Result<(), Error> {
217        let factory_store_facade = FactoryStoreFacade::new();
218
219        for (provider, file_map) in GOLDEN_FILE_DATA.iter() {
220            for (filename, expected_contents) in file_map.iter() {
221                let contents_value = factory_store_facade
222                    .read_file(json!({ "provider": provider, "filename": filename }))
223                    .await?;
224                let contents_base64: String = from_value(contents_value)?;
225                let contents = BASE64_STANDARD.decode(contents_base64.as_bytes())?;
226                assert_eq!(expected_contents.as_bytes(), &contents[..]);
227            }
228        }
229        Ok(())
230    }
231}