| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| use { |
| anyhow::{anyhow, Context, Error}, |
| fidl_fuchsia_io as fio, |
| fidl_fuchsia_pkg::LocalMirrorProxy, |
| fidl_fuchsia_pkg_ext::RepositoryUrl, |
| fuchsia_fs::file::AsyncReader, |
| fuchsia_zircon::Status, |
| futures::{future::BoxFuture, prelude::*}, |
| tuf::{ |
| interchange::Json, |
| metadata::{MetadataPath, MetadataVersion, TargetPath}, |
| repository::RepositoryProvider, |
| }, |
| }; |
| |
| pub struct LocalMirrorRepositoryProvider { |
| proxy: LocalMirrorProxy, |
| url: fuchsia_url::RepositoryUrl, |
| } |
| |
| impl LocalMirrorRepositoryProvider { |
| pub fn new(proxy: LocalMirrorProxy, url: fuchsia_url::RepositoryUrl) -> Self { |
| Self { proxy, url } |
| } |
| } |
| |
| fn make_opaque_error(e: Error) -> tuf::Error { |
| tuf::Error::Opaque(format!("{:#}", e)) |
| } |
| |
| impl RepositoryProvider<Json> for LocalMirrorRepositoryProvider { |
| fn fetch_metadata<'a>( |
| &'a self, |
| meta_path: &MetadataPath, |
| version: MetadataVersion, |
| ) -> BoxFuture<'a, tuf::Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> { |
| let path = meta_path.components::<Json>(version).join("/"); |
| async move { |
| let (local, remote) = fidl::endpoints::create_endpoints::<fio::FileMarker>() |
| .context("creating file proxy") |
| .map_err(make_opaque_error)?; |
| self.proxy |
| .get_metadata(&mut RepositoryUrl::from(self.url.clone()).into(), &path, remote) |
| .await |
| .context("sending get_metadata") |
| .map_err(make_opaque_error)? |
| .map_err(|_| tuf::Error::NotFound)?; |
| |
| // Wait for OnOpen so that we know that the open actually succeeded. |
| let file_proxy = |
| local.into_proxy().context("creating FileProxy").map_err(make_opaque_error)?; |
| let mut stream = file_proxy.take_event_stream(); |
| |
| let event = if let Some(event) = stream.next().await { |
| event |
| } else { |
| return Err(tuf::Error::Opaque(format!("Expected OnOpen, but did not get one."))); |
| }; |
| let status = match event { |
| Ok(fio::FileEvent::OnOpen_ { s, .. }) => Status::ok(s), |
| Ok(fio::FileEvent::OnConnectionInfo { .. }) => Ok(()), |
| Err(e) => return Err(make_opaque_error(anyhow!(e).context("waiting for OnOpen"))), |
| }; |
| |
| match status { |
| Ok(()) => {} |
| Err(Status::NOT_FOUND) => return Err(tuf::Error::NotFound), |
| Err(e) => return Err(tuf::Error::Opaque(format!("open failed: {:?}", e))), |
| } |
| |
| // Drop the stream so that AsyncReader has sole ownership of the proxy. |
| std::mem::drop(stream); |
| |
| let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new( |
| AsyncReader::from_proxy(file_proxy) |
| .context("creating AsyncReader for file") |
| .map_err(make_opaque_error)?, |
| ); |
| |
| Ok(reader) |
| } |
| .boxed() |
| } |
| |
| fn fetch_target<'a>( |
| &'a self, |
| _target_path: &TargetPath, |
| ) -> BoxFuture<'a, tuf::Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> { |
| future::ready(Err(tuf::Error::Opaque( |
| "fetch_target is not supported for LocalMirrorRepositoryProvider".to_string(), |
| ))) |
| .boxed() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| fidl_fuchsia_pkg::LocalMirrorMarker, |
| fuchsia_async as fasync, |
| fuchsia_pkg_testing::{FakePkgLocalMirror, PackageBuilder, Repository, RepositoryBuilder}, |
| tuf::metadata::Role, |
| }; |
| |
| struct TestEnv { |
| _repo: Repository, |
| _task: fasync::Task<()>, |
| provider: LocalMirrorRepositoryProvider, |
| } |
| |
| const EMPTY_REPO_PATH: &str = "/pkg/empty-repo"; |
| impl TestEnv { |
| async fn new() -> Self { |
| let pkg = PackageBuilder::new("test") |
| .add_resource_at("file.txt", "hi there".as_bytes()) |
| .build() |
| .await |
| .unwrap(); |
| let repo = RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH) |
| .add_package(pkg) |
| .build() |
| .await |
| .unwrap(); |
| |
| let url: fuchsia_url::RepositoryUrl = "fuchsia-pkg://example.com".parse().unwrap(); |
| let mirror = FakePkgLocalMirror::from_repository_and_url(&repo, &url).await; |
| let (proxy, stream) = |
| fidl::endpoints::create_proxy_and_stream::<LocalMirrorMarker>().unwrap(); |
| |
| let task = fasync::Task::spawn(async move { |
| mirror.handle_request_stream(stream).await.expect("handle_request_stream ok"); |
| }); |
| |
| let provider = LocalMirrorRepositoryProvider::new(proxy, url); |
| |
| TestEnv { _repo: repo, _task: task, provider } |
| } |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_fetch_metadata_latest_succeeds() { |
| let env = TestEnv::new().await; |
| let mut result = env |
| .provider |
| .fetch_metadata(&MetadataPath::from_role(&Role::Root), MetadataVersion::None) |
| .await |
| .expect("fetch_metadata succeeds"); |
| |
| let mut data = Vec::new(); |
| result.read_to_end(&mut data).await.expect("Read succeeds"); |
| assert!(!data.is_empty()); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_fetch_metadata_v1_succeeds() { |
| let env = TestEnv::new().await; |
| let mut result = env |
| .provider |
| .fetch_metadata(&MetadataPath::from_role(&Role::Root), MetadataVersion::Number(1)) |
| .await |
| .expect("fetch_metadata succeeds"); |
| |
| let mut data = Vec::new(); |
| result.read_to_end(&mut data).await.expect("Read succeeds"); |
| assert!(!data.is_empty()); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_fetch_metadata_v4_fails() { |
| let env = TestEnv::new().await; |
| let result = env |
| .provider |
| .fetch_metadata(&MetadataPath::from_role(&Role::Root), MetadataVersion::Number(4)) |
| .await; |
| |
| assert!(result.is_err()); |
| } |
| } |