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::general_purpose::STANDARD as BASE64_STANDARD;
10use base64::engine::Engine as _;
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::{readdir_recursive, DirentKind};
20use futures::stream::TryStreamExt;
21use serde_json::{from_value, to_value, 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 lazy_static::lazy_static;
126    use maplit::hashmap;
127    use serde_json::json;
128    use std::collections::HashMap;
129
130    lazy_static! {
131        static ref GOLDEN_FILE_DATA: HashMap<&'static str, HashMap<&'static str, &'static str>> = hashmap! {
132            "alpha" => hashmap! {
133                "alpha.file" => "alpha info",
134                "alpha/data" => "alpha data"
135            },
136            "cast" => hashmap! {
137                "txt/info.txt" => "cast info.txt",
138                "more.extra" => "extra cast stuff",
139            },
140            "misc" => hashmap! {
141                "info/misc" => "misc.info",
142                "more.misc" => "more misc stuff"
143            },
144            "playready" => hashmap! {
145                "pr/pr/prinfo.dat" => "playready info",
146                "dat.stuff" => "playready stuff"
147            },
148            "weave" => hashmap! {
149                "weave.file" => "weave info",
150                "weave/data" => "weave data"
151            },
152            "widevine" => hashmap! {
153                "stuff.log" => "widevine stuff",
154                "wv/more_stuff" => "more_stuff from widevine",
155            }
156        };
157    }
158
159    #[fasync::run_singlethreaded(test)]
160    async fn list_files_with_no_message_fails() -> Result<(), Error> {
161        let factory_store_facade = FactoryStoreFacade::new();
162        factory_store_facade.list_files(json!("")).await.unwrap_err();
163        Ok(())
164    }
165
166    #[fasync::run_singlethreaded(test)]
167    async fn list_files_with_unknown_provider_fails() -> Result<(), Error> {
168        let factory_store_facade = FactoryStoreFacade::new();
169        factory_store_facade.list_files(json!({ "provider": "unknown" })).await.unwrap_err();
170        Ok(())
171    }
172
173    #[fasync::run_singlethreaded(test)]
174    async fn list_files() -> Result<(), Error> {
175        let factory_store_facade = FactoryStoreFacade::new();
176
177        for (provider, file_map) in GOLDEN_FILE_DATA.iter() {
178            let file_list_json_value =
179                factory_store_facade.list_files(json!({ "provider": provider })).await?;
180
181            let mut file_list: Vec<String> = from_value(file_list_json_value)?;
182            #[allow(suspicious_double_ref_op)] // TODO(https://fxbug.dev/42176996)
183            let mut expected_file_list: Vec<&str> =
184                file_map.keys().map(|entry| entry.clone()).collect();
185
186            expected_file_list.sort();
187            file_list.sort();
188            assert_eq!(expected_file_list, file_list);
189        }
190
191        Ok(())
192    }
193
194    #[fasync::run_singlethreaded(test)]
195    async fn read_file_with_unknown_provider_fails() -> Result<(), Error> {
196        let factory_store_facade = FactoryStoreFacade::new();
197        factory_store_facade
198            .read_file(json!({ "provider": "unknown", "filename": "missing_file"  }))
199            .await
200            .unwrap_err();
201        Ok(())
202    }
203
204    #[fasync::run_singlethreaded(test)]
205    async fn read_file_with_unknown_file_fails() -> Result<(), Error> {
206        let factory_store_facade = FactoryStoreFacade::new();
207        factory_store_facade
208            .read_file(json!({ "provider": "cast", "filename": "missing_file"  }))
209            .await
210            .unwrap_err();
211        Ok(())
212    }
213
214    #[fasync::run_singlethreaded(test)]
215    async fn read_files() -> Result<(), Error> {
216        let factory_store_facade = FactoryStoreFacade::new();
217
218        for (provider, file_map) in GOLDEN_FILE_DATA.iter() {
219            for (filename, expected_contents) in file_map.iter() {
220                let contents_value = factory_store_facade
221                    .read_file(json!({ "provider": provider, "filename": filename }))
222                    .await?;
223                let contents_base64: String = from_value(contents_value)?;
224                let contents = BASE64_STANDARD.decode(contents_base64.as_bytes())?;
225                assert_eq!(expected_contents.as_bytes(), &contents[..]);
226            }
227        }
228        Ok(())
229    }
230}