1#![warn(missing_docs)]
6
7use async_trait::async_trait;
11use fidl::endpoints::{ClientEnd, ProtocolMarker, Proxy, create_proxy};
12use futures::{StreamExt as _, TryStreamExt as _};
13use {fidl_fuchsia_io as fio, fidl_fuchsia_io_test as io_test};
14
15pub mod test_harness;
17
18pub mod flags;
20
21pub const TEST_FILE: &str = "testing.txt";
23
24pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
26
27pub const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes {
29 mode: 0,
30 id: 0,
31 content_size: 0,
32 storage_size: 0,
33 link_count: 0,
34 creation_time: 0,
35 modification_time: 0,
36};
37
38pub async fn get_open_status(node_proxy: &fio::NodeProxy) -> zx::Status {
40 let mut events = Clone::clone(node_proxy).take_event_stream();
41 if let Some(result) = events.next().await {
42 match result.expect("FIDL error") {
43 fio::NodeEvent::OnOpen_ { s, info: _ } => zx::Status::from_raw(s),
44 fio::NodeEvent::OnRepresentation { .. } => panic!(
45 "This function should only be used with fuchsia.io/Directory.Open, *not* Open3!"
46 ),
47 fio::NodeEvent::_UnknownEvent { .. } => {
48 panic!("This function should only be used with fuchsia.io/Directory.Open")
49 }
50 }
51 } else {
52 zx::Status::PEER_CLOSED
53 }
54}
55
56pub fn convert_node_proxy<T: Proxy>(proxy: fio::NodeProxy) -> T {
59 T::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel"))
60}
61
62pub async fn get_token(dir: &fio::DirectoryProxy) -> fidl::NullableHandle {
65 let (status, token) = dir.get_token().await.expect("get_token failed");
66 assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
67 token.expect("handle missing")
68}
69
70pub async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> {
73 let file =
74 dir.open_node::<fio::FileMarker>(path, fio::Flags::PERM_READ_BYTES, None).await.unwrap();
75 file.read(100).await.expect("read failed").map_err(zx::Status::from_raw).expect("read error")
76}
77
78pub fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String {
80 use io_test::DirectoryEntry;
81 match dir_entry {
82 DirectoryEntry::Directory(entry) => &entry.name,
83 DirectoryEntry::RemoteDirectory(entry) => &entry.name,
84 DirectoryEntry::File(entry) => &entry.name,
85 DirectoryEntry::ExecutableFile(entry) => &entry.name,
86 }
87 .clone()
88}
89
90pub fn validate_vmo_rights(vmo: &zx::Vmo, expected_vmo_rights: fio::VmoFlags) {
95 let vmo_rights: zx::Rights = vmo.basic_info().expect("failed to get VMO info").rights;
96
97 assert!(vmo_rights.contains(zx::Rights::BASIC));
99 assert!(vmo_rights.contains(zx::Rights::MAP));
100 assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY));
101
102 assert!(
104 vmo_rights.contains(zx::Rights::READ) == expected_vmo_rights.contains(fio::VmoFlags::READ)
105 );
106 assert!(
107 vmo_rights.contains(zx::Rights::WRITE)
108 == expected_vmo_rights.contains(fio::VmoFlags::WRITE)
109 );
110 assert!(
111 vmo_rights.contains(zx::Rights::EXECUTE)
112 == expected_vmo_rights.contains(fio::VmoFlags::EXECUTE)
113 );
114
115 if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) {
117 assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
118 }
119}
120
121pub async fn create_file_and_get_backing_memory(
124 dir_entry: io_test::DirectoryEntry,
125 test_harness: &test_harness::TestHarness,
126 file_flags: fio::Flags,
127 vmo_flags: fio::VmoFlags,
128) -> Result<(zx::Vmo, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> {
129 let file_path = get_directory_entry_name(&dir_entry);
130 let dir_proxy = test_harness.get_directory(vec![dir_entry], file_flags);
131 let file_proxy = dir_proxy.open_node::<fio::FileMarker>(&file_path, file_flags, None).await?;
132 let vmo = file_proxy
133 .get_backing_memory(vmo_flags)
134 .await
135 .expect("get_backing_memory failed")
136 .map_err(zx::Status::from_raw)?;
137 Ok((vmo, (dir_proxy, file_proxy)))
138}
139
140pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
142 let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
143 entries.into_iter().map(|e| Some(Box::new(e))).collect();
144 io_test::DirectoryEntry::Directory(io_test::Directory { name: name.to_string(), entries })
145}
146
147pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
149 let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
150 remote_dir.into_channel().unwrap().into_zx_channel(),
151 );
152
153 io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
154 name: name.to_string(),
155 remote_client,
156 })
157}
158
159pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
161 io_test::DirectoryEntry::File(io_test::File { name: name.to_string(), contents })
162}
163
164pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
166 io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
167}
168
169#[async_trait]
172pub trait DirectoryProxyExt {
173 async fn open_node<T: ProtocolMarker>(
178 &self,
179 path: &str,
180 flags: fio::Flags,
181 options: Option<fio::Options>,
182 ) -> Result<T::Proxy, zx::Status>;
183
184 async fn open_node_repr<T: ProtocolMarker>(
189 &self,
190 path: &str,
191 flags: fio::Flags,
192 options: Option<fio::Options>,
193 ) -> Result<(T::Proxy, fio::Representation), zx::Status>;
194}
195
196#[async_trait]
197impl DirectoryProxyExt for fio::DirectoryProxy {
198 async fn open_node<T: ProtocolMarker>(
199 &self,
200 path: &str,
201 flags: fio::Flags,
202 options: Option<fio::Options>,
203 ) -> Result<T::Proxy, zx::Status> {
204 open_node_impl::<T>(self, path, flags, options).await.map(|(proxy, _representation)| proxy)
205 }
206
207 async fn open_node_repr<T: ProtocolMarker>(
208 &self,
209 path: &str,
210 flags: fio::Flags,
211 options: Option<fio::Options>,
212 ) -> Result<(T::Proxy, fio::Representation), zx::Status> {
213 assert!(
214 flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION),
215 "flags must specify the FLAG_SEND_REPRESENTATION flag to use this function!"
216 );
217 let (proxy, representation) = open_node_impl::<T>(self, path, flags, options).await?;
218 Ok((proxy, representation.unwrap()))
219 }
220}
221
222async fn open_node_impl<T: ProtocolMarker>(
223 dir: &fio::DirectoryProxy,
224 path: &str,
225 flags: fio::Flags,
226 options: Option<fio::Options>,
227) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
228 let (proxy, server) = create_proxy::<fio::NodeMarker>();
229 dir.open(path, flags, &options.unwrap_or_default(), server.into_channel())
230 .expect("Failed to call open3");
231 let representation = if flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION) {
232 Some(get_on_representation_event(&proxy).await?)
233 } else {
234 let _ = proxy.get_attributes(Default::default()).await.map_err(|e| {
236 if let fidl::Error::ClientChannelClosed { status, .. } = e {
237 status
238 } else {
239 panic!("Unhandled FIDL error: {:?}", e);
240 }
241 })?;
242 None
243 };
244 Ok((convert_node_proxy(proxy), representation))
245}
246
247async fn get_on_representation_event(
249 node_proxy: &fio::NodeProxy,
250) -> Result<fio::Representation, zx::Status> {
251 let event = Clone::clone(node_proxy)
253 .take_event_stream()
254 .try_next()
255 .await
256 .map_err(|e| {
257 if let fidl::Error::ClientChannelClosed { status, .. } = e {
258 status
259 } else {
260 panic!("Unhandled FIDL error: {:?}", e);
261 }
262 })?
263 .expect("Missing NodeEvent in stream!");
264 let representation = match event {
265 fio::NodeEvent::OnRepresentation { payload } => payload,
266 _ => panic!("Found unexpected NodeEvent type in stream!"),
267 };
268 Ok(representation)
269}