Skip to main content

isolated_swd/
resolver.rs

1// Copyright 2020 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
5#[cfg(test)]
6pub(crate) mod for_tests {
7    use crate::cache::for_tests::CacheForTest;
8    use anyhow::{Error, anyhow};
9    use blobfs_ramdisk::BlobfsRamdisk;
10    use fidl_fuchsia_io as fio;
11    use fidl_fuchsia_metrics as fmetrics;
12    use fidl_fuchsia_pkg as fpkg;
13    use fidl_fuchsia_pkg_ext as pkg;
14    use fidl_fuchsia_pkg_ext::RepositoryConfigs;
15    use fuchsia_async as fasync;
16    use fuchsia_component_test::{
17        Capability, ChildOptions, ChildRef, DirectoryContents, RealmBuilder, RealmInstance, Ref,
18        Route,
19    };
20    use fuchsia_pkg_testing::serve::ServedRepository;
21    use fuchsia_url::RepositoryUrl;
22    use futures::{FutureExt as _, StreamExt as _};
23    use std::sync::Arc;
24
25    const SSL_TEST_CERTS_PATH: &str = "/pkg/data/ssl/cert.pem";
26    const SSL_CERT_FILE_NAME: &str = "cert.pem";
27    pub const EMPTY_REPO_PATH: &str = "/pkg/empty-repo";
28
29    /// Represents the sandboxed package resolver.
30    pub struct Resolver {
31        proxy: fpkg::PackageResolverProxy,
32    }
33
34    impl Resolver {
35        fn new_with_proxy(pkg_resolver_proxy: fpkg::PackageResolverProxy) -> Result<Self, Error> {
36            Ok(Self { proxy: pkg_resolver_proxy })
37        }
38    }
39
40    /// This wraps the `Resolver` in order to reduce test boilerplate.
41    pub struct ResolverForTest {
42        pub cache: CacheForTest,
43        pub resolver: Arc<Resolver>,
44        _served_repo: Arc<ServedRepository>,
45    }
46
47    pub struct ResolverRealm {
48        pub resolver: ChildRef,
49        pub cache: ChildRef,
50    }
51
52    impl ResolverForTest {
53        pub async fn realm_setup(
54            realm_builder: &RealmBuilder,
55            served_repo: Arc<ServedRepository>,
56            repo_url: RepositoryUrl,
57            blobfs: &BlobfsRamdisk,
58        ) -> Result<ResolverRealm, Error> {
59            let cache_ref =
60                CacheForTest::realm_setup(realm_builder, blobfs).await.expect("setting up cache");
61
62            let repo_config =
63                RepositoryConfigs::Version1(vec![served_repo.make_repo_config(repo_url)]);
64
65            let cert_bytes = std::fs::read(std::path::Path::new(SSL_TEST_CERTS_PATH)).unwrap();
66
67            let service_reflector = realm_builder
68                .add_local_child(
69                    "pkg_resolver_service_reflector",
70                    move |handles| {
71                        let mut fs = fuchsia_component::server::ServiceFs::new();
72                        // Not necessary for updates, but prevents spam of irrelevant error logs.
73                        fs.dir("svc").add_fidl_service(move |stream| {
74                            fasync::Task::spawn(
75                                Arc::new(mock_metrics::MockMetricEventLoggerFactory::new())
76                                    .run_logger_factory(stream),
77                            )
78                            .detach()
79                        });
80                        async move {
81                            fs.serve_connection(handles.outgoing_dir).unwrap();
82                            let () = fs.collect().await;
83                            Ok(())
84                        }
85                        .boxed()
86                    },
87                    ChildOptions::new(),
88                )
89                .await
90                .unwrap();
91
92            let http_client = realm_builder
93                .add_child("http-client", "#meta/http-client.cm", ChildOptions::new())
94                .await
95                .unwrap();
96            realm_builder
97                .add_route(
98                    Route::new()
99                        .capability(Capability::configuration(
100                            "fuchsia.http-client.StopOnIdleTimeoutMillis",
101                        ))
102                        .from(Ref::void())
103                        .to(&http_client),
104                )
105                .await
106                .unwrap();
107
108            let pkg_resolver = realm_builder
109                .add_child("pkg-resolver", "#meta/pkg-resolver.cm", ChildOptions::new())
110                .await
111                .unwrap();
112
113            realm_builder
114                .read_only_directory(
115                    "config-data",
116                    vec![&pkg_resolver],
117                    DirectoryContents::new().add_file(
118                        "repositories/test.json",
119                        serde_json::to_string(&repo_config).unwrap(),
120                    ),
121                )
122                .await
123                .unwrap();
124
125            realm_builder
126                .read_only_directory(
127                    "root-ssl-certificates",
128                    vec![&http_client],
129                    DirectoryContents::new().add_file(SSL_CERT_FILE_NAME, cert_bytes),
130                )
131                .await
132                .unwrap();
133
134            realm_builder
135                .add_route(
136                    Route::new()
137                        .capability(Capability::protocol_by_name("fuchsia.net.name.Lookup"))
138                        .capability(Capability::protocol_by_name("fuchsia.posix.socket.Provider"))
139                        .from(Ref::parent())
140                        .to(&http_client),
141                )
142                .await
143                .unwrap();
144
145            realm_builder
146                .add_route(
147                    Route::new()
148                        .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
149                        .from(Ref::parent())
150                        .to(&pkg_resolver)
151                        .to(&http_client),
152                )
153                .await
154                .unwrap();
155
156            realm_builder
157                .add_route(
158                    Route::new()
159                        .capability(Capability::protocol_by_name("fuchsia.pkg.PackageResolver-ota"))
160                        .from(&pkg_resolver)
161                        .to(Ref::parent()),
162                )
163                .await
164                .unwrap();
165
166            realm_builder
167                .add_route(
168                    Route::new()
169                        .capability(Capability::protocol_by_name("fuchsia.pkg.http.Client"))
170                        .capability(Capability::protocol_by_name("fuchsia.net.http.Loader"))
171                        .from(&http_client)
172                        .to(&pkg_resolver),
173                )
174                .await
175                .unwrap();
176
177            realm_builder
178                .add_route(
179                    Route::new()
180                        .capability(Capability::protocol_by_name("fuchsia.pkg.PackageCache"))
181                        .capability(Capability::protocol_by_name(
182                            "fuchsia.pkg.garbagecollector.Manager",
183                        ))
184                        .from(&cache_ref)
185                        .to(&pkg_resolver),
186                )
187                .await
188                .unwrap();
189
190            realm_builder
191                .add_route(
192                    Route::new()
193                        .capability(
194                            Capability::protocol::<fmetrics::MetricEventLoggerFactoryMarker>(),
195                        )
196                        .from(&service_reflector)
197                        .to(&pkg_resolver),
198                )
199                .await
200                .unwrap();
201
202            realm_builder
203                .add_route(
204                    Route::new()
205                        .capability(Capability::configuration(
206                            "fuchsia.pkgresolver.TufNetworkHeaderTimeoutSeconds",
207                        ))
208                        .capability(Capability::configuration(
209                            "fuchsia.pkgresolver.BlobNetworkHeaderTimeoutSeconds",
210                        ))
211                        .capability(Capability::configuration(
212                            "fuchsia.pkgresolver.BlobNetworkBodyTimeoutSeconds",
213                        ))
214                        .from(Ref::void())
215                        .to(&pkg_resolver),
216                )
217                .await
218                .unwrap();
219
220            Ok(ResolverRealm { resolver: pkg_resolver, cache: cache_ref })
221        }
222
223        pub async fn new(
224            realm_instance: &RealmInstance,
225            blobfs: BlobfsRamdisk,
226            served_repo: Arc<ServedRepository>,
227        ) -> Result<Self, Error> {
228            let cache = CacheForTest::new(realm_instance, blobfs).await.unwrap();
229
230            let resolver_proxy = realm_instance
231                .root
232                .connect_to_named_protocol_at_exposed_dir::<fpkg::PackageResolverMarker>(
233                    "fuchsia.pkg.PackageResolver-ota",
234                )
235                .expect("connect to pkg resolver");
236
237            let resolver = Resolver::new_with_proxy(resolver_proxy).unwrap();
238
239            let cache_proxy = cache.cache.package_cache_proxy().unwrap();
240
241            assert_eq!(cache_proxy.sync().await.unwrap(), Ok(()));
242
243            Ok(ResolverForTest { cache, resolver: Arc::new(resolver), _served_repo: served_repo })
244        }
245
246        /// Resolve a package using the resolver, returning the root directory of the package,
247        /// and the context for resolving relative package URLs.
248        pub async fn resolve_package(
249            &self,
250            url: &str,
251        ) -> Result<(fio::DirectoryProxy, pkg::ResolutionContext), Error> {
252            let proxy = &self.resolver.proxy;
253            let (package, package_remote) = fidl::endpoints::create_proxy();
254            let resolved_context = proxy
255                .resolve(url, package_remote)
256                .await
257                .unwrap()
258                .map_err(|e| anyhow!("Package resolver error: {:?}", e))?;
259            Ok((package, (&resolved_context).try_into().expect("resolver returns valid context")))
260        }
261    }
262}
263
264#[cfg(test)]
265pub mod tests {
266    use super::for_tests::{EMPTY_REPO_PATH, ResolverForTest};
267    use anyhow::{Context, Error};
268    use fuchsia_async as fasync;
269    use fuchsia_component_test::RealmBuilder;
270    use fuchsia_pkg_testing::{PackageBuilder, RepositoryBuilder};
271    use std::sync::Arc;
272
273    const TEST_REPO_URL: &str = "fuchsia-pkg://test";
274
275    #[fasync::run_singlethreaded(test)]
276    pub async fn test_resolver() -> Result<(), Error> {
277        let name = "test-resolver";
278        let package = PackageBuilder::new(name)
279            .add_resource_at("data/file1", "hello".as_bytes())
280            .add_resource_at("data/file2", "hello two".as_bytes())
281            .build()
282            .await
283            .unwrap();
284        let repo = Arc::new(
285            RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
286                .add_package(&package)
287                .build()
288                .await
289                .context("Building repo")
290                .unwrap(),
291        );
292
293        let realm_builder = RealmBuilder::new().await.unwrap();
294        let served_repo = Arc::new(Arc::clone(&repo).server().start().unwrap());
295        let blobfs =
296            blobfs_ramdisk::BlobfsRamdisk::start().await.context("starting blobfs").unwrap();
297        let _resolver_realm = ResolverForTest::realm_setup(
298            &realm_builder,
299            Arc::clone(&served_repo),
300            TEST_REPO_URL.parse().unwrap(),
301            &blobfs,
302        )
303        .await
304        .unwrap();
305
306        let realm_instance = realm_builder.build().await.unwrap();
307
308        let resolver = ResolverForTest::new(&realm_instance, blobfs, served_repo)
309            .await
310            .context("launching resolver")?;
311        let (root_dir, _resolved_context) =
312            resolver.resolve_package(&format!("{TEST_REPO_URL}/{name}")).await.unwrap();
313
314        package.verify_contents(&root_dir).await.unwrap();
315        Ok(())
316    }
317}