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