blob: 3dd9860dbf71721cc5bbf6c751cca2dcd529febe [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{registry, CapabilityTrait, ConversionError, Open};
use fidl_fuchsia_component_sandbox as fsandbox;
use fuchsia_zircon::{self as zx};
use futures::channel::mpsc;
use std::{fmt::Debug, sync::Arc};
use vfs::directory::entry::DirectoryEntry;
#[derive(Debug)]
pub struct Message {
pub channel: fidl::Channel,
}
impl From<fsandbox::ProtocolPayload> for Message {
fn from(payload: fsandbox::ProtocolPayload) -> Self {
Message { channel: payload.channel }
}
}
/// Types that implement [`Connectable`] let the holder send channels
/// to them.
pub trait Connectable: Send + Sync + Debug {
fn send(&self, message: Message) -> Result<(), ()>;
}
impl Connectable for mpsc::UnboundedSender<crate::Message> {
fn send(&self, message: Message) -> Result<(), ()> {
self.unbounded_send(message).map_err(|_| ())
}
}
/// A capability that transfers another capability to a [Receiver].
#[derive(Debug, Clone)]
pub struct Connector {
inner: std::sync::Arc<dyn Connectable>,
}
impl Connector {
pub fn new_sendable(connector: impl Connectable + 'static) -> Self {
Self { inner: std::sync::Arc::new(connector) }
}
pub(crate) fn new(sender: mpsc::UnboundedSender<Message>) -> Self {
Self { inner: std::sync::Arc::new(sender) }
}
pub(crate) fn send_channel(&self, channel: zx::Channel) -> Result<(), ()> {
self.send(Message { channel })
}
pub fn send(&self, msg: Message) -> Result<(), ()> {
self.inner.send(msg)
}
}
impl From<Connector> for Open {
fn from(connector: Connector) -> Self {
Self::new(vfs::service::endpoint(move |_scope, server_end| {
let _ = connector.send_channel(server_end.into_zx_channel().into());
}))
}
}
impl Connectable for Connector {
fn send(&self, message: Message) -> Result<(), ()> {
self.send(message)
}
}
impl CapabilityTrait for Connector {
fn try_into_directory_entry(self) -> Result<Arc<dyn DirectoryEntry>, ConversionError> {
Ok(vfs::service::endpoint(move |_scope, server_end| {
let _ = self.send_channel(server_end.into_zx_channel().into());
}))
}
}
impl From<Connector> for fsandbox::Connector {
fn from(value: Connector) -> Self {
fsandbox::Connector { token: registry::insert_token(value.into()) }
}
}
impl From<Connector> for fsandbox::Capability {
fn from(connector: Connector) -> Self {
fsandbox::Capability::Connector(connector.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Receiver;
use assert_matches::assert_matches;
use fidl::endpoints::ClientEnd;
use fidl_fuchsia_io as fio;
use futures::StreamExt;
use vfs::execution_scope::ExecutionScope;
use zx::HandleBased;
// NOTE: sending-and-receiving tests are written in `receiver.rs`.
/// Tests that a Sender can be cloned by cloning its FIDL token.
/// and capabilities sent to the original and clone arrive at the same Receiver.
#[fuchsia::test]
async fn fidl_clone() {
let (receiver, sender) = Receiver::new();
// Send a channel through the Connector.
let (ch1, _ch2) = zx::Channel::create();
sender.send_channel(ch1).unwrap();
// Convert the Sender to a FIDL token.
let connector: fsandbox::Connector = sender.into();
// Clone the Sender by cloning the token.
let token_clone = fsandbox::Connector {
token: connector.token.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
};
let connector_clone = match crate::Capability::try_from(fsandbox::Capability::Connector(
token_clone,
))
.unwrap()
{
crate::Capability::Connector(connector) => connector,
capability @ _ => panic!("wrong type {capability:?}"),
};
// Send a channel through the cloned Sender.
let (ch1, _ch2) = zx::Channel::create();
connector_clone.send_channel(ch1).unwrap();
// The Receiver should receive two channels, one from each connector.
for _ in 0..2 {
let _ch = receiver.receive().await.unwrap();
}
}
#[fuchsia::test]
async fn unwrap_server_end_or_serve_node_node_reference_and_describe() {
let receiver = {
let (receiver, sender) = Receiver::new();
let open: Open = sender.into();
let (client_end, server_end) = zx::Channel::create();
let scope = ExecutionScope::new();
open.open(
scope,
fio::OpenFlags::NODE_REFERENCE | fio::OpenFlags::DESCRIBE,
".",
server_end,
);
// The NODE_REFERENCE connection should be terminated on the sender side.
let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
let node: fio::NodeProxy = client_end.into_proxy().unwrap();
let result = node.take_event_stream().next().await.unwrap();
assert_matches!(
result,
Ok(fio::NodeEvent::OnOpen_ { s, info })
if s == zx::Status::OK.into_raw()
&& *info.as_ref().unwrap().as_ref() == fio::NodeInfoDeprecated::Service(fio::Service {})
);
receiver
};
// After closing the sender, the receiver should be done.
assert_matches!(receiver.receive().await, None);
}
#[fuchsia::test]
async fn unwrap_server_end_or_serve_node_describe() {
let (receiver, sender) = Receiver::new();
let open: Open = sender.into();
let (client_end, server_end) = zx::Channel::create();
// The VFS should send the DESCRIBE event, then hand us the channel.
open.open(ExecutionScope::new(), fio::OpenFlags::DESCRIBE, ".", server_end);
// Check we got the channel.
assert_matches!(receiver.receive().await, Some(_));
// Check the client got describe.
let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
let node: fio::NodeProxy = client_end.into_proxy().unwrap();
let result = node.take_event_stream().next().await.unwrap();
assert_matches!(
result,
Ok(fio::NodeEvent::OnOpen_ { s, info })
if s == zx::Status::OK.into_raw()
&& *info.as_ref().unwrap().as_ref() == fio::NodeInfoDeprecated::Service(fio::Service {})
);
}
#[fuchsia::test]
async fn unwrap_server_end_or_serve_node_empty() {
let (receiver, sender) = Receiver::new();
let open: Open = sender.into();
let (client_end, server_end) = zx::Channel::create();
// The VFS should not send any event, but directly hand us the channel.
open.open(ExecutionScope::new(), fio::OpenFlags::empty(), ".", server_end);
// Check that we got the channel.
assert_matches!(receiver.receive().await, Some(_));
// Check that there's no event.
let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
let node: fio::NodeProxy = client_end.into_proxy().unwrap();
assert_matches!(node.take_event_stream().next().await, None);
}
}