blob: c3b396d2922419493511869fc17d9466a2bc2f4b [file] [log] [blame]
// Copyright 2021 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, CapabilitySource},
model::{
component::{StartReason, WeakComponentInstance},
error::ModelError,
hooks::{Event, EventPayload, EventType, Hook, HooksRegistration},
model::Model,
routing::report_routing_failure,
},
},
async_trait::async_trait,
cm_rust::{CapabilityName, CapabilityPath, ProtocolDecl},
cm_task_scope::TaskScope,
cm_util::channel,
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
lazy_static::lazy_static,
moniker::{AbsoluteMoniker, ExtendedMoniker},
routing::capability_source::{ComponentCapability, InternalCapability},
std::{
path::PathBuf,
sync::{Arc, Weak},
},
};
lazy_static! {
pub static ref BINDER_SERVICE: CapabilityName = "fuchsia.component.Binder".into();
pub static ref BINDER_CAPABILITY: ComponentCapability =
ComponentCapability::Protocol(ProtocolDecl {
name: BINDER_SERVICE.clone(),
source_path: Some(CapabilityPath {
basename: "fuchsia.component.Binder".into(),
dirname: "svc".into()
}),
});
}
/// Implementation of `fuchsia.component.Binder` FIDL protocol.
pub struct BinderCapabilityProvider {
source: WeakComponentInstance,
target: WeakComponentInstance,
}
impl BinderCapabilityProvider {
pub fn new(source: WeakComponentInstance, target: WeakComponentInstance) -> Self {
Self { source, target }
}
}
#[async_trait]
impl CapabilityProvider for BinderCapabilityProvider {
async fn open(
self: Box<Self>,
task_scope: TaskScope,
_flags: fio::OpenFlags,
_open_mode: u32,
_relative_path: PathBuf,
server_end: &mut zx::Channel,
) -> Result<(), ModelError> {
let target = self.target.clone();
let source = self.source.clone();
let server_end = channel::take_channel(server_end);
task_scope
.add_task(async move {
let source = match source.upgrade().map_err(|e| ModelError::from(e)) {
Ok(source) => source,
Err(err) => {
report_routing_failure_to_target(target, err, server_end).await;
return;
}
};
let start_reason = StartReason::AccessCapability {
target: target.abs_moniker.clone(),
name: BINDER_SERVICE.clone(),
};
match source.start(&start_reason).await {
Ok(_) => {
source.scope_to_runtime(server_end).await;
}
Err(err) => {
report_routing_failure_to_target(target, err, server_end).await;
}
}
})
.await;
Ok(())
}
}
// A `Hook` that serves the `fuchsia.component.Binder` FIDL protocol.
#[derive(Clone)]
pub struct BinderCapabilityHost {
model: Weak<Model>,
}
impl BinderCapabilityHost {
pub fn new(model: Weak<Model>) -> Self {
Self { model }
}
pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
vec![HooksRegistration::new(
"BinderCapabilityHost",
vec![EventType::CapabilityRouted],
Arc::downgrade(self) as Weak<dyn Hook>,
)]
}
async fn on_scoped_framework_capability_routed_async<'a>(
self: Arc<Self>,
source: WeakComponentInstance,
target_moniker: AbsoluteMoniker,
capability: &'a InternalCapability,
capability_provider: Option<Box<dyn CapabilityProvider>>,
) -> Result<Option<Box<dyn CapabilityProvider>>, ModelError> {
// If some other capability has already been installed, then there's nothing to
// do here.
if capability_provider.is_none() && capability.matches_protocol(&BINDER_SERVICE) {
let model = self.model.upgrade().ok_or(ModelError::ModelNotAvailable)?;
let target = WeakComponentInstance::new(&model.look_up(&target_moniker).await?);
Ok(Some(Box::new(BinderCapabilityProvider::new(source, target))
as Box<dyn CapabilityProvider>))
} else {
Ok(capability_provider)
}
}
}
#[async_trait]
impl Hook for BinderCapabilityHost {
async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
if let Ok(EventPayload::CapabilityRouted {
source: CapabilitySource::Framework { capability, component },
capability_provider,
}) = &event.result
{
let target_moniker = match &event.target_moniker {
ExtendedMoniker::ComponentManager => {
Err(ModelError::UnexpectedComponentManagerMoniker)
}
ExtendedMoniker::ComponentInstance(moniker) => Ok(moniker),
}?;
let mut capability_provider = capability_provider.lock().await;
*capability_provider = self
.on_scoped_framework_capability_routed_async(
component.clone(),
target_moniker.clone(),
&capability,
capability_provider.take(),
)
.await?;
}
Ok(())
}
}
async fn report_routing_failure_to_target(
target: WeakComponentInstance,
err: ModelError,
server_end: zx::Channel,
) {
match target.upgrade().map_err(|e| ModelError::from(e)) {
Ok(target) => {
report_routing_failure(&target, &*BINDER_CAPABILITY, &err, server_end).await;
}
Err(err) => {
log::warn!("failed to upgrade reference to {}: {}", target.abs_moniker, err);
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
builtin_environment::BuiltinEnvironment,
capability::CapabilityProvider,
model::{
events::{source::EventSource, stream::EventStream},
testing::test_helpers::*,
},
},
assert_matches::assert_matches,
cm_rust::{self, CapabilityName, ComponentDecl, EventMode},
cm_rust_testing::*,
cm_task_scope::TaskScope,
fidl::{client::Client, handle::AsyncChannel},
fuchsia_zircon as zx,
futures::{lock::Mutex, StreamExt},
moniker::AbsoluteMoniker,
std::path::PathBuf,
};
struct BinderCapabilityTestFixture {
builtin_environment: Arc<Mutex<BuiltinEnvironment>>,
}
impl BinderCapabilityTestFixture {
async fn new(components: Vec<(&'static str, ComponentDecl)>) -> Self {
let TestModelResult { builtin_environment, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
BinderCapabilityTestFixture { builtin_environment }
}
async fn new_event_stream(
&self,
events: Vec<CapabilityName>,
mode: EventMode,
) -> (EventSource, EventStream) {
new_event_stream(self.builtin_environment.clone(), events, mode).await
}
async fn provider(
&self,
source: AbsoluteMoniker,
target: AbsoluteMoniker,
) -> Box<BinderCapabilityProvider> {
let builtin_environment = self.builtin_environment.lock().await;
let source = builtin_environment
.model
.look_up(&source)
.await
.expect("failed to look up source moniker");
let target = builtin_environment
.model
.look_up(&target)
.await
.expect("failed to look up target moniker");
Box::new(BinderCapabilityProvider::new(
WeakComponentInstance::new(&source),
WeakComponentInstance::new(&target),
))
}
}
#[fuchsia::test]
async fn component_starts_on_open() {
let fixture = BinderCapabilityTestFixture::new(vec![
(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("source")
.add_lazy_child("target")
.build(),
),
("source", component_decl_with_test_runner()),
("target", component_decl_with_test_runner()),
])
.await;
let (_event_source, mut event_stream) = fixture
.new_event_stream(
vec![EventType::Resolved.into(), EventType::Started.into()],
EventMode::Async,
)
.await;
let (_client_end, mut server_end) =
zx::Channel::create().expect("failed to create channels");
let moniker: AbsoluteMoniker = vec!["source"].into();
let task_scope = TaskScope::new();
fixture
.provider(moniker.clone(), vec!["target"].into())
.await
.open(task_scope.clone(), fio::OpenFlags::empty(), 0, PathBuf::new(), &mut server_end)
.await
.expect("failed to call open()");
task_scope.shutdown().await;
assert!(event_stream.wait_until(EventType::Resolved, moniker.clone()).await.is_some());
assert!(event_stream.wait_until(EventType::Started, moniker.clone()).await.is_some());
}
// TODO(yaneury): Figure out a way to test this behavior.
#[ignore]
#[fuchsia::test]
async fn channel_is_closed_if_component_does_not_exist() {
let fixture = BinderCapabilityTestFixture::new(vec![(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("target")
.add_lazy_child("unresolvable")
.build(),
)])
.await;
let (client_end, mut server_end) =
zx::Channel::create().expect("failed to create channels");
let moniker: AbsoluteMoniker = vec!["foo"].into();
let task_scope = TaskScope::new();
fixture
.provider(moniker, vec![].into())
.await
.open(task_scope.clone(), fio::OpenFlags::empty(), 0, PathBuf::new(), &mut server_end)
.await
.expect("failed to call open()");
task_scope.shutdown().await;
let client_end =
AsyncChannel::from_channel(client_end).expect("failed to create AsyncChanel");
let client = Client::new(client_end, "binder_service");
let mut event_receiver = client.take_event_receiver();
assert_matches!(
event_receiver.next().await,
Some(Err(fidl::Error::ClientChannelClosed {
status: zx::Status::NOT_FOUND,
protocol_name: "binder_service"
}))
);
assert_matches!(event_receiver.next().await, None);
}
}