blob: 1ed6d0f3d0dd3fb614b3c4d438dd45d1ca32204b [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, Path},
fidl::endpoints::{DiscoverableProtocolMarker, ServerEnd},
fidl_fuchsia_component as fcomponent, fuchsia_zircon as zx,
futures::{
channel::mpsc::{unbounded, UnboundedSender},
prelude::*,
},
lazy_static::lazy_static,
namespace::NamespaceError,
sandbox::Capability,
serve_processargs::{BuildNamespaceError, NamespaceBuilder},
std::sync::Arc,
tracing::warn,
vfs::execution_scope::ExecutionScope,
};
lazy_static! {
static ref CAPABILITY_NAME: Name = fcomponent::NamespaceMarker::PROTOCOL_NAME.parse().unwrap();
}
struct NamespaceCapabilityProvider {
host: Arc<NamespaceCapabilityHost>,
}
impl NamespaceCapabilityProvider {
pub fn new(host: Arc<NamespaceCapabilityHost>) -> Self {
Self { host }
}
}
#[async_trait]
impl InternalCapabilityProvider for NamespaceCapabilityProvider {
async fn open_protocol(self: Box<Self>, server_end: zx::Channel) {
let server_end = ServerEnd::<fcomponent::NamespaceMarker>::new(server_end);
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 NamespaceCapabilityHost {
namespace_scope: ExecutionScope,
}
impl Drop for NamespaceCapabilityHost {
fn drop(&mut self) {
self.namespace_scope.shutdown();
}
}
impl NamespaceCapabilityHost {
pub fn new() -> Self {
Self { namespace_scope: ExecutionScope::new() }
}
pub async fn serve(
&self,
mut stream: fcomponent::NamespaceRequestStream,
) -> 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 Namespace response");
}
_ => {}
}
}
Ok(())
}
async fn handle_request(
&self,
request: fcomponent::NamespaceRequest,
) -> Result<(), fidl::Error> {
match request {
fcomponent::NamespaceRequest::Create { entries, responder } => {
let res = self.create(entries).await;
responder.send(res)?;
}
fcomponent::NamespaceRequest::_UnknownMethod { ordinal, .. } => {
warn!(%ordinal, "fuchsia.component/Namespace received unknown method");
}
}
Ok(())
}
async fn create(
&self,
entries: Vec<fcomponent::NamespaceInputEntry>,
) -> Result<Vec<fcomponent::NamespaceEntry>, fcomponent::NamespaceError> {
let mut namespace_builder =
NamespaceBuilder::new(self.namespace_scope.clone(), Self::ignore_not_found());
for entry in entries {
let path = entry.path;
let dict = entry.dictionary.into_proxy().unwrap();
let items: Vec<_> =
dict.read().await.map_err(|_| fcomponent::NamespaceError::DictionaryRead)?;
for item in items {
let capability: Capability = item.value.try_into().unwrap();
let path = Path::new(format!("{}/{}", path, item.key))
.map_err(|_| fcomponent::NamespaceError::BadEntry)?;
namespace_builder.add_object(capability, &path).map_err(Self::error_to_fidl)?;
}
}
let namespace = namespace_builder.serve().map_err(Self::error_to_fidl)?;
let out = namespace.flatten().into_iter().map(Into::into).collect();
Ok(out)
}
fn error_to_fidl(e: BuildNamespaceError) -> fcomponent::NamespaceError {
match e {
BuildNamespaceError::NamespaceError(e) => match e {
NamespaceError::Shadow(_) => fcomponent::NamespaceError::Shadow,
NamespaceError::Duplicate(_) => fcomponent::NamespaceError::Duplicate,
NamespaceError::EntryError(_) => fcomponent::NamespaceError::BadEntry,
},
BuildNamespaceError::Conversion { .. } | BuildNamespaceError::Serve { .. } => {
fcomponent::NamespaceError::Conversion
}
}
}
fn ignore_not_found() -> UnboundedSender<String> {
let (sender, _receiver) = unbounded();
sender
}
}
pub struct NamespaceFrameworkCapability {
host: Arc<NamespaceCapabilityHost>,
}
impl NamespaceFrameworkCapability {
pub fn new() -> Self {
Self { host: Arc::new(NamespaceCapabilityHost::new()) }
}
}
impl FrameworkCapability for NamespaceFrameworkCapability {
fn matches(&self, capability: &InternalCapability) -> bool {
capability.matches_protocol(&CAPABILITY_NAME)
}
fn new_provider(
&self,
_scope: WeakComponentInstance,
_target: WeakComponentInstance,
) -> Box<dyn CapabilityProvider> {
Box::new(NamespaceCapabilityProvider::new(self.host.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use {
assert_matches::assert_matches,
fidl::endpoints::{self, ProtocolMarker, Proxy},
fidl_fidl_examples_routing_echo as fecho, fidl_fuchsia_component_sandbox as fsandbox,
fuchsia_async as fasync,
fuchsia_component::client,
futures::TryStreamExt,
sandbox::{Dict, Receiver},
};
async fn handle_echo_request_stream(response: &str, mut stream: fecho::EchoRequestStream) {
while let Ok(Some(request)) = stream.try_next().await {
match request {
fecho::EchoRequest::EchoString { value: _, responder } => {
responder.send(Some(response)).unwrap();
}
}
}
}
#[fuchsia::test]
async fn namespace_create() {
let mut tasks = fasync::TaskGroup::new();
let host = NamespaceCapabilityHost::new();
let (namespace_proxy, stream) =
endpoints::create_proxy_and_stream::<fcomponent::NamespaceMarker>().unwrap();
tasks.add(fasync::Task::spawn(async move {
host.serve(stream).await.unwrap();
}));
let mut namespace_pairs = vec![];
for (path, response) in [("/svc", "first"), ("/zzz/svc", "second")] {
// Initialize the host and sender/receiver pair.
let (receiver, sender) = Receiver::new();
// Serve an Echo request handler on the Receiver.
tasks.add(fasync::Task::spawn(async move {
loop {
let msg = receiver.receive().await.unwrap();
let stream: fecho::EchoRequestStream =
ServerEnd::<fecho::EchoMarker>::from(msg.channel).into_stream().unwrap();
handle_echo_request_stream(response, stream).await;
}
}));
// Create a dictionary and add the Sender to it.
let mut dict = Dict::new();
dict.lock_entries()
.insert(fecho::EchoMarker::DEBUG_NAME.parse().unwrap(), Capability::Sender(sender));
let (dict_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::DictionaryMarker>().unwrap();
tasks.add(fasync::Task::spawn(async move {
dict.serve_dict(stream).await.unwrap();
}));
namespace_pairs.push(fcomponent::NamespaceInputEntry {
path: path.into(),
dictionary: dict_proxy.into_channel().unwrap().into_zx_channel().into(),
})
}
// Convert the dictionaries to a namespace.
let mut namespace_entries = namespace_proxy.create(namespace_pairs).await.unwrap().unwrap();
// Confirm that the Sender in the dictionary was converted to a service node, and we
// can access the Echo protocol (served by the Receiver) through this node.
let entry = namespace_entries.remove(0);
assert_matches!(entry.path, Some(p) if p == "/svc");
let dir = entry.directory.unwrap().into_proxy().unwrap();
let echo = client::connect_to_protocol_at_dir_root::<fecho::EchoMarker>(&dir).unwrap();
let response = echo.echo_string(None).await.unwrap();
assert_matches!(response, Some(m) if m == "first");
let entry = namespace_entries.remove(0);
assert!(namespace_entries.is_empty());
assert_matches!(entry.path, Some(p) if p == "/zzz/svc");
let dir = entry.directory.unwrap().into_proxy().unwrap();
let echo = client::connect_to_protocol_at_dir_root::<fecho::EchoMarker>(&dir).unwrap();
let response = echo.echo_string(None).await.unwrap();
assert_matches!(response, Some(m) if m == "second");
}
#[fuchsia::test]
async fn namespace_create_err_shadow() {
let mut tasks = fasync::TaskGroup::new();
let host = NamespaceCapabilityHost::new();
let (namespace_proxy, stream) =
endpoints::create_proxy_and_stream::<fcomponent::NamespaceMarker>().unwrap();
tasks.add(fasync::Task::spawn(async move {
host.serve(stream).await.unwrap();
}));
// Two entries with a shadowing path.
let mut namespace_pairs = vec![];
for path in ["/svc", "/svc/shadow"] {
// Initialize the host and sender/receiver pair.
let (receiver, sender) = Receiver::new();
// Serve an Echo request handler on the Receiver.
tasks.add(fasync::Task::spawn(async move {
while let Some(msg) = receiver.receive().await {
let stream: fecho::EchoRequestStream =
ServerEnd::<fecho::EchoMarker>::from(msg.channel).into_stream().unwrap();
handle_echo_request_stream("hello", stream).await;
}
}));
// Create a dictionary and add the Sender to it.
let mut dict = Dict::new();
dict.lock_entries()
.insert(fecho::EchoMarker::DEBUG_NAME.parse().unwrap(), Capability::Sender(sender));
let (dict_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::DictionaryMarker>().unwrap();
tasks.add(fasync::Task::spawn(async move {
dict.serve_dict(stream).await.unwrap();
}));
namespace_pairs.push(fcomponent::NamespaceInputEntry {
path: path.into(),
dictionary: dict_proxy.into_channel().unwrap().into_zx_channel().into(),
})
}
// Try to convert the dictionaries to a namespace. Expect an error because one path
// shadows another.
let res = namespace_proxy.create(namespace_pairs).await.unwrap();
assert_matches!(res, Err(fcomponent::NamespaceError::Shadow));
}
#[fuchsia::test]
async fn namespace_create_err_dict_read() {
let mut tasks = fasync::TaskGroup::new();
let host = NamespaceCapabilityHost::new();
let (namespace_proxy, stream) =
endpoints::create_proxy_and_stream::<fcomponent::NamespaceMarker>().unwrap();
tasks.add(fasync::Task::spawn(async move {
host.serve(stream).await.unwrap();
}));
// Create a dictionary and close the server end.
let (dict_proxy, stream) =
endpoints::create_proxy_and_stream::<fsandbox::DictionaryMarker>().unwrap();
drop(stream);
let namespace_pairs = vec![fcomponent::NamespaceInputEntry {
path: "/svc".into(),
dictionary: dict_proxy.into_channel().unwrap().into_zx_channel().into(),
}];
// Try to convert the dictionaries to a namespace. Expect an error because the dictionary
// was unreadable.
let res = namespace_proxy.create(namespace_pairs).await.unwrap();
assert_matches!(res, Err(fcomponent::NamespaceError::DictionaryRead));
}
}