#![warn(missing_docs)]
use async_trait::async_trait;
use fidl::endpoints::{create_proxy, ClientEnd, ProtocolMarker, Proxy};
use fidl::prelude::*;
use fuchsia_async::{DurationExt, TimeoutExt};
use futures::{StreamExt as _, TryStreamExt as _};
use {fidl_fuchsia_io as fio, fidl_fuchsia_io_test as io_test};
pub mod test_harness;
pub mod flags;
pub const TEST_FILE: &str = "testing.txt";
pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
pub const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes {
mode: 0,
id: 0,
content_size: 0,
storage_size: 0,
link_count: 0,
creation_time: 0,
modification_time: 0,
};
pub async fn get_open_status(node_proxy: &fio::NodeProxy) -> zx::Status {
let mut events = Clone::clone(node_proxy).take_event_stream();
if let Some(result) = events.next().await {
match result.expect("FIDL error") {
fio::NodeEvent::OnOpen_ { s, info: _ } => zx::Status::from_raw(s),
fio::NodeEvent::OnRepresentation { .. } => panic!(
"This function should only be used with fuchsia.io/Directory.Open, *not* Open3!"
),
fio::NodeEvent::_UnknownEvent { .. } => {
panic!("This function should only be used with fuchsia.io/Directory.Open")
}
}
} else {
zx::Status::PEER_CLOSED
}
}
pub async fn assert_on_open_not_received(node_proxy: &fio::NodeProxy) {
let mut events = Clone::clone(node_proxy).take_event_stream();
let event = events
.next()
.on_timeout(zx::MonotonicDuration::from_millis(200).after_now(), || Option::None)
.await;
assert!(event.is_none(), "Unexpected OnOpen event received");
}
pub fn convert_node_proxy<T: Proxy>(proxy: fio::NodeProxy) -> T {
T::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel"))
}
pub async fn open_node<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> T::Proxy {
open_node_status::<T>(dir, flags, path)
.await
.unwrap_or_else(|e| panic!("open_node_status failed for {path} (flags={flags:?}): {e:?}"))
}
pub async fn open_node_status<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> Result<T::Proxy, zx::Status> {
let flags = flags | fio::OpenFlags::DESCRIBE;
let (node_proxy, node_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open(flags, fio::ModeType::empty(), path, node_server).expect("Cannot open node");
let status = get_open_status(&node_proxy).await;
if status != zx::Status::OK {
Err(status)
} else {
Ok(convert_node_proxy(node_proxy))
}
}
pub async fn open_file_with_flags(
parent_dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> fio::FileProxy {
open_node::<fio::FileMarker>(&parent_dir, flags | fio::OpenFlags::NOT_DIRECTORY, path).await
}
pub async fn open_dir_with_flags(
parent_dir: &fio::DirectoryProxy,
flags: fio::OpenFlags,
path: &str,
) -> fio::DirectoryProxy {
open_node::<fio::DirectoryMarker>(&parent_dir, flags | fio::OpenFlags::DIRECTORY, path).await
}
pub async fn open_rw_dir(parent_dir: &fio::DirectoryProxy, path: &str) -> fio::DirectoryProxy {
open_dir_with_flags(
parent_dir,
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
path,
)
.await
}
pub async fn get_token(dir: &fio::DirectoryProxy) -> fidl::Handle {
let (status, token) = dir.get_token().await.expect("get_token failed");
assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
token.expect("handle missing")
}
pub async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> {
let file = open_file_with_flags(dir, fio::OpenFlags::RIGHT_READABLE, path).await;
file.read(100).await.expect("read failed").map_err(zx::Status::from_raw).expect("read error")
}
pub async fn assert_file_not_found(dir: &fio::DirectoryProxy, path: &str) {
let (file_proxy, file_server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open(
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DESCRIBE,
fio::ModeType::empty(),
path,
file_server,
)
.expect("Cannot open file");
assert_eq!(get_open_status(&file_proxy).await, zx::Status::NOT_FOUND);
}
pub fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String {
use io_test::DirectoryEntry;
match dir_entry {
DirectoryEntry::Directory(entry) => &entry.name,
DirectoryEntry::RemoteDirectory(entry) => &entry.name,
DirectoryEntry::File(entry) => &entry.name,
DirectoryEntry::ExecutableFile(entry) => &entry.name,
}
.clone()
}
pub fn validate_vmo_rights(vmo: &zx::Vmo, expected_vmo_rights: fio::VmoFlags) {
let vmo_rights: zx::Rights = vmo.basic_info().expect("failed to get VMO info").rights;
assert!(vmo_rights.contains(zx::Rights::BASIC));
assert!(vmo_rights.contains(zx::Rights::MAP));
assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY));
assert!(
vmo_rights.contains(zx::Rights::READ) == expected_vmo_rights.contains(fio::VmoFlags::READ)
);
assert!(
vmo_rights.contains(zx::Rights::WRITE)
== expected_vmo_rights.contains(fio::VmoFlags::WRITE)
);
assert!(
vmo_rights.contains(zx::Rights::EXECUTE)
== expected_vmo_rights.contains(fio::VmoFlags::EXECUTE)
);
if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) {
assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
}
}
pub async fn create_file_and_get_backing_memory(
dir_entry: io_test::DirectoryEntry,
test_harness: &test_harness::TestHarness,
file_flags: fio::OpenFlags,
vmo_flags: fio::VmoFlags,
) -> Result<(zx::Vmo, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> {
let file_path = get_directory_entry_name(&dir_entry);
let root = root_directory(vec![dir_entry]);
let dir_proxy = test_harness.get_directory(root, file_flags);
let file_proxy = open_node_status::<fio::FileMarker>(
&dir_proxy,
file_flags | fio::OpenFlags::NOT_DIRECTORY,
&file_path,
)
.await?;
let vmo = file_proxy
.get_backing_memory(vmo_flags)
.await
.expect("get_backing_memory failed")
.map_err(zx::Status::from_raw)?;
Ok((vmo, (dir_proxy, file_proxy)))
}
pub fn root_directory(entries: Vec<io_test::DirectoryEntry>) -> io_test::Directory {
let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
entries.into_iter().map(|e| Some(Box::new(e))).collect();
io_test::Directory { name: "/".to_string(), entries }
}
pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
let mut dir = root_directory(entries);
dir.name = name.to_string();
io_test::DirectoryEntry::Directory(dir)
}
pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
remote_dir.into_channel().unwrap().into_zx_channel(),
);
io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
name: name.to_string(),
remote_client,
})
}
pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::File(io_test::File { name: name.to_string(), contents })
}
pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
}
#[async_trait]
pub trait DirectoryProxyExt {
async fn open3_node<T: ProtocolMarker>(
&self,
path: &str,
flags: fio::Flags,
options: Option<fio::Options>,
) -> Result<T::Proxy, zx::Status>;
async fn open3_node_repr<T: ProtocolMarker>(
&self,
path: &str,
flags: fio::Flags,
options: Option<fio::Options>,
) -> Result<(T::Proxy, fio::Representation), zx::Status>;
}
#[async_trait]
impl DirectoryProxyExt for fio::DirectoryProxy {
async fn open3_node<T: ProtocolMarker>(
&self,
path: &str,
flags: fio::Flags,
options: Option<fio::Options>,
) -> Result<T::Proxy, zx::Status> {
open3_node_impl::<T>(self, path, flags, options).await.map(|(proxy, _representation)| proxy)
}
async fn open3_node_repr<T: ProtocolMarker>(
&self,
path: &str,
flags: fio::Flags,
options: Option<fio::Options>,
) -> Result<(T::Proxy, fio::Representation), zx::Status> {
assert!(
flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION),
"flags must specify the FLAG_SEND_REPRESENTATION flag to use this function!"
);
let (proxy, representation) = open3_node_impl::<T>(self, path, flags, options).await?;
Ok((proxy, representation.unwrap()))
}
}
async fn open3_node_impl<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
path: &str,
flags: fio::Flags,
options: Option<fio::Options>,
) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open3(path, flags, &options.unwrap_or_default(), server.into_channel())
.expect("Failed to call open3");
let representation = if flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION) {
Some(get_on_representation_event(&proxy).await?)
} else {
proxy.get_connection_info().await.map_err(|e| {
if let fidl::Error::ClientChannelClosed { status, .. } = e {
status
} else {
panic!("Unhandled FIDL error: {:?}", e);
}
})?;
None
};
Ok((convert_node_proxy(proxy), representation))
}
async fn get_on_representation_event(
node_proxy: &fio::NodeProxy,
) -> Result<fio::Representation, zx::Status> {
let event = Clone::clone(node_proxy)
.take_event_stream()
.try_next()
.await
.map_err(|e| {
if let fidl::Error::ClientChannelClosed { status, .. } = e {
status
} else {
panic!("Unhandled FIDL error: {:?}", e);
}
})?
.expect("Missing NodeEvent in stream!");
let representation = match event {
fio::NodeEvent::OnRepresentation { payload } => payload,
_ => panic!("Found unexpected NodeEvent type in stream!"),
};
Ok(representation)
}