io_conformance_util/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![warn(missing_docs)]
6
7//! Crate to provide fidl logging and test setup helpers for conformance tests
8//! for fuchsia.io.
9
10use 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
15/// Test harness helper struct.
16pub mod test_harness;
17
18/// Utility functions for getting combinations of flags.
19pub mod flags;
20
21/// A common name for a file to create in a conformance test.
22pub const TEST_FILE: &str = "testing.txt";
23
24/// A common set of file contents to write into a test file in a conformance test.
25pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
26
27/// A default value for NodeAttributes, with zeros set for all fields.
28pub 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
38/// Wait for [`fio::NodeEvent::OnOpen_`] to be sent via `node_proxy` and returns its [`zx::Status`].
39pub 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
56/// Converts a generic [`fio::NodeProxy`] to either [`fio::FileProxy`] or [`fio::DirectoryProxy`].
57/// **WARNING**: This function does _not_ verify that the conversion is valid.
58pub 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
62/// Helper function to call `get_token` on a directory. Only use this if testing something
63/// other than the `get_token` call directly.
64pub 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
70/// Helper function to read a file and return its contents. Only use this if testing something other
71/// than the read call directly.
72pub 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
78/// Returns the .name field from a given DirectoryEntry, otherwise panics.
79pub 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
90/// Asserts that the given `vmo_rights` align with the `expected_vmo_rights` passed to a
91/// get_backing_memory call. We check that the returned rights align with and do not exceed those
92/// in the given flags, that we have at least basic VMO rights, and that the flags align with the
93/// expected sharing mode.
94pub 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    // Ensure that we have at least some basic rights.
98    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    // Ensure the returned rights match and do not exceed those we requested in `expected_vmo_rights`.
103    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    // Make sure we get SET_PROPERTY if we specified a private copy.
116    if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) {
117        assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
118    }
119}
120
121/// Creates a directory with the given DirectoryEntry, opening the file with the given
122/// file flags, and returning a Buffer object initialized with the given vmo_flags.
123pub 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
140/// Makes a directory with a name and set of entries.
141pub 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
147/// Makes a remote directory with a name, which forwards the requests to the given directory proxy.
148pub 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
159/// Makes a file to be placed in the test directory.
160pub 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
164/// Makes an executable file to be placed in the test directory.
165pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
166    io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
167}
168
169/// Extension trait for [`fio::DirectoryProxy`] to make interactions with the fuchsia.io protocol
170/// less verbose.
171#[async_trait]
172pub trait DirectoryProxyExt {
173    /// Open `path` specified using `flags` and `options`, returning a proxy to the remote resource.
174    ///
175    /// Waits for [`fio::NodeEvent::OnRepresentation`] if [`fio::Flags::FLAG_SEND_REPRESENTATION`]
176    /// is specified, otherwise calls `fuchsia.io/Node.GetAttributes` to verify the result.
177    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    /// Similar to [`DirectoryProxyExt::open_node`], but waits for and returns the
185    /// [`fio::NodeEvent::OnRepresentation`] event sent when opening a resource.
186    ///
187    /// Requires [`fio::Flags::FLAG_SEND_REPRESENTATION`] to be specified in `flags`.
188    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        // We use GetAttributes to test that opening the resource succeeded.
235        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
247/// Wait for and return a [`fio::NodeEvent::OnRepresentation`] event sent via `node_proxy`.
248async fn get_on_representation_event(
249    node_proxy: &fio::NodeProxy,
250) -> Result<fio::Representation, zx::Status> {
251    // Try to extract the expected NodeEvent, but map channel epitaphs to zx::Status.
252    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}