blob: e95d24e924c0c2aa69f7834c566c309ff27a65f9 [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::{
capability::{CapabilityProvider, FrameworkCapability, InternalCapabilityProvider},
model::component::WeakComponentInstance,
},
::routing::capability_source::InternalCapability,
async_trait::async_trait,
cm_types::Name,
cm_util::TaskGroup,
fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, ServerEnd},
fidl_fuchsia_component_sandbox as fsandbox, fidl_fuchsia_io as fio, fuchsia_zircon as zx,
fuchsia_zircon::AsHandleRef,
futures::prelude::*,
lazy_static::lazy_static,
sandbox::{Dict, Directory, Open, Receiver},
std::sync::Arc,
tracing::warn,
};
lazy_static! {
static ref CAPABILITY_NAME: Name = fsandbox::FactoryMarker::PROTOCOL_NAME.parse().unwrap();
}
struct FactoryCapabilityProvider {
host: Arc<FactoryCapabilityHost>,
}
impl FactoryCapabilityProvider {
fn new(host: Arc<FactoryCapabilityHost>) -> Self {
Self { host }
}
}
#[async_trait]
impl InternalCapabilityProvider for FactoryCapabilityProvider {
async fn open_protocol(self: Box<Self>, server_end: zx::Channel) {
let server_end = ServerEnd::<fsandbox::FactoryMarker>::new(server_end);
// We only need to look up the component matching this scope.
// These operations should all work, even if the component is not running.
let serve_result = self.host.serve(server_end.into_stream().unwrap()).await;
if let Err(error) = serve_result {
// TODO: Set an epitaph to indicate this was an unexpected error.
warn!(%error, "serve failed");
}
}
}
pub struct FactoryCapabilityHost {
tasks: TaskGroup,
}
impl FactoryCapabilityHost {
pub fn new() -> Self {
Self { tasks: TaskGroup::new() }
}
pub async fn serve(
&self,
mut stream: fsandbox::FactoryRequestStream,
) -> Result<(), fidl::Error> {
while let Some(request) = stream.try_next().await? {
let method_name = request.method_name();
let result = self.handle_request(request).await;
match result {
// If the error was PEER_CLOSED then we don't need to log it as a client can
// disconnect while we are processing its request.
Err(error) if !error.is_closed() => {
warn!(%method_name, %error, "Couldn't send Factory response");
}
_ => {}
}
}
Ok(())
}
async fn handle_request(&self, request: fsandbox::FactoryRequest) -> Result<(), fidl::Error> {
match request {
fsandbox::FactoryRequest::CreateSender { receiver, sender, responder } => {
self.create_sender(receiver, sender);
responder.send()?;
}
fsandbox::FactoryRequest::CreateDictionary { server_end, responder } => {
self.create_dictionary(server_end);
responder.send()?;
}
fsandbox::FactoryRequest::CreateOpen { client_end, server_end, responder } => {
self.create_open(client_end, server_end);
responder.send()?;
}
fsandbox::FactoryRequest::CreateDirectory { client_end, responder } => {
let capability = self.create_directory(client_end);
responder.send(capability)?;
}
fsandbox::FactoryRequest::_UnknownMethod { ordinal, .. } => {
warn!(%ordinal, "fuchsia.component.sandbox/Factory received unknown method");
}
}
Ok(())
}
fn create_sender(
&self,
receiver_client: ClientEnd<fsandbox::ReceiverMarker>,
sender_server: ServerEnd<fsandbox::SenderMarker>,
) {
let (receiver, sender) = Receiver::new();
self.tasks.spawn(async move {
receiver.handle_receiver(receiver_client.into_proxy().unwrap()).await;
});
let sender_client_end_koid = sender_server.basic_info().unwrap().related_koid;
sender.serve_and_register(sender_server.into_stream().unwrap(), sender_client_end_koid);
}
fn create_open(
&self,
openable_client: ClientEnd<fio::OpenableMarker>,
openable_server: ServerEnd<fio::OpenableMarker>,
) {
let open: Open = openable_client.into();
let client_end_koid = openable_server.basic_info().unwrap().related_koid;
open.serve_and_register(openable_server.into_stream().unwrap(), client_end_koid);
}
fn create_directory(
&self,
client_end: ClientEnd<fio::DirectoryMarker>,
) -> fsandbox::Capability {
let directory = Directory::new(client_end);
// This will add the Directory to the registry.
let client_end: ClientEnd<fio::DirectoryMarker> = directory.into();
fsandbox::Capability::Directory(client_end)
}
fn create_dictionary(&self, server_end: ServerEnd<fsandbox::DictionaryMarker>) {
let dict = Dict::new();
let client_end_koid = server_end.basic_info().unwrap().related_koid;
dict.serve_and_register(server_end.into_stream().unwrap(), client_end_koid);
}
}
pub struct FactoryFrameworkCapability {
host: Arc<FactoryCapabilityHost>,
}
impl FactoryFrameworkCapability {
pub fn new(host: Arc<FactoryCapabilityHost>) -> Self {
Self { host }
}
}
impl FrameworkCapability for FactoryFrameworkCapability {
fn matches(&self, capability: &InternalCapability) -> bool {
capability.matches_protocol(&CAPABILITY_NAME)
}
fn new_provider(
&self,
_scope: WeakComponentInstance,
_target: WeakComponentInstance,
) -> Box<dyn CapabilityProvider> {
Box::new(FactoryCapabilityProvider::new(self.host.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use {
fidl::endpoints,
fidl_fuchsia_io as fio, fuchsia_async as fasync,
fuchsia_zircon::{self as zx},
};
#[fuchsia::test]
async fn create_sender() {
let mut tasks = fasync::TaskGroup::new();
let host = FactoryCapabilityHost::new();
let (factory_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::FactoryMarker>().unwrap();
tasks.spawn(async move {
host.serve(stream).await.unwrap();
});
let (sender_proxy, sender_server_end) =
endpoints::create_proxy::<fsandbox::SenderMarker>().unwrap();
let (receiver_client_end, mut receiver_stream) =
endpoints::create_request_stream::<fsandbox::ReceiverMarker>().unwrap();
let () = factory_proxy.create_sender(receiver_client_end, sender_server_end).await.unwrap();
let (ch1, _ch2) = zx::Channel::create();
let expected_koid = ch1.get_koid().unwrap();
sender_proxy.send_(ch1).unwrap();
let request = receiver_stream.try_next().await.unwrap().unwrap();
if let fsandbox::ReceiverRequest::Receive { channel, .. } = request {
assert_eq!(channel.get_koid().unwrap(), expected_koid);
} else {
panic!("unexpected request");
}
}
#[fuchsia::test]
async fn create_directory() {
let mut tasks = fasync::TaskGroup::new();
let host = FactoryCapabilityHost::new();
let (factory_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::FactoryMarker>().unwrap();
tasks.spawn(async move {
host.serve(stream).await.unwrap();
});
let (client_end, _server_end) = endpoints::create_endpoints::<fio::DirectoryMarker>();
let expected_koid = client_end.get_koid().unwrap();
let directory = factory_proxy.create_directory(client_end).await.unwrap();
let fsandbox::Capability::Directory(directory) = directory else {
panic!("wrong type");
};
assert_eq!(directory.get_koid().unwrap(), expected_koid);
}
#[fuchsia::test]
async fn create_dict() {
let mut tasks = fasync::TaskGroup::new();
let host = FactoryCapabilityHost::new();
let (factory_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::FactoryMarker>().unwrap();
tasks.spawn(async move {
host.serve(stream).await.unwrap();
});
let (dict_proxy, server_end) =
endpoints::create_proxy::<fsandbox::DictionaryMarker>().unwrap();
let () = factory_proxy.create_dictionary(server_end).await.unwrap();
// The dictionary is empty.
let items = dict_proxy.read().await.unwrap();
assert_eq!(items.len(), 0);
}
}