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 =
114        test_harness.get_directory(vec![dir_entry], test_harness.dir_rights.all_flags());
115    let file_proxy = dir_proxy.open_node::<fio::FileMarker>(&file_path, file_flags, None).await?;
116    let vmo = file_proxy
117        .get_backing_memory(vmo_flags)
118        .await
119        .expect("get_backing_memory failed")
120        .map_err(zx::Status::from_raw)?;
121    Ok((vmo, (dir_proxy, file_proxy)))
122}
123
124/// Makes a directory with a name and set of entries.
125pub fn directory(name: &str, entries: Vec<io_test::DirectoryEntry>) -> io_test::DirectoryEntry {
126    let entries: Vec<Option<Box<io_test::DirectoryEntry>>> =
127        entries.into_iter().map(|e| Some(Box::new(e))).collect();
128    io_test::DirectoryEntry::Directory(io_test::Directory { name: name.to_string(), entries })
129}
130
131/// Makes a remote directory with a name, which forwards the requests to the given directory proxy.
132pub fn remote_directory(name: &str, remote_dir: fio::DirectoryProxy) -> io_test::DirectoryEntry {
133    let remote_client = ClientEnd::<fio::DirectoryMarker>::new(
134        remote_dir.into_channel().unwrap().into_zx_channel(),
135    );
136
137    io_test::DirectoryEntry::RemoteDirectory(io_test::RemoteDirectory {
138        name: name.to_string(),
139        remote_client,
140    })
141}
142
143/// Makes a file to be placed in the test directory.
144pub fn file(name: &str, contents: Vec<u8>) -> io_test::DirectoryEntry {
145    io_test::DirectoryEntry::File(io_test::File { name: name.to_string(), contents })
146}
147
148/// Makes an executable file to be placed in the test directory.
149pub fn executable_file(name: &str) -> io_test::DirectoryEntry {
150    io_test::DirectoryEntry::ExecutableFile(io_test::ExecutableFile { name: name.to_string() })
151}
152
153/// Extension trait for [`fio::DirectoryProxy`] to make interactions with the fuchsia.io protocol
154/// less verbose.
155#[async_trait]
156pub trait DirectoryProxyExt {
157    /// Open `path` specified using `flags` and `options`, returning a proxy to the remote resource.
158    ///
159    /// Waits for [`fio::NodeEvent::OnRepresentation`] if [`fio::Flags::FLAG_SEND_REPRESENTATION`]
160    /// is specified, otherwise calls `fuchsia.io/Node.GetAttributes` to verify the result.
161    async fn open_node<T: ProtocolMarker>(
162        &self,
163        path: &str,
164        flags: fio::Flags,
165        options: Option<fio::Options>,
166    ) -> Result<T::Proxy, zx::Status>;
167
168    /// Similar to [`DirectoryProxyExt::open_node`], but waits for and returns the
169    /// [`fio::NodeEvent::OnRepresentation`] event sent when opening a resource.
170    ///
171    /// Requires [`fio::Flags::FLAG_SEND_REPRESENTATION`] to be specified in `flags`.
172    async fn open_node_repr<T: ProtocolMarker>(
173        &self,
174        path: &str,
175        flags: fio::Flags,
176        options: Option<fio::Options>,
177    ) -> Result<(T::Proxy, fio::Representation), zx::Status>;
178}
179
180#[async_trait]
181impl DirectoryProxyExt for fio::DirectoryProxy {
182    async fn open_node<T: ProtocolMarker>(
183        &self,
184        path: &str,
185        flags: fio::Flags,
186        options: Option<fio::Options>,
187    ) -> Result<T::Proxy, zx::Status> {
188        open_node_impl::<T>(self, path, flags, options).await.map(|(proxy, _representation)| proxy)
189    }
190
191    async fn open_node_repr<T: ProtocolMarker>(
192        &self,
193        path: &str,
194        flags: fio::Flags,
195        options: Option<fio::Options>,
196    ) -> Result<(T::Proxy, fio::Representation), zx::Status> {
197        assert!(
198            flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION),
199            "flags must specify the FLAG_SEND_REPRESENTATION flag to use this function!"
200        );
201        let (proxy, representation) = open_node_impl::<T>(self, path, flags, options).await?;
202        Ok((proxy, representation.unwrap()))
203    }
204}
205
206async fn open_node_impl<T: ProtocolMarker>(
207    dir: &fio::DirectoryProxy,
208    path: &str,
209    flags: fio::Flags,
210    options: Option<fio::Options>,
211) -> Result<(T::Proxy, Option<fio::Representation>), zx::Status> {
212    let (proxy, server) = create_proxy::<fio::NodeMarker>();
213    dir.open(path, flags, &options.unwrap_or_default(), server.into_channel())
214        .expect("Failed to call open3");
215    let representation = if flags.contains(fio::Flags::FLAG_SEND_REPRESENTATION) {
216        Some(get_on_representation_event(&proxy).await?)
217    } else {
218        // We use GetAttributes to test that opening the resource succeeded.
219        let _ = proxy.get_attributes(Default::default()).await.map_err(|e| {
220            if let fidl::Error::ClientChannelClosed { epitaph, .. } = e {
221                match epitaph.into() {
222                    Err(s) => s,
223                    Ok(()) => zx::Status::PEER_CLOSED,
224                }
225            } else {
226                panic!("Unhandled FIDL error: {:?}", e);
227            }
228        })?;
229        None
230    };
231    Ok((convert_node_proxy(proxy), representation))
232}
233
234/// Wait for and return a [`fio::NodeEvent::OnRepresentation`] event sent via `node_proxy`.
235async fn get_on_representation_event(
236    node_proxy: &fio::NodeProxy,
237) -> Result<fio::Representation, zx::Status> {
238    // Try to extract the expected NodeEvent, but map channel epitaphs to zx::Status.
239    let event = Clone::clone(node_proxy)
240        .take_event_stream()
241        .try_next()
242        .await
243        .map_err(|e| {
244            if let fidl::Error::ClientChannelClosed { epitaph, .. } = e {
245                match epitaph.into() {
246                    Err(s) => s,
247                    Ok(()) => zx::Status::PEER_CLOSED,
248                }
249            } else {
250                panic!("Unhandled FIDL error: {:?}", e);
251            }
252        })?
253        .expect("Missing NodeEvent in stream!");
254    let representation = match event {
255        fio::NodeEvent::OnRepresentation { payload } => payload,
256        _ => panic!("Found unexpected NodeEvent type in stream!"),
257    };
258    Ok(representation)
259}