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