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