blob: 8435549830fbaf48ba976a7f3c36b03b66fabec2 [file] [log] [blame]
// Copyright 2022 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::{ComponentInstance, InstanceState},
error::ModelError,
hooks::{Event, EventPayload, EventType, Hook, HooksRegistration},
model::Model,
routing::{
request_for_namespace_capability_expose, request_for_namespace_capability_use,
route,
},
},
},
async_trait::async_trait,
cm_rust::{CapabilityName, ExposeDecl, ExposeDeclCommon, SourceName, UseDecl},
cm_task_scope::TaskScope,
cm_util::channel,
fidl::endpoints::{DiscoverableProtocolMarker, ServerEnd},
fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys,
fuchsia_zircon as zx,
futures::lock::Mutex,
futures::StreamExt,
lazy_static::lazy_static,
moniker::{AbsoluteMoniker, AbsoluteMonikerBase, RelativeMoniker, RelativeMonikerBase},
std::{
convert::TryFrom,
path::PathBuf,
sync::{Arc, Weak},
},
tracing::warn,
};
lazy_static! {
pub static ref ROUTE_VALIDATOR_CAPABILITY_NAME: CapabilityName =
fsys::RouteValidatorMarker::PROTOCOL_NAME.into();
}
/// Serves the fuchsia.sys2.RouteValidator protocol.
pub struct RouteValidator {
model: Arc<Model>,
}
impl RouteValidator {
pub fn new(model: Arc<Model>) -> Self {
Self { model }
}
pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
vec![HooksRegistration::new(
"RouteValidator",
vec![EventType::CapabilityRouted],
Arc::downgrade(self) as Weak<dyn Hook>,
)]
}
/// Given a `CapabilitySource`, determine if it is a framework-provided
/// RouteValidator capability. If so, serve the capability.
async fn on_capability_routed_async(
self: Arc<Self>,
source: CapabilitySource,
capability_provider: Arc<Mutex<Option<Box<dyn CapabilityProvider>>>>,
) -> Result<(), ModelError> {
// If this is a scoped framework directory capability, then check the source path
if let CapabilitySource::Framework { capability, component } = source {
if capability.matches_protocol(&ROUTE_VALIDATOR_CAPABILITY_NAME) {
// Set the capability provider, if not already set.
let mut capability_provider = capability_provider.lock().await;
if capability_provider.is_none() {
*capability_provider = Some(Box::new(RouteValidatorCapabilityProvider::query(
self,
component.abs_moniker.clone(),
)));
}
}
}
Ok(())
}
async fn validate(
self: &Arc<Self>,
scope_moniker: &AbsoluteMoniker,
moniker_str: String,
) -> Result<Vec<fsys::RouteReport>, fcomponent::Error> {
// Construct the complete moniker using the scope moniker and the relative moniker string.
let moniker = join_monikers(scope_moniker, &moniker_str)?;
let instance =
self.model.find(&moniker).await.ok_or(fcomponent::Error::InstanceNotFound)?;
// Get all use and expose declarations for this component
let (uses, exposes) = {
let state = instance.lock_state().await;
let resolved = match *state {
InstanceState::Resolved(ref r) => r,
// TODO(http://fxbug.dev/102026): The error is that the instance is not currently
// resolved. Use a better error here, when one exists.
_ => return Err(fcomponent::Error::InstanceCannotResolve),
};
let uses = resolved.decl().uses.clone();
let exposes = resolved.decl().exposes.clone();
(uses, exposes)
};
let mut reports = validate_uses(uses, &instance).await;
let mut expose_reports = validate_exposes(exposes, &instance).await;
reports.append(&mut expose_reports);
Ok(reports)
}
/// Serve the fuchsia.sys2.RouteValidator protocol for a given scope on a given stream
async fn serve(
self: Arc<Self>,
scope_moniker: AbsoluteMoniker,
mut stream: fsys::RouteValidatorRequestStream,
) {
loop {
let fsys::RouteValidatorRequest::Validate { moniker, responder } =
match stream.next().await {
Some(Ok(request)) => request,
Some(Err(e)) => {
warn!(error = %e, "Could not get next RouteValidator request");
break;
}
None => break,
};
let mut result = self.validate(&scope_moniker, moniker).await;
if let Err(e) = responder.send(&mut result) {
warn!(error = %e, "RouteValidator failed to respond to Validate call");
break;
}
}
}
}
#[async_trait]
impl Hook for RouteValidator {
async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
match &event.result {
Ok(EventPayload::CapabilityRouted { source, capability_provider }) => {
self.on_capability_routed_async(source.clone(), capability_provider.clone())
.await?;
}
_ => {}
}
Ok(())
}
}
pub struct RouteValidatorCapabilityProvider {
query: Arc<RouteValidator>,
scope_moniker: AbsoluteMoniker,
}
impl RouteValidatorCapabilityProvider {
pub fn query(query: Arc<RouteValidator>, scope_moniker: AbsoluteMoniker) -> Self {
Self { query, scope_moniker }
}
}
#[async_trait]
impl CapabilityProvider for RouteValidatorCapabilityProvider {
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> {
if flags != fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE {
warn!(?flags, "RouteValidator capability got open request with bad flags");
return Ok(());
}
if relative_path.components().count() != 0 {
warn!(
path = %relative_path.display(),
"RouteValidator capability got open request with non-empty path",
);
return Ok(());
}
let server_end = channel::take_channel(server_end);
let server_end = ServerEnd::<fsys::RouteValidatorMarker>::new(server_end);
let stream: fsys::RouteValidatorRequestStream =
server_end.into_stream().map_err(ModelError::stream_creation_error)?;
task_scope
.add_task(async move {
self.query.serve(self.scope_moniker, stream).await;
})
.await;
Ok(())
}
}
/// Takes the scoped component's moniker and a relative moniker string and join them into an
/// absolute moniker.
fn join_monikers(
scope_moniker: &AbsoluteMoniker,
relative_moniker_str: &str,
) -> Result<AbsoluteMoniker, fcomponent::Error> {
let relative_moniker = RelativeMoniker::try_from(relative_moniker_str)
.map_err(|_| fcomponent::Error::InvalidArguments)?;
if !relative_moniker.up_path().is_empty() {
return Err(fcomponent::Error::InvalidArguments);
}
let abs_moniker = AbsoluteMoniker::from_relative(scope_moniker, &relative_moniker)
.map_err(|_| fcomponent::Error::InvalidArguments)?;
Ok(abs_moniker)
}
async fn validate_uses(
uses: Vec<UseDecl>,
instance: &Arc<ComponentInstance>,
) -> Vec<fsys::RouteReport> {
let mut reports = vec![];
for use_ in uses {
let capability = Some(use_.source_name().to_string());
let decl_type = Some(fsys::DeclType::Use);
if let Ok(route_request) = request_for_namespace_capability_use(use_) {
let error = if let Err(e) = route(route_request, &instance).await {
Some(fsys::RouteError { summary: Some(e.to_string()), ..fsys::RouteError::EMPTY })
} else {
None
};
reports.push(fsys::RouteReport {
capability,
decl_type,
error,
..fsys::RouteReport::EMPTY
})
}
}
reports
}
async fn validate_exposes(
exposes: Vec<ExposeDecl>,
instance: &Arc<ComponentInstance>,
) -> Vec<fsys::RouteReport> {
let mut reports = vec![];
for expose in exposes {
let capability = Some(expose.target_name().to_string());
let decl_type = Some(fsys::DeclType::Expose);
if let Ok(route_request) = request_for_namespace_capability_expose(expose) {
let error = if let Err(e) = route(route_request, instance).await {
Some(fsys::RouteError { summary: Some(e.to_string()), ..fsys::RouteError::EMPTY })
} else {
None
};
reports.push(fsys::RouteReport {
capability,
decl_type,
error,
..fsys::RouteReport::EMPTY
})
}
}
reports
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::testing::test_helpers::{TestEnvironmentBuilder, TestModelResult},
cm_rust::*,
cm_rust_testing::ComponentDeclBuilder,
fidl::endpoints::create_proxy_and_stream,
fuchsia_async as fasync,
};
#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct Key {
capability: String,
decl_type: fsys::DeclType,
}
#[fuchsia::test]
async fn all_routes_success() {
let use_from_framework_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Framework,
source_name: "fuchsia.component.Realm".into(),
target_path: CapabilityPath::try_from("/svc/fuchsia.component.Realm").unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let use_from_child_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Child("my_child".to_string()),
source_name: "foo.bar".into(),
target_path: CapabilityPath::try_from("/svc/foo.bar").unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let expose_from_child_decl = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("my_child".to_string()),
source_name: "foo.bar".into(),
target: ExposeTarget::Parent,
target_name: "foo.bar".into(),
});
let expose_from_self_decl = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo.bar".into(),
target: ExposeTarget::Parent,
target_name: "foo.bar".into(),
});
let capability_decl = ProtocolDecl {
name: "foo.bar".into(),
source_path: Some(CapabilityPath::try_from("/svc/foo.bar").unwrap()),
};
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(use_from_framework_decl)
.use_(use_from_child_decl)
.expose(expose_from_child_decl)
.add_lazy_child("my_child")
.build(),
),
(
"my_child",
ComponentDeclBuilder::new()
.protocol(capability_decl)
.expose(expose_from_self_decl)
.build(),
),
];
let TestModelResult { model, builtin_environment, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let route_validator = {
let env = builtin_environment.lock().await;
env.route_validator.clone().unwrap()
};
let (validator, validator_request_stream) =
create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
route_validator.serve(AbsoluteMoniker::root(), validator_request_stream).await
});
model.start().await;
// `my_child` should not be resolved right now
let instance = model.find_resolved(&vec!["my_child"].into()).await;
assert!(instance.is_none());
// Validate the root
let mut results = validator.validate(".").await.unwrap().unwrap();
results.sort_by_key(|r| Key {
capability: r.capability.clone().unwrap(),
decl_type: r.decl_type.clone().unwrap(),
});
assert_eq!(results.len(), 3);
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "foo.bar");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Use);
assert!(report.error.is_none());
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "foo.bar");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Expose);
assert!(report.error.is_none());
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "fuchsia.component.Realm");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Use);
assert!(report.error.is_none());
// This validation should have caused `my_child` to be resolved
let instance = model.find_resolved(&vec!["my_child"].into()).await;
assert!(instance.is_some());
// Validate `my_child`
let mut results = validator.validate("./my_child").await.unwrap().unwrap();
assert_eq!(results.len(), 1);
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "foo.bar");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Expose);
assert!(report.error.is_none());
}
#[fuchsia::test]
async fn all_routes_fail() {
let invalid_source_name_use_from_child_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Child("my_child".to_string()),
source_name: "a".into(),
target_path: CapabilityPath::try_from("/svc/a").unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let invalid_source_use_from_child_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Child("bad_child".to_string()),
source_name: "b".into(),
target_path: CapabilityPath::try_from("/svc/b").unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required,
});
let invalid_source_name_expose_from_child_decl = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("my_child".to_string()),
source_name: "c".into(),
target: ExposeTarget::Parent,
target_name: "c".into(),
});
let invalid_source_expose_from_child_decl = ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("bad_child".to_string()),
source_name: "d".into(),
target: ExposeTarget::Parent,
target_name: "d".into(),
});
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(invalid_source_name_use_from_child_decl)
.use_(invalid_source_use_from_child_decl)
.expose(invalid_source_name_expose_from_child_decl)
.expose(invalid_source_expose_from_child_decl)
.add_lazy_child("my_child")
.build(),
),
("my_child", ComponentDeclBuilder::new().build()),
];
let TestModelResult { model, builtin_environment, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let route_validator = {
let env = builtin_environment.lock().await;
env.route_validator.clone().unwrap()
};
let (validator, validator_request_stream) =
create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
route_validator.serve(AbsoluteMoniker::root(), validator_request_stream).await
});
model.start().await;
// `my_child` should not be resolved right now
let instance = model.find_resolved(&vec!["my_child"].into()).await;
assert!(instance.is_none());
// Validate the root
let mut results = validator.validate(".").await.unwrap().unwrap();
assert_eq!(results.len(), 4);
results.sort_by_key(|r| Key {
capability: r.capability.clone().unwrap(),
decl_type: r.decl_type.clone().unwrap(),
});
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "a");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Use);
assert!(report.error.is_some());
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "b");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Use);
assert!(report.error.is_some());
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "c");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Expose);
assert!(report.error.is_some());
let report = results.remove(0);
assert_eq!(report.capability.unwrap(), "d");
assert_eq!(report.decl_type.unwrap(), fsys::DeclType::Expose);
assert!(report.error.is_some());
// This validation should have caused `my_child` to be resolved
let instance = model.find_resolved(&vec!["my_child"].into()).await;
assert!(instance.is_some());
}
}