blob: 42d313655dd8921b8afb6297bef255c6e3aebca7 [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.
pub mod component_id_index;
pub mod policy;
pub mod rights;
pub mod storage;
pub mod storage_admin;
use {
assert_matches::assert_matches,
async_trait::async_trait,
cm_moniker::InstancedRelativeMoniker,
cm_rust::{
CapabilityDecl, CapabilityName, CapabilityPath, CapabilityTypeName, ComponentDecl,
DependencyType, DictionaryValue, EventDecl, EventMode, ExposeDecl, ExposeDirectoryDecl,
ExposeProtocolDecl, ExposeRunnerDecl, ExposeServiceDecl, ExposeSource, ExposeTarget,
OfferDecl, OfferDirectoryDecl, OfferEventDecl, OfferProtocolDecl, OfferRunnerDecl,
OfferServiceDecl, OfferSource, OfferTarget, ProgramDecl, ProtocolDecl, RegistrationSource,
RunnerDecl, RunnerRegistration, ServiceDecl, UseDecl, UseDirectoryDecl, UseEventDecl,
UseProtocolDecl, UseServiceDecl, UseSource,
},
cm_rust_testing::{
ChildDeclBuilder, ComponentDeclBuilder, DirectoryDeclBuilder, EnvironmentDeclBuilder,
ProtocolDeclBuilder, TEST_RUNNER_NAME,
},
fidl::endpoints::ProtocolMarker,
fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_data as fdata, fuchsia_zircon_status as zx,
maplit::hashmap,
moniker::{
AbsoluteMoniker, AbsoluteMonikerBase, ChildMonikerBase, ExtendedMoniker,
RelativeMonikerBase,
},
routing::{
capability_source::{CapabilitySourceInterface, ComponentCapability, InternalCapability},
component_id_index::ComponentInstanceId,
component_instance::ComponentInstanceInterface,
config::{AllowlistEntry, CapabilityAllowlistKey, CapabilityAllowlistSource},
error::RoutingError,
event::EventSubscription,
rights::READ_RIGHTS,
route_capability, RouteRequest, RouteSource,
},
std::{
collections::HashSet,
convert::{TryFrom, TryInto},
marker::PhantomData,
path::{Path, PathBuf},
sync::Arc,
},
};
/// Construct a capability path for the hippo service.
pub fn default_service_capability() -> CapabilityPath {
"/svc/hippo".try_into().unwrap()
}
/// Construct a capability path for the hippo directory.
pub fn default_directory_capability() -> CapabilityPath {
"/data/hippo".try_into().unwrap()
}
/// Returns an empty component decl for an executable component.
pub fn default_component_decl() -> ComponentDecl {
ComponentDecl { ..Default::default() }
}
/// Returns an empty component decl set up to have a non-empty program and to use the "test_runner"
/// runner.
pub fn component_decl_with_test_runner() -> ComponentDecl {
ComponentDecl {
program: Some(ProgramDecl {
runner: Some(TEST_RUNNER_NAME.into()),
info: fdata::Dictionary { entries: Some(vec![]), ..fdata::Dictionary::EMPTY },
}),
..Default::default()
}
}
/// Same as above but with the component also exposing Binder protocol.
pub fn component_decl_with_exposed_binder() -> ComponentDecl {
ComponentDecl {
program: Some(ProgramDecl {
runner: Some(TEST_RUNNER_NAME.into()),
info: fdata::Dictionary { entries: Some(vec![]), ..fdata::Dictionary::EMPTY },
}),
exposes: vec![ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Framework,
source_name: CapabilityName(fcomponent::BinderMarker::DEBUG_NAME.to_owned()),
target: ExposeTarget::Parent,
target_name: CapabilityName(fcomponent::BinderMarker::DEBUG_NAME.to_owned()),
})],
..Default::default()
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ExpectedResult {
Ok,
Err(zx::Status),
ErrWithNoEpitaph,
}
pub enum CheckUse {
Protocol {
path: CapabilityPath,
expected_res: ExpectedResult,
},
Service {
path: CapabilityPath,
instance: String,
member: String,
expected_res: ExpectedResult,
},
Directory {
path: CapabilityPath,
file: PathBuf,
expected_res: ExpectedResult,
},
Storage {
path: CapabilityPath,
// The relative moniker from the storage declaration to the use declaration. Only
// used if `expected_res` is Ok.
storage_relation: Option<InstancedRelativeMoniker>,
// The backing directory for this storage is in component manager's namespace, not the
// test's isolated test directory.
from_cm_namespace: bool,
storage_subdir: Option<String>,
expected_res: ExpectedResult,
},
StorageAdmin {
// The relative moniker from the storage declaration to the use declaration.
storage_relation: InstancedRelativeMoniker,
// The backing directory for this storage is in component manager's namespace, not the
// test's isolated test directory.
from_cm_namespace: bool,
storage_subdir: Option<String>,
expected_res: ExpectedResult,
},
Event {
request: EventSubscription,
expected_res: ExpectedResult,
},
}
impl CheckUse {
pub fn default_directory(expected_res: ExpectedResult) -> Self {
Self::Directory {
path: default_directory_capability(),
file: PathBuf::from("hippo"),
expected_res,
}
}
}
// This function should reproduce the logic of `crate::storage::generate_storage_path`.
pub fn generate_storage_path(
subdir: Option<String>,
relative_moniker: &InstancedRelativeMoniker,
instance_id: Option<&ComponentInstanceId>,
) -> PathBuf {
if let Some(id) = instance_id {
return [id].iter().collect();
}
assert!(relative_moniker.up_path().is_empty());
let mut down_path = relative_moniker.down_path().iter();
let mut dir_path = vec![];
if let Some(subdir) = subdir {
dir_path.push(subdir);
}
if let Some(p) = down_path.next() {
dir_path.push(p.as_str().to_string());
}
while let Some(p) = down_path.next() {
dir_path.push("children".to_string());
dir_path.push(p.as_str().to_string());
}
// Storage capabilities used to have a hardcoded set of types, which would be appended
// here. To maintain compatibility with the old paths (and thus not lose data when this was
// migrated) we append "data" here. This works because this is the only type of storage
// that was actually used in the wild.
//
// This is only temporary, until the storage instance id migration changes this layout.
dir_path.push("data".to_string());
dir_path.into_iter().collect()
}
/// A `RoutingTestModel` attempts to use capabilities from instances in a component model
/// and checks the result of the attempt against an expectation.
#[async_trait]
pub trait RoutingTestModel {
type C: ComponentInstanceInterface + std::fmt::Debug + 'static;
/// Checks a `use` declaration at `moniker` by trying to use `capability`.
async fn check_use(&self, moniker: AbsoluteMoniker, check: CheckUse);
/// Checks using a capability from a component's exposed directory.
async fn check_use_exposed_dir(&self, moniker: AbsoluteMoniker, check: CheckUse);
/// Looks up a component instance by its absolute moniker.
async fn look_up_instance(
&self,
moniker: &AbsoluteMoniker,
) -> Result<Arc<Self::C>, anyhow::Error>;
/// Checks that a use declaration of `path` at `moniker` can be opened with
/// Fuchsia file operations.
async fn check_open_file(&self, moniker: AbsoluteMoniker, path: CapabilityPath);
/// Create a file with the given contents in the test dir, along with any subdirectories
/// required.
async fn create_static_file(&self, path: &Path, contents: &str) -> Result<(), anyhow::Error>;
/// Installs a new directory at `path` in the test's namespace.
fn install_namespace_directory(&self, path: &str);
/// Creates a subdirectory in the outgoing dir's /data directory.
fn add_subdir_to_data_directory(&self, subdir: &str);
/// Asserts that the subdir given by `path` within the test directory contains exactly the
/// filenames in `expected`.
async fn check_test_subdir_contents(&self, path: &str, expected: Vec<String>);
/// Asserts that the directory at absolute `path` contains exactly the filenames in `expected`.
async fn check_namespace_subdir_contents(&self, path: &str, expected: Vec<String>);
/// Asserts that the subdir given by `path` within the test directory contains a file named `expected`.
async fn check_test_subdir_contains(&self, path: &str, expected: String);
/// Asserts that the tree in the test directory under `path` contains a file named `expected`.
async fn check_test_dir_tree_contains(&self, expected: String);
}
/// Builds an implementation of `RoutingTestModel` from a set of `ComponentDecl`s.
#[async_trait]
pub trait RoutingTestModelBuilder {
type Model: RoutingTestModel;
/// Create a new builder. Both string arguments refer to component names, not URLs,
/// ex: "a", not "test:///a" or "test:///a_resolved".
fn new(root_component: &str, components: Vec<(&'static str, ComponentDecl)>) -> Self;
/// Set the capabilities that should be available from the top instance's namespace.
fn set_namespace_capabilities(&mut self, caps: Vec<CapabilityDecl>);
/// Set the capabilities that should be available as built-in capabilities.
fn set_builtin_capabilities(&mut self, caps: Vec<CapabilityDecl>);
/// Register a mock `runner` in the built-in environment.
fn register_mock_builtin_runner(&mut self, runner: &str);
/// Add a custom capability security policy to restrict routing of certain caps.
fn add_capability_policy(
&mut self,
key: CapabilityAllowlistKey,
allowlist: HashSet<AllowlistEntry>,
);
/// Add a custom debug capability security policy to restrict routing of certain caps.
fn add_debug_capability_policy(
&mut self,
key: CapabilityAllowlistKey,
allowlist: HashSet<(AbsoluteMoniker, String)>,
);
/// Sets the path to the component ID index for the test model.
fn set_component_id_index_path(&mut self, index_path: String);
async fn build(self) -> Self::Model;
}
/// The CommonRoutingTests are run under multiple contexts, e.g. both on Fuchsia under
/// component_manager and on the build host under cm_fidl_analyzer. This macro helps ensure that all
/// tests are run in each context.
#[macro_export]
macro_rules! instantiate_common_routing_tests {
($builder_impl:path) => {
// New CommonRoutingTest tests must be added to this list to run.
instantiate_common_routing_tests! {
$builder_impl,
test_use_from_parent,
test_use_from_child,
test_use_from_self,
test_use_from_grandchild,
test_use_from_grandparent,
test_use_from_sibling_no_root,
test_use_from_sibling_root,
test_use_from_niece,
test_use_kitchen_sink,
test_use_from_component_manager_namespace,
test_offer_from_component_manager_namespace,
test_use_not_offered,
test_use_offer_source_not_exposed,
test_use_offer_source_not_offered,
test_use_from_expose,
test_route_protocol_from_expose,
test_use_from_expose_to_framework,
test_offer_from_non_executable,
test_use_directory_with_subdir_from_grandparent,
test_use_directory_with_subdir_from_sibling,
test_expose_directory_with_subdir,
test_expose_from_self_and_child,
test_use_not_exposed,
test_use_protocol_denied_by_capability_policy,
test_use_directory_with_alias_denied_by_capability_policy,
test_use_protocol_partial_chain_allowed_by_capability_policy,
test_use_protocol_component_provided_capability_policy,
test_use_from_component_manager_namespace_denied_by_policy,
test_use_event_from_framework_denied_by_capability_policy,
test_use_protocol_component_provided_debug_capability_policy_at_root_from_self,
test_use_protocol_component_provided_debug_capability_policy_from_self,
test_use_protocol_component_provided_debug_capability_policy_from_child,
test_use_protocol_component_provided_debug_capability_policy_from_grandchild,
test_use_event_from_framework,
test_can_offer_capability_requested_event,
test_use_event_from_parent,
test_use_event_from_grandparent,
test_event_filter_routing,
test_use_builtin_event,
test_route_service_from_parent,
test_route_service_from_child,
test_route_service_from_sibling,
test_use_builtin_from_grandparent,
test_invalid_use_from_component_manager,
test_invalid_offer_from_component_manager,
test_route_runner_from_parent_environment,
test_route_runner_from_grandparent_environment,
test_route_runner_from_sibling_environment,
test_route_runner_from_inherited_environment,
test_route_runner_from_environment_not_found,
test_route_builtin_runner,
test_route_builtin_runner_from_root_env,
test_route_builtin_runner_not_found,
test_route_builtin_runner_from_root_env_registration_not_found,
}
};
($builder_impl:path, $test:ident, $($remaining:ident),+ $(,)?) => {
instantiate_common_routing_tests! { $builder_impl, $test }
instantiate_common_routing_tests! { $builder_impl, $($remaining),+ }
};
($builder_impl:path, $test:ident) => {
// TODO(fxbug.dev/77647): #[fuchsia::test] did not work inside a declarative macro, so this
// falls back on fuchsia_async and manual logging initialization for now.
#[fuchsia_async::run_singlethreaded(test)]
async fn $test() {
fuchsia::init_logging_for_component_with_executor(
|| {}, &[], fuchsia::Interest::EMPTY)();
$crate::CommonRoutingTest::<$builder_impl>::new().$test().await
}
};
}
pub struct CommonRoutingTest<T: RoutingTestModelBuilder> {
builder: PhantomData<T>,
}
impl<T: RoutingTestModelBuilder> CommonRoutingTest<T> {
pub fn new() -> Self {
Self { builder: PhantomData }
}
/// a
/// \
/// b
///
/// a: offers directory /data/foo from self as /data/bar
/// a: offers service /svc/foo from self as /svc/bar
/// a: offers service /svc/file from self as /svc/device
/// b: uses directory /data/bar as /data/hippo
/// b: uses service /svc/bar as /svc/hippo
/// b: uses service /svc/device
///
/// The test related to `/svc/file` is used to verify that services that require
/// extended flags, like `OPEN_FLAG_DESCRIBE`, work correctly. This often
/// happens for fuchsia.hardware protocols that compose fuchsia.io protocols,
/// and expect that `fdio_open` should operate correctly.
pub async fn test_use_from_parent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.protocol(ProtocolDeclBuilder::new("file").path("/svc/file").build())
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "file".into(),
target_name: "device".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "bar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "bar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "device".into(),
target_path: CapabilityPath::try_from("/svc/device").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model.check_use(vec!["b"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model.check_open_file(vec!["b"].into(), "/svc/device".try_into().unwrap()).await;
}
/// a
/// \
/// b
///
/// a: uses directory /data/bar from #b as /data/hippo
/// a: uses service /svc/bar from #b as /svc/hippo
/// b: exposes directory /data/foo from self as /data/bar
/// b: exposes service /svc/foo from self as /svc/bar
pub async fn test_use_from_child(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Child("b".to_string()),
source_name: "bar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Child("b".to_string()),
source_name: "bar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model.check_use(vec![].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a: uses protocol /svc/hippo from self
pub async fn test_use_from_self(&self) {
let components = vec![(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("hippo").build())
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Self_,
source_name: "hippo".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
)];
let model = T::new("a", components).build().await;
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// a: uses /data/baz from #b as /data/hippo
/// a: uses /svc/baz from #b as /svc/hippo
/// b: exposes directory /data/bar from #c as /data/baz
/// b: exposes service /svc/bar from #c as /svc/baz
/// c: exposes directory /data/foo from self as /data/bar
/// c: exposes service /svc/foo from self as /svc/bar
pub async fn test_use_from_grandchild(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Child("b".to_string()),
source_name: "baz_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Child("b".to_string()),
source_name: "baz_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("c".to_string()),
source_name: "bar_data".into(),
target_name: "baz_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("c".to_string()),
source_name: "bar_svc".into(),
target_name: "baz_svc".into(),
target: ExposeTarget::Parent,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model.check_use(vec![].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// a: offers directory /data/foo from self as /data/bar
/// a: offers service /svc/foo from self as /svc/bar
/// b: offers directory /data/bar from realm as /data/baz
/// b: offers service /svc/bar from realm as /svc/baz
/// c: uses /data/baz as /data/hippo
/// c: uses /svc/baz as /svc/hippo
pub async fn test_use_from_grandparent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Parent,
source_name: "bar_data".into(),
target_name: "baz_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "bar_svc".into(),
target_name: "baz_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "baz_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "baz_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(vec!["b", "c"].into(), CheckUse::default_directory(ExpectedResult::Ok))
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// a: offers service /svc/builtin.Echo from realm
/// b: offers service /svc/builtin.Echo from realm
/// c: uses /svc/builtin.Echo as /svc/hippo
pub async fn test_use_builtin_from_grandparent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "builtin.Echo".into(),
target_name: "builtin.Echo".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "builtin.Echo".into(),
target_name: "builtin.Echo".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "builtin.Echo".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "builtin.Echo".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// /
/// b
/// / \
/// d c
///
/// d: exposes directory /data/foo from self as /data/bar
/// b: offers directory /data/bar from d as /data/foobar to c
/// c: uses /data/foobar as /data/hippo
pub async fn test_use_from_sibling_no_root(&self) {
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("d".to_string()),
source_name: "bar_data".into(),
target_name: "foobar_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::static_child("d".to_string()),
source_name: "bar_svc".into(),
target_name: "foobar_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.add_lazy_child("d")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foobar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "foobar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(vec!["b", "c"].into(), CheckUse::default_directory(ExpectedResult::Ok))
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// / \
/// b c
///
/// b: exposes directory /data/foo from self as /data/bar
/// a: offers directory /data/bar from b as /data/baz to c
/// c: uses /data/baz as /data/hippo
pub async fn test_use_from_sibling_root(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "bar_data".into(),
target_name: "baz_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "bar_svc".into(),
target_name: "baz_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "baz_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "baz_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model.check_use(vec!["c"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec!["c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// / \
/// b c
/// /
/// d
///
/// d: exposes directory /data/foo from self as /data/bar
/// b: exposes directory /data/bar from d as /data/baz
/// a: offers directory /data/baz from b as /data/foobar to c
/// c: uses /data/foobar as /data/hippo
pub async fn test_use_from_niece(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "baz_data".into(),
target_name: "foobar_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "baz_svc".into(),
target_name: "foobar_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("d".to_string()),
source_name: "bar_data".into(),
target_name: "baz_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("d".to_string()),
source_name: "bar_svc".into(),
target_name: "baz_svc".into(),
target: ExposeTarget::Parent,
}))
.add_lazy_child("d")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foobar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "foobar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model.check_use(vec!["c"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec!["c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// / \
/// / \
/// b c
/// / \ / \
/// d e f g
/// \
/// h
///
/// a,d,h: hosts /svc/foo and /data/foo
/// e: uses /svc/foo as /svc/hippo from a, uses /data/foo as /data/hippo from d
/// f: uses /data/foo from d as /data/hippo, uses /svc/foo from h as /svc/hippo
pub async fn test_use_kitchen_sink(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "foo_svc".into(),
target_name: "foo_from_a_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "foo_from_d_data".into(),
target_name: "foo_from_d_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
(
"b",
ComponentDeclBuilder::new_empty_component()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("d".to_string()),
source_name: "foo_from_d_data".into(),
target_name: "foo_from_d_data".into(),
target: OfferTarget::static_child("e".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "foo_from_a_svc".into(),
target_name: "foo_from_a_svc".into(),
target: OfferTarget::static_child("e".to_string()),
dependency_type: DependencyType::Strong,
}))
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("d".to_string()),
source_name: "foo_from_d_data".into(),
target_name: "foo_from_d_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.add_lazy_child("d")
.add_lazy_child("e")
.build(),
),
(
"c",
ComponentDeclBuilder::new_empty_component()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Parent,
source_name: "foo_from_d_data".into(),
target_name: "foo_from_d_data".into(),
target: OfferTarget::static_child("f".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::static_child("g".to_string()),
source_name: "foo_from_h_svc".into(),
target_name: "foo_from_h_svc".into(),
target: OfferTarget::static_child("f".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("f")
.add_lazy_child("g")
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "foo_from_d_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.build(),
),
(
"e",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo_from_d_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "foo_from_a_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
(
"f",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo_from_d_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "foo_from_h_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
(
"g",
ComponentDeclBuilder::new_empty_component()
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("h".to_string()),
source_name: "foo_from_h_svc".into(),
target_name: "foo_from_h_svc".into(),
target: ExposeTarget::Parent,
}))
.add_lazy_child("h")
.build(),
),
(
"h",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "foo_from_h_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(vec!["b", "e"].into(), CheckUse::default_directory(ExpectedResult::Ok))
.await;
model
.check_use(
vec!["b", "e"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(vec!["c", "f"].into(), CheckUse::default_directory(ExpectedResult::Ok))
.await;
model
.check_use(
vec!["c", "f"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// component manager's namespace
/// |
/// a
///
/// a: uses directory /use_from_cm_namespace/data/foo as foo_data
/// a: uses service /use_from_cm_namespace/svc/foo as foo_svc
pub async fn test_use_from_component_manager_namespace(&self) {
let components = vec![(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "foo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
)];
let namespace_capabilities = vec![
CapabilityDecl::Directory(
DirectoryDeclBuilder::new("foo_data")
.path("/use_from_cm_namespace/data/foo")
.build(),
),
CapabilityDecl::Protocol(
ProtocolDeclBuilder::new("foo_svc").path("/use_from_cm_namespace/svc/foo").build(),
),
];
let mut builder = T::new("a", components);
builder.set_namespace_capabilities(namespace_capabilities);
let model = builder.build().await;
model.install_namespace_directory("/use_from_cm_namespace");
model.check_use(vec![].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// component manager's namespace
/// |
/// a
/// \
/// b
///
/// a: offers directory /offer_from_cm_namespace/data/foo from realm as bar_data
/// a: offers service /offer_from_cm_namespace/svc/foo from realm as bar_svc
/// b: uses directory bar_data as /data/hippo
/// b: uses service bar_svc as /svc/hippo
pub async fn test_offer_from_component_manager_namespace(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Parent,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "bar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "bar_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let namespace_capabilities = vec![
CapabilityDecl::Directory(
DirectoryDeclBuilder::new("foo_data")
.path("/offer_from_cm_namespace/data/foo")
.build(),
),
CapabilityDecl::Protocol(
ProtocolDeclBuilder::new("foo_svc")
.path("/offer_from_cm_namespace/svc/foo")
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_namespace_capabilities(namespace_capabilities);
let model = builder.build().await;
model.install_namespace_directory("/offer_from_cm_namespace");
model.check_use(vec!["b"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
///
/// b: uses directory /data/hippo as /data/hippo, but it's not in its realm
/// b: uses service /svc/hippo as /svc/hippo, but it's not in its realm
pub async fn test_use_not_offered(&self) {
let components = vec![
("a", ComponentDeclBuilder::new_empty_component().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "hippo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// / \
/// b c
///
/// a: offers directory /data/hippo from b as /data/hippo, but it's not exposed by b
/// a: offers service /svc/hippo from b as /svc/hippo, but it's not exposed by b
/// c: uses directory /data/hippo as /data/hippo
/// c: uses service /svc/hippo as /svc/hippo
pub async fn test_use_offer_source_not_exposed(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new_empty_component()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source_name: "hippo_data".into(),
source: OfferSource::static_child("b".to_string()),
target_name: "hippo_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source_name: "hippo_svc".into(),
source: OfferSource::static_child("b".to_string()),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
("b", component_decl_with_test_runner()),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "hippo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(
vec!["c"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
model
.check_use(
vec!["c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// b: offers directory /data/hippo from its realm as /data/hippo, but it's not offered by a
/// b: offers service /svc/hippo from its realm as /svc/hippo, but it's not offfered by a
/// c: uses directory /data/hippo as /data/hippo
/// c: uses service /svc/hippo as /svc/hippo
pub async fn test_use_offer_source_not_offered(&self) {
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new_empty_component()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source_name: "hippo_data".into(),
source: OfferSource::Parent,
target_name: "hippo_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source_name: "hippo_svc".into(),
source: OfferSource::Parent,
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "hippo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let test = T::new("a", components).build().await;
test.check_use(
vec!["b", "c"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
test.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// b: uses directory /data/hippo as /data/hippo, but it's exposed to it, not offered
/// b: uses service /svc/hippo as /svc/hippo, but it's exposed to it, not offered
/// c: exposes /data/hippo
/// c: exposes /svc/hippo
pub async fn test_use_from_expose(&self) {
let components = vec![
("a", ComponentDeclBuilder::new_empty_component().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "hippo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("hippo_data").build())
.protocol(ProtocolDeclBuilder::new("hippo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source_name: "hippo_data".into(),
source: ExposeSource::Self_,
target_name: "hippo_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source_name: "hippo_svc".into(),
source: ExposeSource::Self_,
target_name: "hippo_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
///
/// a: exposes "foo" to parent from child
/// b: exposes "foo" to parent from self
pub async fn test_route_protocol_from_expose(&self) {
let expose_decl = ExposeProtocolDecl {
source: ExposeSource::Child("b".into()),
source_name: "foo".into(),
target_name: "foo".into(),
target: ExposeTarget::Parent,
};
let expected_protocol_decl =
ProtocolDecl { name: "foo".into(), source_path: Some("/svc/foo".parse().unwrap()) };
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Protocol(expose_decl.clone()))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo".into(),
target_name: "foo".into(),
target: ExposeTarget::Parent,
}))
.protocol(expected_protocol_decl.clone())
.build(),
),
];
let model = T::new("a", components).build().await;
let root_instance = model.look_up_instance(&vec![].into()).await.expect("root instance");
let expected_source_moniker = AbsoluteMoniker::parse_str("/b").unwrap();
assert_matches!(
route_capability(RouteRequest::ExposeProtocol(expose_decl), &root_instance).await,
Ok((RouteSource::Protocol(
CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C
>::Component {
capability: ComponentCapability::Protocol(protocol_decl),
component,
}), _route)
) if protocol_decl == expected_protocol_decl && component.abs_moniker == expected_source_moniker
);
}
/// a
/// / \
/// b c
///
/// b: exposes directory /data/foo from self as /data/bar to framework (NOT realm)
/// a: offers directory /data/bar from b as /data/baz to c, but it is not exposed via realm
/// c: uses /data/baz as /data/hippo
pub async fn test_use_from_expose_to_framework(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "bar_data".into(),
target_name: "baz_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "bar_svc".into(),
target_name: "baz_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: ExposeTarget::Framework,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "bar_svc".into(),
target: ExposeTarget::Framework,
}))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "baz_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "baz_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(
vec!["c"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
model
.check_use(
vec!["c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
///
/// a: offers directory /data/hippo to b, but a is not executable
/// a: offers service /svc/hippo to b, but a is not executable
/// b: uses directory /data/hippo as /data/hippo, but it's not in its realm
/// b: uses service /svc/hippo as /svc/hippo, but it's not in its realm
pub async fn test_offer_from_non_executable(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new_empty_component()
.directory(DirectoryDeclBuilder::new("hippo_data").build())
.protocol(ProtocolDeclBuilder::new("hippo_svc").build())
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source_name: "hippo_data".into(),
source: OfferSource::Self_,
target_name: "hippo_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source_name: "hippo_svc".into(),
source: OfferSource::Self_,
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "hippo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)),
)
.await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// a: offers directory /data/foo from self with subdir 's1/s2'
/// b: offers directory /data/foo from realm with subdir 's3'
/// c: uses /data/foo as /data/hippo
pub async fn test_use_directory_with_subdir_from_grandparent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Self_,
source_name: "foo_data".into(),
target_name: "foo_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: Some(PathBuf::from("s1/s2")),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Parent,
source_name: "foo_data".into(),
target_name: "foo_data".into(),
target: OfferTarget::static_child("c".to_string()),
rights: Some(*READ_RIGHTS),
subdir: Some(PathBuf::from("s3")),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: Some(PathBuf::from("s4")),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.create_static_file(Path::new("foo/s1/s2/s3/s4/inner"), "hello")
.await
.expect("failed to create file");
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Directory {
path: default_directory_capability(),
file: PathBuf::from("inner"),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// / \
/// b c
///
///
/// b: exposes directory /data/foo from self with subdir 's1/s2'
/// a: offers directory /data/foo from `b` to `c` with subdir 's3'
/// c: uses /data/foo as /data/hippo
pub async fn test_use_directory_with_subdir_from_sibling(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::static_child("b".to_string()),
source_name: "foo_data".into(),
target: OfferTarget::static_child("c".to_string()),
target_name: "foo_data".into(),
rights: Some(*READ_RIGHTS),
subdir: Some(PathBuf::from("s3")),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target: ExposeTarget::Parent,
target_name: "foo_data".into(),
rights: Some(*READ_RIGHTS),
subdir: Some(PathBuf::from("s1/s2")),
}))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.create_static_file(Path::new("foo/s1/s2/s3/inner"), "hello")
.await
.expect("failed to create file");
model
.check_use(
vec!["c"].into(),
CheckUse::Directory {
path: default_directory_capability(),
file: PathBuf::from("inner"),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// c: exposes /data/foo from self
/// b: exposes /data/foo from `c` with subdir `s1/s2`
/// a: exposes /data/foo from `b` with subdir `s3` as /data/hippo
/// use /data/hippo from a's exposed dir
pub async fn test_expose_directory_with_subdir(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("b".to_string()),
source_name: "foo_data".into(),
target_name: "hippo_data".into(),
target: ExposeTarget::Parent,
rights: None,
subdir: Some(PathBuf::from("s3")),
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("c".to_string()),
source_name: "foo_data".into(),
target_name: "foo_data".into(),
target: ExposeTarget::Parent,
rights: None,
subdir: Some(PathBuf::from("s1/s2")),
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "foo_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.create_static_file(Path::new("foo/s1/s2/s3/inner"), "hello")
.await
.expect("failed to create file");
model
.check_use_exposed_dir(
vec![].into(),
CheckUse::Directory {
path: "/hippo_data".try_into().unwrap(),
file: PathBuf::from("inner"),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
pub async fn test_expose_from_self_and_child(&self) {
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Child("c".to_string()),
source_name: "hippo_data".into(),
target_name: "hippo_bar_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("c".to_string()),
source_name: "hippo_svc".into(),
target_name: "hippo_bar_svc".into(),
target: ExposeTarget::Parent,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "hippo_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "hippo_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
model
.check_use_exposed_dir(
vec!["b"].into(),
CheckUse::Directory {
path: "/hippo_bar_data".try_into().unwrap(),
file: PathBuf::from("hippo"),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use_exposed_dir(
vec!["b"].into(),
CheckUse::Protocol {
path: "/hippo_bar_svc".try_into().unwrap(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use_exposed_dir(
vec!["b", "c"].into(),
CheckUse::Directory {
path: "/hippo_data".try_into().unwrap(),
file: PathBuf::from("hippo"),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use_exposed_dir(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: "/hippo_svc".try_into().unwrap(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
pub async fn test_use_not_exposed(&self) {
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
("b", ComponentDeclBuilder::new().add_lazy_child("c").build()),
(
"c",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.protocol(ProtocolDeclBuilder::new("foo_svc").build())
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo_data".into(),
target_name: "hippo_data".into(),
target: ExposeTarget::Parent,
rights: Some(*READ_RIGHTS),
subdir: None,
}))
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo_svc".into(),
target_name: "hippo_svc".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
// Capability is only exposed from "c", so it only be usable from there.
// When trying to open a capability that's not exposed to realm, there's no node for it in the
// exposed dir, so no routing takes place.
model
.check_use_exposed_dir(
vec!["b"].into(),
CheckUse::Directory {
path: "/hippo_data".try_into().unwrap(),
file: PathBuf::from("hippo"),
expected_res: ExpectedResult::Err(zx::Status::NOT_FOUND),
},
)
.await;
model
.check_use_exposed_dir(
vec!["b"].into(),
CheckUse::Protocol {
path: "/hippo_svc".try_into().unwrap(),
expected_res: ExpectedResult::Err(zx::Status::NOT_FOUND),
},
)
.await;
model
.check_use_exposed_dir(
vec!["b", "c"].into(),
CheckUse::Directory {
path: "/hippo_data".try_into().unwrap(),
file: PathBuf::from("hippo"),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use_exposed_dir(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: "/hippo_svc".try_into().unwrap(),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// (cm)
/// |
/// a
///
/// a: uses an invalid service from the component manager.
pub async fn test_invalid_use_from_component_manager(&self) {
let components = vec![(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "invalid".into(),
target_path: CapabilityPath::try_from("/svc/valid").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
)];
// Try and use the service. We expect a failure.
let model = T::new("a", components).build().await;
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: CapabilityPath::try_from("/svc/valid").unwrap(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// (cm)
/// |
/// a
/// |
/// b
///
/// a: offers an invalid service from the component manager to "b".
/// b: attempts to use the service
pub async fn test_invalid_offer_from_component_manager(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source_name: "invalid".into(),
source: OfferSource::Parent,
target_name: "valid".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "valid".into(),
target_path: CapabilityPath::try_from("/svc/valid").unwrap(),
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
// Try and use the service. We expect a failure.
let model = T::new("a", components).build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: CapabilityPath::try_from("/svc/valid").unwrap(),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
///
/// b: uses framework events "started", and "capability_requested"
pub async fn test_use_event_from_framework(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "capability_requested".into(),
target_name: "capability_requested".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "started".into(),
target_name: "started".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new("capability_requested".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new("started".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
///
/// a; attempts to offer event "capability_requested" to b.
pub async fn test_can_offer_capability_requested_event(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "capability_requested".into(),
target_name: "capability_requested_on_a".into(),
target: OfferTarget::static_child("b".to_string()),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Parent,
source_name: "capability_requested_on_a".into(),
target_name: "capability_requested_from_parent".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new(
"capability_requested_from_parent".into(),
EventMode::Sync,
),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
///
/// a: uses framework event "started" and offers to b as "started_on_a"
/// b: uses framework event "started_on_a" as "started"
pub async fn test_use_event_from_parent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "started".into(),
target_name: "started_on_a".into(),
target: OfferTarget::static_child("b".to_string()),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Parent,
source_name: "started_on_a".into(),
target_name: "started".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new("started".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
}
/// a
/// \
/// b
/// \
/// c
///
/// a: uses framework event "started" and offers to b as "started_on_a"
/// a: uses framework event "stopped" and offers to b as "stopped_on_a"
/// b: offers realm event "started_on_a" to c
/// b: offers realm event "destroyed" from framework
/// c: uses realm event "started_on_a"
/// c: uses realm event "destroyed"
/// c: uses realm event "stopped_on_a" but fails to do so
pub async fn test_use_event_from_grandparent(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "started".into(),
target_name: "started_on_a".into(),
target: OfferTarget::static_child("b".to_string()),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "stopped".into(),
target_name: "stopped_on_b".into(),
target: OfferTarget::static_child("b".to_string()),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Parent,
source_name: "started_on_a".into(),
target_name: "started_on_a".into(),
target: OfferTarget::static_child("c".to_string()),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "destroyed".into(),
target_name: "destroyed".into(),
target: OfferTarget::static_child("c".to_string()),
filter: Some(hashmap!{"path".to_string() => DictionaryValue::Str("/diagnostics".to_string())}),
mode: cm_rust::EventMode::Sync,
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Parent,
source_name: "started_on_a".into(),
target_name: "started".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Parent,
source_name: "destroyed".into(),
target_name: "destroyed".into(),
filter: Some(hashmap!{"path".to_string() => DictionaryValue::Str("/diagnostics".to_string())}),
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Parent,
source_name: "stopped_on_a".into(),
target_name: "stopped".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
dependency_type: DependencyType::Strong,
}))
.build(),
)];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Event {
request: EventSubscription::new("started".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Event {
request: EventSubscription::new("destroyed".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Event {
request: EventSubscription::new("stopped".into(), EventMode::Sync),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// |
/// b
/// / \
/// c d
///
/// a: offer framework event "directory_ready" with filters "/foo", "/bar", "/baz" to b
/// b: uses realm event "directory_ready" with filters "/foo"
/// b: offers realm event "capabilty_ready" with filters "/foo", "/bar" to c, d
/// c: uses realm event "directory_ready" with filters "/foo", "/bar"
/// d: uses realm event "directory_ready" with filters "/baz" (fails)
pub async fn test_event_filter_routing(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Framework,
source_name: "directory_ready".into(),
target_name: "directory_ready".into(),
target: OfferTarget::static_child("b".to_string()),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::StrVec(vec![
"foo".to_string(), "bar".to_string(), "baz".to_string()
])
}),
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready_foo".into(),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::Str("foo".into()),
}),
mode: cm_rust::EventMode::Sync,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("d".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready".into(),
target: OfferTarget::static_child("c".to_string()),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::StrVec(vec![
"foo".to_string(), "bar".to_string()
])
}),
mode: cm_rust::EventMode::Sync,
}))
.offer(OfferDecl::Event(OfferEventDecl {
source: OfferSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready".into(),
target: OfferTarget::static_child("d".to_string()),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::StrVec(vec![
"foo".to_string(), "bar".to_string()
])
}),
mode: cm_rust::EventMode::Sync,
}))
.add_lazy_child("c")
.add_lazy_child("d")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready_foo_bar".into(),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::StrVec(vec![
"foo".to_string(), "bar".to_string()
])
}),
mode: cm_rust::EventMode::Sync,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready_baz".into(),
filter: Some(hashmap! {
"name".to_string() => DictionaryValue::Str("baz".into()),
}),
mode: cm_rust::EventMode::Sync,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new("directory_ready_foo".into(), EventMode::Sync),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Event {
request: EventSubscription::new(
"directory_ready_foo_bar".into(),
EventMode::Sync,
),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "d"].into(),
CheckUse::Event {
request: EventSubscription::new("directory_ready_baz".into(), EventMode::Sync),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
///
/// a: uses registered built-in event "directory_ready"
/// b: uses unregistered event "unregistered" as a built-in; should fail
pub async fn test_use_builtin_event(&self) {
let components = vec![(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "directory_ready".into(),
target_name: "directory_ready".into(),
filter: None,
mode: cm_rust::EventMode::Async,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "unregistered".into(),
target_name: "unregistered".into(),
filter: None,
mode: cm_rust::EventMode::Async,
}))
.build(),
)];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![
CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
}),
CapabilityDecl::Event(EventDecl { name: "directory_ready".into() }),
]);
let model = builder.build().await;
model
.check_use(
vec![].into(),
CheckUse::Event {
request: EventSubscription::new("directory_ready".into(), EventMode::Async),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec![].into(),
CheckUse::Event {
request: EventSubscription::new("unregistered".into(), EventMode::Async),
expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE),
},
)
.await;
}
/// a
/// \
/// b
///
/// b: uses service /svc/hippo as /svc/hippo.
/// a: provides b with the service but policy prevents it.
pub async fn test_use_protocol_denied_by_capability_policy(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("hippo_svc").build())
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()),
source_name: CapabilityName::from("hippo_svc"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
HashSet::new(),
);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
///
/// b: uses directory /data/foo as /data/bar.
/// a: provides b with the directory but policy prevents it.
pub async fn test_use_directory_with_alias_denied_by_capability_policy(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.directory(DirectoryDeclBuilder::new("foo_data").build())
.offer(OfferDecl::Directory(OfferDirectoryDecl {
source: OfferSource::Self_,
source_name: "foo_data".into(),
target_name: "bar_data".into(),
target: OfferTarget::static_child("b".to_string()),
rights: Some(*READ_RIGHTS),
subdir: None,
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "bar_data".into(),
target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
rights: *READ_RIGHTS,
subdir: None,
dependency_type: DependencyType::Strong,
}))
.build(),
),
];
let mut builder = T::new("a", components);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()),
source_name: CapabilityName::from("foo_data"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Directory,
},
HashSet::new(),
);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::default_directory(ExpectedResult::Err(zx::Status::ACCESS_DENIED)),
)
.await;
}
/// a
/// \
/// b
/// \
/// c
/// c: uses service /svc/hippo as /svc/hippo.
/// b: uses service /svc/hippo as /svc/hippo.
/// a: provides b with the service policy allows it.
/// b: provides c with the service policy does not allow it.
pub async fn test_use_protocol_partial_chain_allowed_by_capability_policy(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("hippo_svc").build())
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.add_lazy_child("c")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert(AllowlistEntry::Exact(AbsoluteMoniker::from(vec!["b"])));
let mut builder = T::new("a", components);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()),
source_name: CapabilityName::from("hippo_svc"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
/// / \
/// c d
/// b: provides d with the service policy allows denies it.
/// b: provides c with the service policy allows it.
/// c: uses service /svc/hippo as /svc/hippo.
/// Tests component provided caps in the middle of a path
pub async fn test_use_protocol_component_provided_capability_policy(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("hippo_svc").build())
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("c".to_string()),
dependency_type: DependencyType::Strong,
}))
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "hippo_svc".into(),
target_name: "hippo_svc".into(),
target: OfferTarget::static_child("d".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("c")
.add_lazy_child("d")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "hippo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert(AllowlistEntry::Exact(AbsoluteMoniker::from(vec!["b"])));
allowlist.insert(AllowlistEntry::Exact(AbsoluteMoniker::from(vec!["b", "c"])));
let mut builder = T::new("a", components);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()),
source_name: CapabilityName::from("hippo_svc"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "d"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// component manager's namespace
/// |
/// a
///
/// a: uses service /use_from_cm_namespace/svc/foo as foo_svc
pub async fn test_use_from_component_manager_namespace_denied_by_policy(&self) {
let components = vec![(
"a",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "foo_svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
)];
let namespace_capabilities = vec![CapabilityDecl::Protocol(
ProtocolDeclBuilder::new("foo_svc").path("/use_from_cm_namespace/svc/foo").build(),
)];
let mut builder = T::new("a", components);
builder.set_namespace_capabilities(namespace_capabilities);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentManager,
source_name: CapabilityName::from("foo_svc"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
HashSet::new(),
);
let model = builder.build().await;
model.install_namespace_directory("/use_from_cm_namespace");
model
.check_use(
vec![].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
///
/// b: uses framework events "started", and "capability_requested".
/// Capability policy denies the route from being allowed for started but
/// not for capability_requested.
pub async fn test_use_event_from_framework_denied_by_capability_policy(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target: OfferTarget::static_child("b".to_string()),
dependency_type: DependencyType::Strong,
}))
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "fuchsia.sys2.EventSource".try_into().unwrap(),
target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(),
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "capability_requested".into(),
target_name: "capability_requested".into(),
filter: None,
mode: cm_rust::EventMode::Async,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "started".into(),
target_name: "started".into(),
filter: None,
mode: cm_rust::EventMode::Async,
}))
.use_(UseDecl::Event(UseEventDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Framework,
source_name: "resolved".into(),
target_name: "resolved".into(),
filter: None,
mode: cm_rust::EventMode::Sync,
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert(AllowlistEntry::Exact(AbsoluteMoniker::from(vec!["b"])));
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.sys2.EventSource".into(),
source_path: None,
})]);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![
"b",
])),
source_name: CapabilityName::from("started"),
source: CapabilityAllowlistSource::Framework,
capability: CapabilityTypeName::Event,
},
HashSet::new(),
);
builder.add_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![
"b",
])),
source_name: CapabilityName::from("capability_requested"),
source: CapabilityAllowlistSource::Framework,
capability: CapabilityTypeName::Event,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new(
"capability_requested".into(),
EventMode::Async,
),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b"].into(),
CheckUse::Event {
request: EventSubscription::new("started".into(), EventMode::Async),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await
}
/// a
/// \
/// b
/// / \
/// c d
/// b: provides d with the service policy allows denies it.
/// b: provides c with the service policy allows it.
/// c: uses service svc_allowed as /svc/hippo.
/// a: offers services using environment.
/// Tests component provided caps in the middle of a path
pub async fn test_use_protocol_component_provided_debug_capability_policy_at_root_from_self(
&self,
) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("svc_allowed").build())
.protocol(ProtocolDeclBuilder::new("svc_not_allowed").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm)
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
source: RegistrationSource::Self_,
},
))
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
source: RegistrationSource::Self_,
},
)),
)
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env_a"))
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("c"))
.add_child(ChildDeclBuilder::new_lazy_child("d"))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_not_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert((AbsoluteMoniker::root(), "env_a".to_owned()));
let mut builder = T::new("a", components);
builder.add_debug_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()),
source_name: CapabilityName::from("svc_allowed"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "d"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
/// / \
/// c d
/// b: provides d with the service policy allows denies it.
/// b: provides c with the service policy allows it.
/// c: uses service svc_allowed as /svc/hippo.
/// b: offers services using environment.
/// Tests component provided debug caps in the middle of a path
pub async fn test_use_protocol_component_provided_debug_capability_policy_from_self(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b"))
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("svc_allowed").build())
.protocol(ProtocolDeclBuilder::new("svc_not_allowed").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm)
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
source: RegistrationSource::Self_,
},
))
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
source: RegistrationSource::Self_,
},
)),
)
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env_b"))
.add_child(ChildDeclBuilder::new_lazy_child("d").environment("env_b"))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_not_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert((AbsoluteMoniker::from(vec!["b"]), "env_b".to_owned()));
let mut builder = T::new("a", components);
builder.add_debug_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![
"b",
])),
source_name: CapabilityName::from("svc_allowed"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "d"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
/// / \ \
/// c d e
/// b: provides e with the service policy which denies it.
/// b: provides c with the service policy which allows it.
/// c: uses service svc_allowed as /svc/hippo.
/// b: offers services using environment.
/// d: exposes the service to b
/// Tests component provided debug caps in the middle of a path
pub async fn test_use_protocol_component_provided_debug_capability_policy_from_child(&self) {
let expose_decl_svc_allowed = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
target: ExposeTarget::Parent,
};
let expose_decl_svc_not_allowed = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
target: ExposeTarget::Parent,
};
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.add_environment(
EnvironmentDeclBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm)
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
source: RegistrationSource::Child("d".to_string()),
},
))
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
source: RegistrationSource::Child("d".to_string()),
},
)),
)
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env_b"))
.add_child(ChildDeclBuilder::new_lazy_child("d"))
.add_child(ChildDeclBuilder::new_lazy_child("e").environment("env_b"))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("svc_allowed").build())
.expose(cm_rust::ExposeDecl::Protocol(expose_decl_svc_allowed))
.protocol(ProtocolDeclBuilder::new("svc_not_allowed").build())
.expose(cm_rust::ExposeDecl::Protocol(expose_decl_svc_not_allowed))
.build(),
),
(
"e",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_not_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert((AbsoluteMoniker::from(vec!["b"]), "env_b".to_owned()));
let mut builder = T::new("a", components);
builder.add_debug_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![
"b", "d",
])),
source_name: CapabilityName::from("svc_allowed"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "e"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// \
/// b
/// / \
/// c d
/// \
/// e
/// b: defines an environment with a debug protocols exposed by d, 1 allowed and 1 not allowed
/// b: provides c with the environment
/// c: uses service svc_allowed as /svc/hippo.
/// c: uses service svc_not_allowed as /svc/hippo_not_allowed
/// d: exposes the service to b
/// e: exposes the service to d
/// Tests component provided debug caps in the middle of a path
pub async fn test_use_protocol_component_provided_debug_capability_policy_from_grandchild(
&self,
) {
let expose_decl_svc_allowed = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
target: ExposeTarget::Parent,
};
let expose_decl_svc_not_allowed = ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
target: ExposeTarget::Parent,
};
let components = vec![
("a", ComponentDeclBuilder::new().add_lazy_child("b").build()),
(
"b",
ComponentDeclBuilder::new()
.add_environment(
EnvironmentDeclBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm)
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
source: RegistrationSource::Child("d".to_string()),
},
))
.add_debug_registration(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
source: RegistrationSource::Child("d".to_string()),
},
)),
)
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env_b"))
.add_child(ChildDeclBuilder::new_lazy_child("d"))
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Debug,
source_name: "svc_not_allowed".into(),
target_path: CapabilityPath::try_from("/svc/hippo_not_allowed").unwrap(),
}))
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.expose(cm_rust::ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("e".into()),
source_name: "svc_allowed".into(),
target_name: "svc_allowed".into(),
target: ExposeTarget::Parent,
}))
.expose(cm_rust::ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Child("e".into()),
source_name: "svc_not_allowed".into(),
target_name: "svc_not_allowed".into(),
target: ExposeTarget::Parent,
}))
.add_lazy_child("e".into())
.build(),
),
(
"e",
ComponentDeclBuilder::new()
.protocol(ProtocolDeclBuilder::new("svc_allowed").build())
.expose(cm_rust::ExposeDecl::Protocol(expose_decl_svc_allowed))
.protocol(ProtocolDeclBuilder::new("svc_not_allowed").build())
.expose(cm_rust::ExposeDecl::Protocol(expose_decl_svc_not_allowed))
.build(),
),
];
let mut allowlist = HashSet::new();
allowlist.insert((AbsoluteMoniker::from(vec!["b"]), "env_b".to_owned()));
let mut builder = T::new("a", components);
builder.add_debug_capability_policy(
CapabilityAllowlistKey {
source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![
"b", "d", "e",
])),
source_name: CapabilityName::from("svc_allowed"),
source: CapabilityAllowlistSource::Self_,
capability: CapabilityTypeName::Protocol,
},
allowlist,
);
let model = builder.build().await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: default_service_capability(),
expected_res: ExpectedResult::Ok,
},
)
.await;
model
.check_use(
vec!["b", "c"].into(),
CheckUse::Protocol {
path: "/svc/hippo_not_allowed".try_into().unwrap(),
expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED),
},
)
.await;
}
/// a
/// /
/// b
///
/// a: offer to b from self
/// b: use from parent
pub async fn test_route_service_from_parent(&self) {
let use_decl = UseServiceDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "foo".into(),
target_path: CapabilityPath::try_from("/foo").unwrap(),
};
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Service(OfferServiceDecl {
source: OfferSource::Self_,
source_name: "foo".into(),
target_name: "foo".into(),
target: OfferTarget::static_child("b".to_string()),
}))
.service(ServiceDecl {
name: "foo".into(),
source_path: Some("/svc/foo".try_into().unwrap()),
})
.add_lazy_child("b")
.build(),
),
("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()),
];
let model = T::new("a", components).build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let a_component =
model.look_up_instance(&AbsoluteMoniker::root()).await.expect("root instance");
let (source, _route) = route_capability(RouteRequest::UseService(use_decl), &b_component)
.await
.expect("failed to route service");
match source {
RouteSource::Service(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Service(ServiceDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("foo".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/foo".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// /
/// b
///
/// a: use from #b
/// b: expose to parent from self
pub async fn test_route_service_from_child(&self) {
let use_decl = UseServiceDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Child("b".to_string()),
source_name: "foo".into(),
target_path: CapabilityPath::try_from("/foo").unwrap(),
};
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.use_(use_decl.clone().into())
.add_lazy_child("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.service(ServiceDecl {
name: "foo".into(),
source_path: Some("/svc/foo".try_into().unwrap()),
})
.expose(ExposeDecl::Service(ExposeServiceDecl {
source: ExposeSource::Self_,
source_name: "foo".into(),
target_name: "foo".into(),
target: ExposeTarget::Parent,
}))
.build(),
),
];
let model = T::new("a", components).build().await;
let a_component =
model.look_up_instance(&AbsoluteMoniker::root()).await.expect("root instance");
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let (source, _route) = route_capability(RouteRequest::UseService(use_decl), &a_component)
.await
.expect("failed to route service");
match source {
RouteSource::Service(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Service(ServiceDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("foo".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/foo".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &b_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// / \
/// b c
///
/// a: offer to b from child c
/// b: use from parent
/// c: expose from self
pub async fn test_route_service_from_sibling(&self) {
let use_decl = UseServiceDecl {
dependency_type: DependencyType::Strong,
source: UseSource::Parent,
source_name: "foo".into(),
target_path: CapabilityPath::try_from("/foo").unwrap(),
};
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.offer(OfferDecl::Service(OfferServiceDecl {
source: OfferSource::static_child("c".into()),
source_name: "foo".into(),
target_name: "foo".into(),
target: OfferTarget::static_child("b".to_string()),
}))
.add_lazy_child("b")
.add_lazy_child("c")
.build(),
),
("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()),
(
"c",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Service(ExposeServiceDecl {
source: ExposeSource::Self_,
source_name: "foo".into(),
target_name: "foo".into(),
target: ExposeTarget::Parent,
}))
.service(ServiceDecl {
name: "foo".into(),
source_path: Some("/svc/foo".try_into().unwrap()),
})
.build(),
),
];
let model = T::new("a", components).build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let c_component = model.look_up_instance(&vec!["c"].into()).await.expect("c instance");
let (source, _route) = route_capability(RouteRequest::UseService(use_decl), &b_component)
.await
.expect("failed to route service");
// Verify this source comes from `c`.
match source {
RouteSource::Service(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Service(ServiceDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("foo".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/foo".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &c_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// \
/// b
///
/// a: declares runner "elf" with service "/svc/runner" from "self".
/// a: registers runner "elf" from self in environment as "hobbit".
/// b: uses runner "hobbit".
pub async fn test_route_runner_from_parent_environment(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "elf".into(),
source: RegistrationSource::Self_,
target_name: "hobbit".into(),
})
.build(),
)
.runner(RunnerDecl {
name: "elf".into(),
source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()),
})
.build(),
),
("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let model = T::new("a", components).build().await;
let a_component = model.look_up_instance(&vec![].into()).await.expect("a instance");
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let (source, _route) =
route_capability(RouteRequest::Runner("hobbit".into()), &b_component)
.await
.expect("failed to route runner");
// Verify this source comes from `a`.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Runner(RunnerDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("elf".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/runner".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// \
/// b
/// \
/// c
///
/// a: declares runner "elf" as service "/svc/runner" from self.
/// a: offers runner "elf" from self to "b" as "dwarf".
/// b: registers runner "dwarf" from realm in environment as "hobbit".
/// c: uses runner "hobbit".
pub async fn test_route_runner_from_grandparent_environment(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_lazy_child("b")
.offer(OfferDecl::Runner(OfferRunnerDecl {
source: OfferSource::Self_,
source_name: CapabilityName("elf".to_string()),
target: OfferTarget::static_child("b".to_string()),
target_name: CapabilityName("dwarf".to_string()),
}))
.runner(RunnerDecl {
name: "elf".into(),
source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()),
})
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "dwarf".into(),
source: RegistrationSource::Parent,
target_name: "hobbit".into(),
})
.build(),
)
.build(),
),
("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let model = T::new("a", components).build().await;
let a_component = model.look_up_instance(&vec![].into()).await.expect("a instance");
let c_component = model.look_up_instance(&vec!["b", "c"].into()).await.expect("c instance");
let (source, _route) =
route_capability(RouteRequest::Runner("hobbit".into()), &c_component)
.await
.expect("failed to route runner");
// Verify this source comes from `a`.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Runner(RunnerDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("elf".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/runner".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// / \
/// b c
///
/// a: registers runner "dwarf" from "b" in environment as "hobbit".
/// b: exposes runner "elf" as service "/svc/runner" from self as "dwarf".
/// c: uses runner "hobbit".
pub async fn test_route_runner_from_sibling_environment(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_lazy_child("b")
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "dwarf".into(),
source: RegistrationSource::Child("b".into()),
target_name: "hobbit".into(),
})
.build(),
)
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Runner(ExposeRunnerDecl {
source: ExposeSource::Self_,
source_name: CapabilityName("elf".to_string()),
target: ExposeTarget::Parent,
target_name: CapabilityName("dwarf".to_string()),
}))
.runner(RunnerDecl {
name: "elf".into(),
source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()),
})
.build(),
),
("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let model = T::new("a", components).build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let c_component = model.look_up_instance(&vec!["c"].into()).await.expect("c instance");
let (source, _route) =
route_capability(RouteRequest::Runner("hobbit".into()), &c_component)
.await
.expect("failed to route runner");
// Verify this source comes from `b`.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Runner(RunnerDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("elf".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/runner".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &b_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// \
/// b
/// \
/// c
///
/// a: declares runner "elf" as service "/svc/runner" from self.
/// a: registers runner "elf" from realm in environment as "hobbit".
/// b: creates environment extending from realm.
/// c: uses runner "hobbit".
pub async fn test_route_runner_from_inherited_environment(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "elf".into(),
source: RegistrationSource::Self_,
target_name: "hobbit".into(),
})
.build(),
)
.runner(RunnerDecl {
name: "elf".into(),
source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()),
})
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("c").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.build(),
)
.build(),
),
("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let model = T::new("a", components).build().await;
let a_component = model.look_up_instance(&vec![].into()).await.expect("a instance");
let c_component = model.look_up_instance(&vec!["b", "c"].into()).await.expect("c instance");
let (source, _route) =
route_capability(RouteRequest::Runner("hobbit".into()), &c_component)
.await
.expect("failed to route runner");
// Verify this source comes from `a`.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Component {
capability: ComponentCapability::Runner(RunnerDecl { name, source_path }),
component,
}) => {
assert_eq!(name, CapabilityName("elf".into()));
assert_eq!(
source_path.expect("missing source path"),
"/svc/runner".parse::<CapabilityPath>().unwrap()
);
assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component));
}
_ => panic!("bad capability source"),
};
}
/// a
/// \
/// b
///
/// a: declares runner "elf" with service "/svc/runner" from "self".
/// a: registers runner "elf" from self in environment as "hobbit".
/// b: uses runner "hobbit". Fails because "hobbit" was not in environment.
pub async fn test_route_runner_from_environment_not_found(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "elf".into(),
source: RegistrationSource::Self_,
target_name: "dwarf".into(),
})
.build(),
)
.runner(RunnerDecl {
name: "elf".into(),
source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()),
})
.build(),
),
("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let model = T::new("a", components).build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let route_result =
route_capability(RouteRequest::Runner("hobbit".into()), &b_component).await;
assert_matches!(
route_result,
Err(RoutingError::UseFromEnvironmentNotFound {
moniker,
capability_type,
capability_name,
}
)
if moniker == *b_component.abs_moniker() &&
capability_type == "runner" &&
capability_name == CapabilityName("hobbit".to_string())
);
}
/// a
/// \
/// b
///
/// a: registers built-in runner "elf" from realm in environment as "hobbit".
/// b: uses runner "hobbit".
pub async fn test_route_builtin_runner(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "elf".into(),
source: RegistrationSource::Parent,
target_name: "hobbit".into(),
})
.build(),
)
.build(),
),
("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Runner(RunnerDecl {
name: "elf".into(),
source_path: None,
})]);
builder.register_mock_builtin_runner("elf");
let model = builder.build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let (source, _route) =
route_capability(RouteRequest::Runner("hobbit".into()), &b_component)
.await
.expect("failed to route runner");
// Verify this is a built-in source.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Builtin {
capability: InternalCapability::Runner(name),
..
}) => {
assert_eq!(name, CapabilityName("elf".into()));
}
_ => panic!("bad capability source"),
};
}
/// a
///
/// a: uses built-in runner "elf" from the root environment.
pub async fn test_route_builtin_runner_from_root_env(&self) {
let components = vec![("a", ComponentDeclBuilder::new().build())];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Runner(RunnerDecl {
name: "elf".into(),
source_path: None,
})]);
builder.register_mock_builtin_runner("elf");
let model = builder.build().await;
let a_component = model.look_up_instance(&vec![].into()).await.expect("a instance");
let (source, _route) = route_capability(RouteRequest::Runner("elf".into()), &a_component)
.await
.expect("failed to route runner");
// Verify this is a built-in source.
match source {
RouteSource::Runner(CapabilitySourceInterface::<
<<T as RoutingTestModelBuilder>::Model as RoutingTestModel>::C,
>::Builtin {
capability: InternalCapability::Runner(name),
..
}) => {
assert_eq!(name, CapabilityName("elf".into()));
}
_ => panic!("bad capability source"),
};
}
/// a
/// \
/// b
///
/// a: registers built-in runner "elf" from realm in environment as "hobbit". The ELF runner is
/// registered in the root environment, but not declared as a built-in capability.
/// b: uses runner "hobbit"; should fail.
pub async fn test_route_builtin_runner_not_found(&self) {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build())
.add_environment(
EnvironmentDeclBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.add_runner(RunnerRegistration {
source_name: "elf".into(),
source: RegistrationSource::Parent,
target_name: "hobbit".into(),
})
.build(),
)
.build(),
),
("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()),
];
let mut builder = T::new("a", components);
builder.register_mock_builtin_runner("elf");
let model = builder.build().await;
let b_component = model.look_up_instance(&vec!["b"].into()).await.expect("b instance");
let route_result =
route_capability(RouteRequest::Runner("hobbit".into()), &b_component).await;
assert_matches!(
route_result,
Err(RoutingError::RegisterFromComponentManagerNotFound {
capability_id,
}
)
if capability_id == "elf".to_string()
);
}
/// a
///
/// a: Attempts to use unregistered runner "hobbit" from the root environment.
/// The runner is provided as a built-in capability, but not registered in
/// the root environment.
pub async fn test_route_builtin_runner_from_root_env_registration_not_found(&self) {
let components = vec![("a", ComponentDeclBuilder::new().build())];
let mut builder = T::new("a", components);
builder.set_builtin_capabilities(vec![CapabilityDecl::Runner(RunnerDecl {
name: "hobbit".into(),
source_path: None,
})]);
let model = builder.build().await;
let a_component = model.look_up_instance(&vec![].into()).await.expect("a instance");
let route_result =
route_capability(RouteRequest::Runner("hobbit".into()), &a_component).await;
assert_matches!(
route_result,
Err(RoutingError::UseFromEnvironmentNotFound {
moniker,
capability_type,
capability_name,
}
)
if moniker == *a_component.abs_moniker()
&& capability_type == "runner".to_string()
&& capability_name == CapabilityName("hobbit".into())
);
}
}