blob: 2b859914886a8e2b60e7e3b6877cb45be7f88e63 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::model::{
component::{
manager::ComponentManagerInstance, ComponentInstance, ExtendedInstance,
WeakExtendedInstance,
},
resolver::{Resolver, ResolverRegistry},
},
::routing::environment::{DebugRegistry, EnvironmentExtends, RunnerRegistry},
::routing::resolving::{ComponentAddress, ResolvedComponent, ResolverError},
async_trait::async_trait,
cm_rust::EnvironmentDecl,
fidl_fuchsia_component_decl as fdecl,
std::{sync::Arc, time::Duration},
tracing::error,
};
#[cfg(test)]
use std::sync::Weak;
/// A realm's environment, populated from a component's [`EnvironmentDecl`].
/// An environment defines intrinsic behaviors of a component's realm. Components
/// can define an environment, but do not interact with it directly.
///
/// [`EnvironmentDecl`]: fidl_fuchsia_sys2::EnvironmentDecl
#[derive(Debug)]
pub struct Environment {
env: routing::environment::Environment<ComponentInstance>,
/// The resolvers in this environment, mapped to URL schemes.
resolver_registry: ResolverRegistry,
/// The deadline for runners to respond to `ComponentController.Stop` calls.
stop_timeout: Duration,
}
pub const DEFAULT_STOP_TIMEOUT: Duration = Duration::from_secs(5);
impl Environment {
/// Creates a new empty environment parented to component manager.
#[cfg(test)]
pub fn empty() -> Environment {
Environment {
env: routing::environment::Environment::new(
None,
WeakExtendedInstance::AboveRoot(Weak::new()),
EnvironmentExtends::None,
RunnerRegistry::default(),
DebugRegistry::default(),
),
resolver_registry: ResolverRegistry::new(),
stop_timeout: DEFAULT_STOP_TIMEOUT,
}
}
/// Creates a new root environment with a resolver registry, parented to component manager.
pub fn new_root(
top_instance: &Arc<ComponentManagerInstance>,
runner_registry: RunnerRegistry,
resolver_registry: ResolverRegistry,
debug_registry: DebugRegistry,
) -> Environment {
Environment {
env: routing::environment::Environment::new(
None,
WeakExtendedInstance::AboveRoot(Arc::downgrade(top_instance)),
EnvironmentExtends::None,
runner_registry,
debug_registry,
),
resolver_registry,
stop_timeout: DEFAULT_STOP_TIMEOUT,
}
}
/// Creates an environment from `env_decl`, using `parent` as the parent realm.
pub fn from_decl(parent: &Arc<ComponentInstance>, env_decl: &EnvironmentDecl) -> Environment {
Environment {
env: routing::environment::Environment::new(
Some(env_decl.name.clone()),
WeakExtendedInstance::Component(parent.into()),
env_decl.extends.into(),
RunnerRegistry::from_decl(&env_decl.runners),
env_decl.debug_capabilities.clone().into(),
),
resolver_registry: ResolverRegistry::from_decl(&env_decl.resolvers, parent),
stop_timeout: match env_decl.stop_timeout_ms {
Some(timeout) => Duration::from_millis(timeout.into()),
None => match env_decl.extends {
fdecl::EnvironmentExtends::Realm => parent.environment.stop_timeout(),
fdecl::EnvironmentExtends::None => {
panic!("EnvironmentDecl is missing stop_timeout");
}
},
},
}
}
/// Creates a new environment with `parent` as the parent.
pub fn new_inheriting(parent: &Arc<ComponentInstance>) -> Environment {
Environment {
env: routing::environment::Environment::new(
None,
WeakExtendedInstance::Component(parent.into()),
EnvironmentExtends::Realm,
RunnerRegistry::default(),
DebugRegistry::default(),
),
resolver_registry: ResolverRegistry::new(),
stop_timeout: parent.environment.stop_timeout(),
}
}
pub fn stop_timeout(&self) -> Duration {
self.stop_timeout
}
pub fn environment(&self) -> &routing::environment::Environment<ComponentInstance> {
&self.env
}
}
#[async_trait]
impl Resolver for Environment {
async fn resolve(
&self,
component_address: &ComponentAddress,
) -> Result<ResolvedComponent, ResolverError> {
let parent = self.env.parent().upgrade().map_err(|_| {
error!("error getting the component that created the environment");
ResolverError::SchemeNotRegistered
})?;
match self.resolver_registry.resolve(component_address).await {
Err(ResolverError::SchemeNotRegistered) => match self.env.extends() {
EnvironmentExtends::Realm => match parent {
ExtendedInstance::Component(parent) => {
parent.environment.resolve(component_address).await
}
ExtendedInstance::AboveRoot(_) => {
unreachable!("root env can't extend")
}
},
EnvironmentExtends::None => Err(ResolverError::SchemeNotRegistered),
},
result => result,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::{
component::StartReason,
context::ModelContext,
model::{Model, ModelParams},
structured_dict::ComponentInput,
testing::mocks::MockResolver,
token::InstanceRegistry,
},
::routing::{environment::DebugRegistration, policy::PolicyError},
assert_matches::assert_matches,
cm_config::{
AllowlistEntryBuilder, CapabilityAllowlistSource, DebugCapabilityAllowlistEntry,
DebugCapabilityKey, RuntimeConfig, SecurityPolicy,
},
cm_rust::{RegistrationSource, RunnerRegistration},
cm_rust_testing::{
ChildBuilder, CollectionBuilder, ComponentDeclBuilder, EnvironmentBuilder,
},
cm_types::Name,
errors::{ActionError, ModelError, ResolveActionError},
fidl_fuchsia_component as fcomponent,
maplit::hashmap,
moniker::{Moniker, MonikerBase},
std::collections::{HashMap, HashSet},
};
#[fuchsia::test]
async fn test_from_decl() {
let component = ComponentInstance::new_root(
Environment::empty(),
Arc::new(ModelContext::new_for_test()),
Weak::new(),
"test:///root".to_string(),
)
.await;
let environment = Environment::from_decl(
&component,
&EnvironmentBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::None)
.stop_timeout(1234)
.build(),
);
assert_matches!(environment.env.parent(), WeakExtendedInstance::Component(_));
let environment = Environment::from_decl(
&component,
&EnvironmentBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::Realm)
.build(),
);
assert_matches!(environment.env.parent(), WeakExtendedInstance::Component(_));
let environment = Environment::from_decl(
&component,
&EnvironmentBuilder::new()
.name("env")
.extends(fdecl::EnvironmentExtends::None)
.stop_timeout(1234)
.debug(cm_rust::DebugRegistration::Protocol(cm_rust::DebugProtocolRegistration {
source_name: "source_name".parse().unwrap(),
target_name: "target_name".parse().unwrap(),
source: RegistrationSource::Parent,
}))
.build(),
);
let expected_debug_capability: HashMap<Name, DebugRegistration> = hashmap! {
"target_name".parse().unwrap() =>
DebugRegistration {
source_name: "source_name".parse().unwrap(),
source: RegistrationSource::Parent,
}
};
assert_eq!(environment.env.debug_registry().debug_capabilities, expected_debug_capability);
}
#[fuchsia::test]
async fn test_debug_policy_error() {
for runtime_config in vec![
make_debug_allowlisting_config("source_name", "env_a", Moniker::root()),
make_debug_allowlisting_config("target_name", "env_b", Moniker::root()),
make_debug_allowlisting_config("target_name", "env_a", "a".try_into().unwrap()),
] {
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(EnvironmentBuilder::new().name("env_a").debug(
cm_rust::DebugRegistration::Protocol(cm_rust::DebugProtocolRegistration {
source_name: "source_name".parse().unwrap(),
target_name: "target_name".parse().unwrap(),
source: RegistrationSource::Parent,
}),
))
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.environment(EnvironmentBuilder::new().name("env_b"))
.build(),
);
let resolvers = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let model = Model::new(
ModelParams {
runtime_config,
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::new(HashMap::new()),
resolvers,
DebugRegistry::default(),
),
top_instance,
},
InstanceRegistry::new(),
)
.await
.unwrap();
model.discover_root_component(ComponentInput::default()).await;
assert_matches!(
model.root().resolve().await,
Err(ActionError::ResolveError {
err: ResolveActionError::Policy(
PolicyError::DebugCapabilityUseDisallowed { .. }
)
})
);
}
}
// Each component declares an environment for their child that inherits from the component's
// environment. The leaf component should be able to access the resolvers of the root.
#[fuchsia::test]
async fn test_inherit_root() -> Result<(), ModelError> {
let runner_reg = RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
};
let runners: HashMap<Name, RunnerRegistration> = hashmap! {
"test".parse().unwrap() => runner_reg.clone()
};
let debug_reg = DebugRegistration {
source_name: "source_name".parse().unwrap(),
source: RegistrationSource::Self_,
};
let debug_capabilities: HashMap<Name, DebugRegistration> = hashmap! {
"target_name".parse().unwrap() => debug_reg.clone()
};
let debug_registry = DebugRegistry { debug_capabilities };
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(
EnvironmentBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("b").environment("env_b"))
.environment(
EnvironmentBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component("b", ComponentDeclBuilder::new_empty_component().build());
let resolvers = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let model = Model::new(
ModelParams {
runtime_config: Arc::new(RuntimeConfig::default()),
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::new(runners),
resolvers,
debug_registry,
),
top_instance,
},
InstanceRegistry::new(),
)
.await
.unwrap();
model.discover_root_component(ComponentInput::default()).await;
let component = model
.root()
.start_instance(&vec!["a", "b"].try_into().unwrap(), &StartReason::Eager)
.await?;
assert_eq!(component.component_url, "test:///b");
let registered_runner =
component.environment.env.get_registered_runner(&"test".parse().unwrap()).unwrap();
assert_matches!(registered_runner, Some((ExtendedInstance::AboveRoot(_), r)) if r == runner_reg);
assert_matches!(
component.environment.env.get_registered_runner(&"foo".parse().unwrap()),
Ok(None)
);
let debug_capability = component
.environment
.env
.get_debug_capability(&"target_name".parse().unwrap())
.unwrap();
assert_matches!(debug_capability, Some((ExtendedInstance::AboveRoot(_), None, d)) if d == debug_reg);
assert_matches!(
component.environment.env.get_debug_capability(&"foo".parse().unwrap()),
Ok(None)
);
Ok(())
}
// A component declares an environment that inherits from realm, and the realm's environment
// added something that should be available in the component's realm.
#[fuchsia::test]
async fn test_inherit_parent() -> Result<(), ModelError> {
let runner_reg = RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
};
let runners: HashMap<Name, RunnerRegistration> = hashmap! {
"test".parse().unwrap() => runner_reg.clone()
};
let debug_reg = DebugRegistration {
source_name: "source_name".parse().unwrap(),
source: RegistrationSource::Parent,
};
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(
EnvironmentBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm)
.runner(RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
})
.debug(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "source_name".parse().unwrap(),
target_name: "target_name".parse().unwrap(),
source: RegistrationSource::Parent,
},
)),
)
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("b").environment("env_b"))
.environment(
EnvironmentBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component("b", ComponentDeclBuilder::new_empty_component().build());
let resolvers = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let runtime_config =
make_debug_allowlisting_config("target_name", "env_a", Moniker::root());
let model = Model::new(
ModelParams {
runtime_config,
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::new(runners),
resolvers,
DebugRegistry::default(),
),
top_instance,
},
InstanceRegistry::new(),
)
.await?;
model.discover_root_component(ComponentInput::default()).await;
let component = model
.root()
.start_instance(&vec!["a", "b"].try_into().unwrap(), &StartReason::Eager)
.await?;
assert_eq!(component.component_url, "test:///b");
let registered_runner =
component.environment.env.get_registered_runner(&"test".parse().unwrap()).unwrap();
assert_matches!(registered_runner, Some((ExtendedInstance::Component(c), r))
if r == runner_reg && c.moniker == Moniker::root());
assert_matches!(
component.environment.env.get_registered_runner(&"foo".parse().unwrap()),
Ok(None)
);
let debug_capability = component
.environment
.env
.get_debug_capability(&"target_name".parse().unwrap())
.unwrap();
assert_matches!(debug_capability, Some((ExtendedInstance::Component(c), Some(_), d))
if d == debug_reg && c.moniker == Moniker::root());
assert_matches!(
component.environment.env.get_debug_capability(&"foo".parse().unwrap()),
Ok(None)
);
Ok(())
}
fn make_debug_allowlisting_config(
name: &str,
env_name: &str,
env_moniker: Moniker,
) -> Arc<RuntimeConfig> {
let mut allowlist = HashSet::new();
allowlist.insert(DebugCapabilityAllowlistEntry::new(
AllowlistEntryBuilder::build_exact_from_moniker(&env_moniker),
));
let mut debug_capability_policy = HashMap::new();
debug_capability_policy.insert(
DebugCapabilityKey {
name: name.parse().unwrap(),
source: CapabilityAllowlistSource::Self_,
capability: cm_rust::CapabilityTypeName::Protocol,
env_name: env_name.parse().unwrap(),
},
allowlist,
);
let security_policy =
Arc::new(SecurityPolicy { debug_capability_policy, ..Default::default() });
Arc::new(RuntimeConfig { security_policy, ..Default::default() })
}
// A component in a collection declares an environment that inherits from realm, and the
// realm's environment added something that should be available in the component's realm.
#[fuchsia::test]
async fn test_inherit_in_collection() -> Result<(), ModelError> {
let runner_reg = RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
};
let runners: HashMap<Name, RunnerRegistration> = hashmap! {
"test".parse().unwrap() => runner_reg.clone()
};
let debug_reg = DebugRegistration {
source_name: "source_name".parse().unwrap(),
source: RegistrationSource::Parent,
};
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(
EnvironmentBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm)
.runner(RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
})
.debug(cm_rust::DebugRegistration::Protocol(
cm_rust::DebugProtocolRegistration {
source_name: "source_name".parse().unwrap(),
target_name: "target_name".parse().unwrap(),
source: RegistrationSource::Parent,
},
)),
)
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.collection(CollectionBuilder::new().name("coll").environment("env_b"))
.environment(
EnvironmentBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component("b", ComponentDeclBuilder::new_empty_component().build());
let resolvers = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let runtime_config =
make_debug_allowlisting_config("target_name", "env_a", Moniker::root());
let model = Model::new(
ModelParams {
runtime_config,
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::new(runners),
resolvers,
DebugRegistry::default(),
),
top_instance,
},
InstanceRegistry::new(),
)
.await?;
model.discover_root_component(ComponentInput::default()).await;
// Add instance to collection.
{
let parent = model
.root()
.start_instance(&vec!["a"].try_into().unwrap(), &StartReason::Eager)
.await?;
let child_decl = ChildBuilder::new().name("b").build();
parent
.add_dynamic_child(
"coll".into(),
&child_decl,
fcomponent::CreateChildArgs::default(),
)
.await
.expect("failed to add child");
}
let component = model
.root()
.start_instance(&vec!["a", "coll:b"].try_into().unwrap(), &StartReason::Eager)
.await?;
assert_eq!(component.component_url, "test:///b");
let registered_runner =
component.environment.env.get_registered_runner(&"test".parse().unwrap()).unwrap();
assert_matches!(registered_runner, Some((ExtendedInstance::Component(c), r))
if r == runner_reg && c.moniker == Moniker::root());
assert_matches!(
component.environment.env.get_registered_runner(&"foo".parse().unwrap()),
Ok(None)
);
let debug_capability = component
.environment
.env
.get_debug_capability(&"target_name".parse().unwrap())
.unwrap();
assert_matches!(debug_capability, Some((ExtendedInstance::Component(c), Some(n), d))
if d == debug_reg && n == "env_a" && c.moniker == Moniker::root());
assert_matches!(
component.environment.env.get_debug_capability(&"foo".parse().unwrap()),
Ok(None)
);
Ok(())
}
// One of the components does not declare or specify an environment for the leaf child. The
// leaf child component should still be able to access the resolvers of the root, as an
// implicit inheriting environment is assumed.
#[fuchsia::test]
async fn test_auto_inheritance() -> Result<(), ModelError> {
let runner_reg = RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "test-src".parse().unwrap(),
target_name: "test".parse().unwrap(),
};
let runners: HashMap<Name, RunnerRegistration> = hashmap! {
"test".parse().unwrap() => runner_reg.clone()
};
let debug_reg = DebugRegistration {
source_name: "source_name".parse().unwrap(),
source: RegistrationSource::Parent,
};
let debug_capabilities: HashMap<Name, DebugRegistration> = hashmap! {
"target_name".parse().unwrap() => debug_reg.clone()
};
let debug_registry = DebugRegistry { debug_capabilities };
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(
EnvironmentBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("b"))
.build(),
);
resolver.add_component("b", ComponentDeclBuilder::new_empty_component().build());
let resolvers = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let model = Model::new(
ModelParams {
runtime_config: Arc::new(RuntimeConfig::default()),
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::new(runners),
resolvers,
debug_registry,
),
top_instance,
},
InstanceRegistry::new(),
)
.await
.unwrap();
model.discover_root_component(ComponentInput::default()).await;
let component = model
.root()
.start_instance(&vec!["a", "b"].try_into().unwrap(), &StartReason::Eager)
.await?;
assert_eq!(component.component_url, "test:///b");
let registered_runner =
component.environment.env.get_registered_runner(&"test".parse().unwrap()).unwrap();
assert_matches!(registered_runner, Some((ExtendedInstance::AboveRoot(_), r)) if r == runner_reg);
assert_matches!(
component.environment.env.get_registered_runner(&"foo".parse().unwrap()),
Ok(None)
);
let debug_capability = component
.environment
.env
.get_debug_capability(&"target_name".parse().unwrap())
.unwrap();
assert_matches!(debug_capability, Some((ExtendedInstance::AboveRoot(_), None, d)) if d == debug_reg);
assert_matches!(
component.environment.env.get_debug_capability(&"foo".parse().unwrap()),
Ok(None)
);
Ok(())
}
// One of the components declares an environment that does not inherit from the realm. This
// means that any child components of this component cannot be resolved.
#[fuchsia::test]
async fn test_resolver_no_inheritance() -> Result<(), ModelError> {
let mut resolver = MockResolver::new();
resolver.add_component(
"root",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("a").environment("env_a"))
.environment(
EnvironmentBuilder::new()
.name("env_a")
.extends(fdecl::EnvironmentExtends::Realm),
)
.build(),
);
resolver.add_component(
"a",
ComponentDeclBuilder::new_empty_component()
.child(ChildBuilder::new().name("b").environment("env_b"))
.environment(
EnvironmentBuilder::new()
.name("env_b")
.extends(fdecl::EnvironmentExtends::None)
.stop_timeout(1234),
)
.build(),
);
resolver.add_component("b", ComponentDeclBuilder::new_empty_component().build());
let registry = {
let mut registry = ResolverRegistry::new();
registry.register("test".to_string(), Box::new(resolver));
registry
};
let top_instance = Arc::new(ComponentManagerInstance::new(vec![], vec![]));
let model = Model::new(
ModelParams {
runtime_config: Arc::new(RuntimeConfig::default()),
root_component_url: "test:///root".to_string(),
root_environment: Environment::new_root(
&top_instance,
RunnerRegistry::default(),
registry,
DebugRegistry::default(),
),
top_instance,
},
InstanceRegistry::new(),
)
.await?;
model.discover_root_component(ComponentInput::default()).await;
assert_matches!(
model
.root()
.start_instance(&vec!["a", "b"].try_into().unwrap(), &StartReason::Eager)
.await,
Err(ModelError::ActionError { err: ActionError::ResolveError { .. } })
);
Ok(())
}
}