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                        .capability(Capability::configuration(
103                            "fuchsia.http-client.TcpReceiveBufferSizeBytes",
104                        ))
105                        .from(Ref::void())
106                        .to(&http_client),
107                )
108                .await
109                .unwrap();
110
111            let pkg_resolver = realm_builder
112                .add_child("pkg-resolver", "#meta/pkg-resolver.cm", ChildOptions::new())
113                .await
114                .unwrap();
115
116            realm_builder
117                .read_only_directory(
118                    "config-data",
119                    vec![&pkg_resolver],
120                    DirectoryContents::new().add_file(
121                        "repositories/test.json",
122                        serde_json::to_string(&repo_config).unwrap(),
123                    ),
124                )
125                .await
126                .unwrap();
127
128            realm_builder
129                .read_only_directory(
130                    "root-ssl-certificates",
131                    vec![&http_client],
132                    DirectoryContents::new().add_file(SSL_CERT_FILE_NAME, cert_bytes),
133                )
134                .await
135                .unwrap();
136
137            realm_builder
138                .add_route(
139                    Route::new()
140                        .capability(Capability::protocol_by_name("fuchsia.net.name.Lookup"))
141                        .capability(Capability::protocol_by_name("fuchsia.posix.socket.Provider"))
142                        .from(Ref::parent())
143                        .to(&http_client),
144                )
145                .await
146                .unwrap();
147
148            realm_builder
149                .add_route(
150                    Route::new()
151                        .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
152                        .from(Ref::parent())
153                        .to(&pkg_resolver)
154                        .to(&http_client),
155                )
156                .await
157                .unwrap();
158
159            realm_builder
160                .add_route(
161                    Route::new()
162                        .capability(Capability::protocol_by_name("fuchsia.pkg.PackageResolver-ota"))
163                        .from(&pkg_resolver)
164                        .to(Ref::parent()),
165                )
166                .await
167                .unwrap();
168
169            realm_builder
170                .add_route(
171                    Route::new()
172                        .capability(Capability::protocol_by_name("fuchsia.pkg.http.Client"))
173                        .capability(Capability::protocol_by_name("fuchsia.net.http.Loader"))
174                        .from(&http_client)
175                        .to(&pkg_resolver),
176                )
177                .await
178                .unwrap();
179
180            realm_builder
181                .add_route(
182                    Route::new()
183                        .capability(Capability::protocol_by_name("fuchsia.pkg.PackageCache"))
184                        .capability(Capability::protocol_by_name(
185                            "fuchsia.pkg.garbagecollector.Manager",
186                        ))
187                        .from(&cache_ref)
188                        .to(&pkg_resolver),
189                )
190                .await
191                .unwrap();
192
193            realm_builder
194                .add_route(
195                    Route::new()
196                        .capability(
197                            Capability::protocol::<fmetrics::MetricEventLoggerFactoryMarker>(),
198                        )
199                        .from(&service_reflector)
200                        .to(&pkg_resolver),
201                )
202                .await
203                .unwrap();
204
205            realm_builder
206                .add_route(
207                    Route::new()
208                        .capability(Capability::configuration(
209                            "fuchsia.pkgresolver.TufNetworkHeaderTimeoutSeconds",
210                        ))
211                        .capability(Capability::configuration(
212                            "fuchsia.pkgresolver.BlobNetworkHeaderTimeoutSeconds",
213                        ))
214                        .capability(Capability::configuration(
215                            "fuchsia.pkgresolver.BlobNetworkBodyTimeoutSeconds",
216                        ))
217                        .from(Ref::void())
218                        .to(&pkg_resolver),
219                )
220                .await
221                .unwrap();
222
223            Ok(ResolverRealm { resolver: pkg_resolver, cache: cache_ref })
224        }
225
226        pub async fn new(
227            realm_instance: &RealmInstance,
228            blobfs: BlobfsRamdisk,
229            served_repo: Arc<ServedRepository>,
230        ) -> Result<Self, Error> {
231            let cache = CacheForTest::new(realm_instance, blobfs).await.unwrap();
232
233            let resolver_proxy = realm_instance
234                .root
235                .connect_to_named_protocol_at_exposed_dir::<fpkg::PackageResolverMarker>(
236                    "fuchsia.pkg.PackageResolver-ota",
237                )
238                .expect("connect to pkg resolver");
239
240            let resolver = Resolver::new_with_proxy(resolver_proxy).unwrap();
241
242            let cache_proxy = cache.cache.package_cache_proxy().unwrap();
243
244            assert_eq!(cache_proxy.sync().await.unwrap(), Ok(()));
245
246            Ok(ResolverForTest { cache, resolver: Arc::new(resolver), _served_repo: served_repo })
247        }
248
249        /// Resolve a package using the resolver, returning the root directory of the package,
250        /// and the context for resolving relative package URLs.
251        pub async fn resolve_package(
252            &self,
253            url: &str,
254        ) -> Result<(fio::DirectoryProxy, pkg::ResolutionContext), Error> {
255            let proxy = &self.resolver.proxy;
256            let (package, package_remote) = fidl::endpoints::create_proxy();
257            let resolved_context = proxy
258                .resolve(url, package_remote)
259                .await
260                .unwrap()
261                .map_err(|e| anyhow!("Package resolver error: {:?}", e))?;
262            Ok((package, (&resolved_context).try_into().expect("resolver returns valid context")))
263        }
264    }
265}
266
267#[cfg(test)]
268pub mod tests {
269    use super::for_tests::{EMPTY_REPO_PATH, ResolverForTest};
270    use anyhow::{Context, Error};
271    use fuchsia_component_test::RealmBuilder;
272    use fuchsia_pkg_testing::{PackageBuilder, RepositoryBuilder};
273    use std::sync::Arc;
274
275    const TEST_REPO_URL: &str = "fuchsia-pkg://test";
276
277    #[fuchsia::test]
278    pub async fn test_resolver() -> Result<(), Error> {
279        let name = "test-resolver";
280        let package = PackageBuilder::new(name)
281            .add_resource_at("data/file1", "hello".as_bytes())
282            .add_resource_at("data/file2", "hello two".as_bytes())
283            .build()
284            .await
285            .unwrap();
286        let repo = Arc::new(
287            RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
288                .add_package(&package)
289                .build()
290                .await
291                .context("Building repo")
292                .unwrap(),
293        );
294
295        let realm_builder = RealmBuilder::new().await.unwrap();
296        let served_repo = Arc::new(Arc::clone(&repo).server().start().unwrap());
297        let blobfs =
298            blobfs_ramdisk::BlobfsRamdisk::start().await.context("starting blobfs").unwrap();
299        let _resolver_realm = ResolverForTest::realm_setup(
300            &realm_builder,
301            Arc::clone(&served_repo),
302            TEST_REPO_URL.parse().unwrap(),
303            &blobfs,
304        )
305        .await
306        .unwrap();
307
308        let realm_instance = realm_builder.build().await.unwrap();
309
310        let resolver = ResolverForTest::new(&realm_instance, blobfs, served_repo)
311            .await
312            .context("launching resolver")?;
313        let (root_dir, _resolved_context) =
314            resolver.resolve_package(&format!("{TEST_REPO_URL}/{name}")).await.unwrap();
315
316        package.verify_contents(&root_dir).await.unwrap();
317        Ok(())
318    }
319}