Skip to main content

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 fidl_fuchsia_io as fio;
13use fidl_fuchsia_io_test as io_test;
14use futures::TryStreamExt as _;
15
16/// Test harness helper struct.
17pub mod test_harness;
18
19/// Utility functions for getting combinations of flags.
20pub mod flags;
21
22/// A common name for a file to create in a conformance test.
23pub const TEST_FILE: &str = "testing.txt";
24
25/// A common set of file contents to write into a test file in a conformance test.
26pub const TEST_FILE_CONTENTS: &[u8] = "abcdef".as_bytes();
27
28/// A default value for NodeAttributes, with zeros set for all fields.
29pub const EMPTY_NODE_ATTRS: fio::NodeAttributes = fio::NodeAttributes {
30    mode: 0,
31    id: 0,
32    content_size: 0,
33    storage_size: 0,
34    link_count: 0,
35    creation_time: 0,
36    modification_time: 0,
37};
38
39/// Converts a generic [`fio::NodeProxy`] to either [`fio::FileProxy`] or [`fio::DirectoryProxy`].
40/// **WARNING**: This function does _not_ verify that the conversion is valid.
41pub fn convert_node_proxy<T: Proxy>(proxy: fio::NodeProxy) -> T {
42    T::from_channel(proxy.into_channel().expect("Cannot convert node proxy to channel"))
43}
44
45/// Helper function to call `get_token` on a directory. Only use this if testing something
46/// other than the `get_token` call directly.
47pub async fn get_token(dir: &fio::DirectoryProxy) -> fidl::NullableHandle {
48    let (status, token) = dir.get_token().await.expect("get_token failed");
49    assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
50    token.expect("handle missing")
51}
52
53/// Helper function to read a file and return its contents. Only use this if testing something other
54/// than the read call directly.
55pub async fn read_file(dir: &fio::DirectoryProxy, path: &str) -> Vec<u8> {
56    let file =
57        dir.open_node::<fio::FileMarker>(path, fio::Flags::PERM_READ_BYTES, None).await.unwrap();
58    file.read(100).await.expect("read failed").map_err(zx::Status::from_raw).expect("read error")
59}
60
61/// Returns the .name field from a given DirectoryEntry, otherwise panics.
62pub fn get_directory_entry_name(dir_entry: &io_test::DirectoryEntry) -> String {
63    use io_test::DirectoryEntry;
64    match dir_entry {
65        DirectoryEntry::Directory(entry) => &entry.name,
66        DirectoryEntry::RemoteDirectory(entry) => &entry.name,
67        DirectoryEntry::File(entry) => &entry.name,
68        DirectoryEntry::ExecutableFile(entry) => &entry.name,
69    }
70    .clone()
71}
72
73/// Asserts that the given `vmo_rights` align with the `expected_vmo_rights` passed to a
74/// get_backing_memory call. We check that the returned rights align with and do not exceed those
75/// in the given flags, that we have at least basic VMO rights, and that the flags align with the
76/// expected sharing mode.
77pub fn validate_vmo_rights(vmo: &zx::Vmo, expected_vmo_rights: fio::VmoFlags) {
78    let vmo_rights: zx::Rights = vmo.basic_info().expect("failed to get VMO info").rights;
79
80    // Ensure that we have at least some basic rights.
81    assert!(vmo_rights.contains(zx::Rights::BASIC));
82    assert!(vmo_rights.contains(zx::Rights::MAP));
83    assert!(vmo_rights.contains(zx::Rights::GET_PROPERTY));
84
85    // Ensure the returned rights match and do not exceed those we requested in `expected_vmo_rights`.
86    assert!(
87        vmo_rights.contains(zx::Rights::READ) == expected_vmo_rights.contains(fio::VmoFlags::READ)
88    );
89    assert!(
90        vmo_rights.contains(zx::Rights::WRITE)
91            == expected_vmo_rights.contains(fio::VmoFlags::WRITE)
92    );
93    assert!(
94        vmo_rights.contains(zx::Rights::EXECUTE)
95            == expected_vmo_rights.contains(fio::VmoFlags::EXECUTE)
96    );
97
98    // Make sure we get SET_PROPERTY if we specified a private copy.
99    if expected_vmo_rights.contains(fio::VmoFlags::PRIVATE_CLONE) {
100        assert!(vmo_rights.contains(zx::Rights::SET_PROPERTY));
101    }
102}
103
104/// Creates a directory with the given DirectoryEntry, opening the file with the given
105/// file flags, and returning a Buffer object initialized with the given vmo_flags.
106pub async fn create_file_and_get_backing_memory(
107    dir_entry: io_test::DirectoryEntry,
108    test_harness: &test_harness::TestHarness,
109    file_flags: fio::Flags,
110    vmo_flags: fio::VmoFlags,
111) -> Result<(zx::Vmo, (fio::DirectoryProxy, fio::FileProxy)), zx::Status> {
112    let file_path = get_directory_entry_name(&dir_entry);
113    let dir_proxy = test_harness.get_directory(vec![dir_entry], file_flags);
114    let file_proxy = dir_proxy.open_node::<fio::FileMarker>(&file_path, file_flags, None).await?;
115    let vmo = file_proxy
116        .get_backing_memory(vmo_flags)
117        .await
118        .expect("get_backing_memory failed")
119        .map_err(zx::Status::from_raw)?;
120    Ok((vmo, (dir_proxy, file_proxy)))
121}
122
123/// Makes a directory with a name and set of entries.
124pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
125    let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
126        entries.into_iter().map(|e| Some(Box::new(e))).collect();
127    io_test::DirectoryEntry::Directory(io_test::Directory { name: name.to_string(), entries })
128}
129
130/// Makes a remote directory with a name, which forwards the requests to the given directory proxy.
131pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
132    let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
133        remote_dir.into_channel().unwrap().into_zx_channel(),
134    );
135
136    io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
137        name: name.to_string(),
138        remote_client,
139    })
140}
141
142/// Makes a file to be placed in the test directory.
143pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
144    io_test::DirectoryEntry::File(io_test::File { name: name.to_string(), contents })
145}
146
147/// Makes an executable file to be placed in the test directory.
148pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
149    io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
150}
151
152/// Extension trait for [`fio::DirectoryProxy`] to make interactions with the fuchsia.io protocol
153/// less verbose.
154#[async_trait]
155pub trait DirectoryProxyExt {
156    /// Open `path` specified using `flags` and `options`, returning a proxy to the remote resource.
157    ///
158    /// Waits for [`fio::NodeEvent::OnRepresentation`] if [`fio::Flags::FLAG_SEND_REPRESENTATION`]
159    /// is specified, otherwise calls `fuchsia.io/Node.GetAttributes` to verify the result.
160    async fn open_node<T: ProtocolMarker>(
161        &self,
162        path: &str,
163        flags: fio::Flags,
164        options: Option<fio::Options>,
165    ) -> Result<T::Proxy, zx::Status>;
166
167    /// Similar to [`DirectoryProxyExt::open_node`], but waits for and returns the
168    /// [`fio::NodeEvent::OnRepresentation`] event sent when opening a resource.
169    ///
170    /// Requires [`fio::Flags::FLAG_SEND_REPRESENTATION`] to be specified in `flags`.
171    async fn open_node_repr<T: ProtocolMarker>(
172        &self,
173        path: &str,
174        flags: fio::Flags,
175        options: Option<fio::Options>,
176    ) -> Result<(T::Proxy, fio::Representation), zx::Status>;
177}
178
179#[async_trait]
180impl DirectoryProxyExt for fio::DirectoryProxy {
181    async fn open_node<T: ProtocolMarker>(
182        &self,
183        path: &str,
184        flags: fio::Flags,
185        options: Option<fio::Options>,
186    ) -> Result<T::Proxy, zx::Status> {
187        open_node_impl::<T>(self, path, flags, options).await.map(|(proxy, _representation)| proxy)
188    }
189
190    async fn open_node_repr<T: ProtocolMarker>(
191        &self,
192        path: &str,
193        flags: fio::Flags,
194        options: Option<fio::Options>,
195    ) -> Result<(T::Proxy, fio::Representation), zx::Status> {
196        assert!(
197            flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION),
198            "flags must specify the FLAG_SEND_REPRESENTATION flag to use this function!"
199        );
200        let (proxy, representation) = open_node_impl::<T>(self, path, flags, options).await?;
201        Ok((proxy, representation.unwrap()))
202    }
203}
204
205async fn open_node_impl<T: ProtocolMarker>(
206    dir: &fio::DirectoryProxy,
207    path: &str,
208    flags: fio::Flags,
209    options: Option<fio::Options>,
210) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
211    let (proxy, server) = create_proxy::<fio::NodeMarker>();
212    dir.open(path, flags, &options.unwrap_or_default(), server.into_channel())
213        .expect("Failed to call open3");
214    let representation = if flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION) {
215        Some(get_on_representation_event(&proxy).await?)
216    } else {
217        // We use GetAttributes to test that opening the resource succeeded.
218        let _ = proxy.get_attributes(Default::default()).await.map_err(|e| {
219            if let fidl::Error::ClientChannelClosed { status, .. } = e {
220                status
221            } else {
222                panic!("Unhandled FIDL error: {:?}", e);
223            }
224        })?;
225        None
226    };
227    Ok((convert_node_proxy(proxy), representation))
228}
229
230/// Wait for and return a [`fio::NodeEvent::OnRepresentation`] event sent via `node_proxy`.
231async fn get_on_representation_event(
232    node_proxy: &fio::NodeProxy,
233) -> Result<fio::Representation, zx::Status> {
234    // Try to extract the expected NodeEvent, but map channel epitaphs to zx::Status.
235    let event = Clone::clone(node_proxy)
236        .take_event_stream()
237        .try_next()
238        .await
239        .map_err(|e| {
240            if let fidl::Error::ClientChannelClosed { status, .. } = e {
241                status
242            } else {
243                panic!("Unhandled FIDL error: {:?}", e);
244            }
245        })?
246        .expect("Missing NodeEvent in stream!");
247    let representation = match event {
248        fio::NodeEvent::OnRepresentation { payload } => payload,
249        _ => panic!("Found unexpected NodeEvent type in stream!"),
250    };
251    Ok(representation)
252}