blob: 05167c43f28f29120d22140d895f7e25febe145c [file] [log] [blame]
// 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},
};
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::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::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::root(), MetadataVersion::Number(4)).await;
assert!(result.is_err());
}
}