#![warn(missing_docs)]
use {
async_trait::async_trait,
fidl::{
endpoints::{create_proxy, ClientEnd, ProtocolMarker, Proxy},
prelude::*,
},
fidl_fuchsia_io as fio, fidl_fuchsia_io_test as io_test,
fuchsia_async::{DurationExt, TimeoutExt},
fuchsia_zircon as zx,
futures::{StreamExt as _, TryStreamExt as _},
};
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* Open2!"
),
}
} 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::Duration::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.as_ref(),
DirectoryEntry::RemoteDirectory(entry) => entry.name.as_ref(),
DirectoryEntry::File(entry) => entry.name.as_ref(),
DirectoryEntry::ExecutableFile(entry) => entry.name.as_ref(),
}
.expect("DirectoryEntry name is None!")
.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: None, entries: Some(entries), ..Default::default() }
}
pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
let mut dir = root_directory(entries);
dir.name = Some(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: Some(name.to_string()),
remote_client: Some(remote_client),
..Default::default()
})
}
pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::File(io_test::File {
name: Some(name.to_string()),
contents: Some(contents),
..Default::default()
})
}
pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile {
name: Some(name.to_string()),
..Default::default()
})
}
#[async_trait]
pub trait DirectoryProxyExt {
async fn open2_node<T: ProtocolMarker>(
&self,
path: &str,
node_options: fio::NodeOptions,
) -> Result<T::Proxy, zx::Status>;
async fn open2_node_get_representation<T: ProtocolMarker>(
&self,
path: &str,
node_options: fio::NodeOptions,
) -> Result<(T::Proxy, fio::Representation), zx::Status>;
}
#[async_trait]
impl DirectoryProxyExt for fio::DirectoryProxy {
async fn open2_node<T: ProtocolMarker>(
&self,
path: &str,
node_options: fio::NodeOptions,
) -> Result<T::Proxy, zx::Status> {
Ok(open2_node_impl::<T>(self, path, node_options).await?.0)
}
async fn open2_node_get_representation<T: ProtocolMarker>(
&self,
path: &str,
node_options: fio::NodeOptions,
) -> Result<(T::Proxy, fio::Representation), zx::Status> {
let get_representation = node_options
.flags
.is_some_and(|flags| flags.contains(fio::NodeFlags::GET_REPRESENTATION));
assert!(
get_representation,
"node_options must specify the GET_REPRESENTATION flag to use this function!"
);
let (proxy, on_representation) = open2_node_impl::<T>(self, path, node_options).await?;
Ok((proxy, on_representation.unwrap()))
}
}
async fn open2_node_impl<T: ProtocolMarker>(
dir: &fio::DirectoryProxy,
path: &str,
node_options: fio::NodeOptions,
) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
let get_representation =
node_options.flags.is_some_and(|flags| flags.contains(fio::NodeFlags::GET_REPRESENTATION));
let (proxy, server) = create_proxy::<fio::NodeMarker>().expect("Cannot create proxy");
dir.open2(path, &fio::ConnectionProtocols::Node(node_options), server.into_channel())
.expect("Failed to call open2");
if get_representation {
let representation = Some(get_on_representation_event(&proxy).await?);
return Ok((convert_node_proxy(proxy), representation));
}
proxy.get_connection_info().await.map_err(|e| {
if let fidl::Error::ClientChannelClosed { status, .. } = e {
status
} else {
panic!("Unhandled FIDL error: {:?}", e);
}
})?;
Ok((convert_node_proxy(proxy), None))
}
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)
}