use crate::{
MetaContents, MetaContentsError, MetaPackage, MetaPackageError, MetaSubpackages,
MetaSubpackagesError,
};
use fidl::endpoints::ServerEnd;
use fuchsia_hash::{Hash, ParseHashError};
use thiserror::Error;
use version_history::AbiRevision;
use {fidl_fuchsia_io as fio, zx_status};
pub use fuchsia_fs::file::ReadError;
pub use fuchsia_fs::node::{CloneError, CloseError, OpenError};
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum ReadHashError {
#[error("while reading 'meta/'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/'")]
Parse(#[source] ParseHashError),
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadMetaPackageError {
#[error("while reading 'meta/package'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/package'")]
Parse(#[source] MetaPackageError),
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadMetaSubpackagesError {
#[error("while reading '{}'", MetaSubpackages::PATH)]
Read(#[source] ReadError),
#[error("while parsing '{}'", MetaSubpackages::PATH)]
Parse(#[source] MetaSubpackagesError),
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadMetaContentsError {
#[error("while reading 'meta/contents'")]
Read(#[source] ReadError),
#[error("while parsing 'meta/contents'")]
Parse(#[source] MetaContentsError),
}
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum LoadAbiRevisionError {
#[error("while opening '{}'", AbiRevision::PATH)]
Open(#[from] OpenError),
#[error("while reading '{}'", AbiRevision::PATH)]
Read(#[from] ReadError),
#[error("while parsing '{}'", AbiRevision::PATH)]
Parse(#[from] std::array::TryFromSliceError),
}
#[derive(Debug, Clone)]
pub struct PackageDirectory {
proxy: fio::DirectoryProxy,
}
impl PackageDirectory {
pub fn from_proxy(proxy: fio::DirectoryProxy) -> Self {
Self { proxy }
}
pub fn create_request() -> Result<(Self, ServerEnd<fio::DirectoryMarker>), fidl::Error> {
let (proxy, request) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>()?;
Ok((Self::from_proxy(proxy), request))
}
#[cfg(target_os = "fuchsia")]
pub fn open_from_namespace() -> Result<Self, OpenError> {
let dir = fuchsia_fs::directory::open_in_namespace("/pkg", fio::PERM_READABLE)?;
Ok(Self::from_proxy(dir))
}
pub async fn close(self) -> Result<(), CloseError> {
fuchsia_fs::directory::close(self.proxy).await
}
pub fn reopen(&self, dir_request: ServerEnd<fio::DirectoryMarker>) -> Result<(), CloneError> {
fuchsia_fs::directory::clone_onto_no_describe(&self.proxy, None, dir_request)
}
pub fn into_proxy(self) -> fio::DirectoryProxy {
self.proxy
}
async fn read_file_to_string(&self, path: &str) -> Result<String, ReadError> {
fuchsia_fs::directory::read_file_to_string(&self.proxy, path).await
}
pub async fn read_file(&self, path: &str) -> Result<Vec<u8>, ReadError> {
fuchsia_fs::directory::read_file(&self.proxy, path).await
}
pub async fn merkle_root(&self) -> Result<Hash, ReadHashError> {
let merkle = self.read_file_to_string("meta").await.map_err(ReadHashError::Read)?;
merkle.parse().map_err(ReadHashError::Parse)
}
pub async fn meta_contents(&self) -> Result<MetaContents, LoadMetaContentsError> {
let meta_contents =
self.read_file("meta/contents").await.map_err(LoadMetaContentsError::Read)?;
let meta_contents = MetaContents::deserialize(meta_contents.as_slice())
.map_err(LoadMetaContentsError::Parse)?;
Ok(meta_contents)
}
pub async fn meta_package(&self) -> Result<MetaPackage, LoadMetaPackageError> {
let meta_package =
self.read_file("meta/package").await.map_err(LoadMetaPackageError::Read)?;
let meta_package = MetaPackage::deserialize(meta_package.as_slice())
.map_err(LoadMetaPackageError::Parse)?;
Ok(meta_package)
}
pub async fn meta_subpackages(&self) -> Result<MetaSubpackages, LoadMetaSubpackagesError> {
match self.read_file(MetaSubpackages::PATH).await {
Ok(file) => Ok(MetaSubpackages::deserialize(file.as_slice())
.map_err(LoadMetaSubpackagesError::Parse)?),
Err(ReadError::Open(OpenError::OpenError(zx_status::Status::NOT_FOUND))) => {
Ok(MetaSubpackages::default())
}
Err(err) => Err(LoadMetaSubpackagesError::Read(err)),
}
}
pub async fn abi_revision(&self) -> Result<AbiRevision, LoadAbiRevisionError> {
let abi_revision_bytes = self.read_file(AbiRevision::PATH).await?;
Ok(AbiRevision::try_from(abi_revision_bytes.as_slice())?)
}
pub async fn blobs(&self) -> Result<impl Iterator<Item = Hash>, LoadMetaContentsError> {
Ok(self.meta_contents().await?.into_hashes_undeduplicated())
}
}
#[cfg(test)]
#[cfg(target_os = "fuchsia")]
mod tests {
use super::*;
use assert_matches::assert_matches;
use fidl::endpoints::Proxy;
#[fuchsia_async::run_singlethreaded(test)]
async fn open_close() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let () = pkg.close().await.unwrap();
}
#[fuchsia_async::run_singlethreaded(test)]
async fn reopen_is_new_connection() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let (proxy, server_end) = fidl::endpoints::create_proxy().unwrap();
assert_matches!(pkg.reopen(server_end), Ok(()));
assert_matches!(PackageDirectory::from_proxy(proxy).close().await, Ok(()));
pkg.into_proxy().into_channel().expect("no other users of the wrapped channel");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn merkle_root_is_pkg_meta() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let merkle: Hash = std::fs::read_to_string("/pkg/meta").unwrap().parse().unwrap();
assert_eq!(pkg.merkle_root().await.unwrap(), merkle);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn list_blobs() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let blobs = pkg.blobs().await.unwrap().collect::<Vec<_>>();
assert!(!blobs.is_empty());
let duplicate_blob_merkle = fuchsia_merkle::from_slice("Hello World!".as_bytes()).root();
assert_eq!(blobs.iter().filter(|hash| *hash == &duplicate_blob_merkle).count(), 2);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn package_name_is_test_package_name() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
assert_eq!(
pkg.meta_package().await.unwrap().into_path().to_string(),
"fuchsia-pkg-tests/0"
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn missing_subpackages_file_is_empty_subpackages() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
assert_eq!(pkg.meta_subpackages().await.unwrap(), MetaSubpackages::default(),);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn abi_revision_succeeds() {
let pkg = PackageDirectory::open_from_namespace().unwrap();
let _: AbiRevision = pkg.abi_revision().await.unwrap();
}
}