blob: a99a9e0566782249d1f84ca3b23404a6397c2f66 [file] [log] [blame]
// Copyright 2019 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::{
error::ModelError,
moniker::{AbsoluteMoniker, ChildMoniker},
realm::WeakRealm,
},
async_trait::async_trait,
cm_rust::*,
fidl_fuchsia_sys2 as fsys, fuchsia_zircon as zx,
std::{collections::HashSet, fmt, path::PathBuf},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("Invalid scoped framework capability.")]
InvalidScopedFrameworkCapability {},
#[error("Invalid framework capability.")]
InvalidFrameworkCapability {},
}
/// Describes the source of a capability, as determined by `find_capability_source`
#[derive(Clone, Debug)]
pub enum CapabilitySource {
/// This capability originates from the component instance for the given Realm.
/// point.
Component { capability: ComponentCapability, realm: WeakRealm },
/// This capability originates from component manager itself and is optionally
/// scoped to a component's realm.
Framework { capability: FrameworkCapability, scope_moniker: Option<AbsoluteMoniker> },
}
impl CapabilitySource {
pub fn path(&self) -> Option<&CapabilityPath> {
match self {
CapabilitySource::Component { capability, .. } => capability.source_path(),
CapabilitySource::Framework { capability, .. } => capability.path(),
}
}
pub fn name(&self) -> Option<String> {
match self {
CapabilitySource::Component { capability, .. } => {
capability.source_name().map(|name| name.to_string())
}
CapabilitySource::Framework { capability, .. } => {
capability.name().map(|name| name.to_string())
}
}
}
}
impl fmt::Display for CapabilitySource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
CapabilitySource::Component { capability, realm } => {
format!("{} '{}'", capability, realm.moniker)
}
CapabilitySource::Framework { capability, .. } => capability.to_string(),
}
)
}
}
/// Describes a capability provided by the component manager which could either be
/// scoped to the realm, or global. Each capability type has a corresponding
/// `CapabilityPath` in the component manager's namespace. Note that this path may
/// not be unique as capabilities can compose.
#[derive(Debug, Clone)]
pub enum FrameworkCapability {
Service(CapabilityPath),
Protocol(CapabilityPath),
Directory(CapabilityPath),
Runner(CapabilityName),
Event(CapabilityName),
}
impl FrameworkCapability {
/// Returns a name for the capability type.
pub fn type_name(&self) -> &'static str {
match self {
FrameworkCapability::Service(_) => "service",
FrameworkCapability::Protocol(_) => "protocol",
FrameworkCapability::Directory(_) => "directory",
FrameworkCapability::Runner(_) => "runner",
FrameworkCapability::Event(_) => "event",
}
}
pub fn path(&self) -> Option<&CapabilityPath> {
match self {
FrameworkCapability::Service(source_path) => Some(&source_path),
FrameworkCapability::Protocol(source_path) => Some(&source_path),
FrameworkCapability::Directory(source_path) => Some(&source_path),
FrameworkCapability::Runner(_) | FrameworkCapability::Event(_) => None,
}
}
pub fn name(&self) -> Option<&CapabilityName> {
match self {
FrameworkCapability::Runner(name) => Some(&name),
FrameworkCapability::Event(name) => Some(&name),
FrameworkCapability::Service(_)
| FrameworkCapability::Protocol(_)
| FrameworkCapability::Directory(_) => None,
}
}
/// Returns the source path or name of the capability as a string, useful for debugging.
pub fn source_id(&self) -> String {
self.path()
.map(|p| format!("{}", p))
.or_else(|| self.name().map(|n| format!("{}", n)))
.unwrap_or_default()
}
pub fn builtin_from_use_decl(decl: &UseDecl) -> Result<Self, Error> {
match decl {
UseDecl::Service(s) if s.source == UseSource::Realm => {
Ok(FrameworkCapability::Service(s.source_path.clone()))
}
UseDecl::Protocol(s) if s.source == UseSource::Realm => {
Ok(FrameworkCapability::Protocol(s.source_path.clone()))
}
UseDecl::Directory(d) if d.source == UseSource::Realm => {
Ok(FrameworkCapability::Directory(d.source_path.clone()))
}
UseDecl::Event(e) if e.source == UseSource::Realm => {
Ok(FrameworkCapability::Event(e.source_name.clone()))
}
UseDecl::Runner(s) => Ok(FrameworkCapability::Runner(s.source_name.clone())),
_ => Err(Error::InvalidFrameworkCapability {}),
}
}
pub fn builtin_from_offer_decl(decl: &OfferDecl) -> Result<Self, Error> {
match decl {
OfferDecl::Protocol(s) if s.source == OfferServiceSource::Realm => {
Ok(FrameworkCapability::Protocol(s.source_path.clone()))
}
OfferDecl::Directory(d) if d.source == OfferDirectorySource::Realm => {
Ok(FrameworkCapability::Directory(d.source_path.clone()))
}
OfferDecl::Runner(s) if s.source == OfferRunnerSource::Realm => {
Ok(FrameworkCapability::Runner(s.source_name.clone()))
}
OfferDecl::Event(e) if e.source == OfferEventSource::Realm => {
Ok(FrameworkCapability::Event(e.source_name.clone()))
}
_ => {
return Err(Error::InvalidFrameworkCapability {});
}
}
}
pub fn builtin_from_storage_decl(decl: &StorageDecl) -> Result<Self, Error> {
if decl.source == StorageDirectorySource::Realm {
Ok(FrameworkCapability::Directory(decl.source_path.clone()))
} else {
Err(Error::InvalidFrameworkCapability {})
}
}
pub fn framework_from_use_decl(decl: &UseDecl) -> Result<Self, Error> {
match decl {
UseDecl::Service(s) if s.source == UseSource::Framework => {
Ok(FrameworkCapability::Service(s.source_path.clone()))
}
UseDecl::Protocol(s) if s.source == UseSource::Framework => {
Ok(FrameworkCapability::Protocol(s.source_path.clone()))
}
UseDecl::Directory(d) if d.source == UseSource::Framework => {
Ok(FrameworkCapability::Directory(d.source_path.clone()))
}
UseDecl::Event(e) if e.source == UseSource::Framework => {
Ok(FrameworkCapability::Event(e.source_name.clone()))
}
_ => {
return Err(Error::InvalidScopedFrameworkCapability {});
}
}
}
pub fn framework_from_offer_decl(decl: &OfferDecl) -> Result<Self, Error> {
match decl {
OfferDecl::Protocol(s) if s.source == OfferServiceSource::Realm => {
Ok(FrameworkCapability::Protocol(s.source_path.clone()))
}
OfferDecl::Directory(d) if d.source == OfferDirectorySource::Framework => {
Ok(FrameworkCapability::Directory(d.source_path.clone()))
}
OfferDecl::Event(e) if e.source == OfferEventSource::Framework => {
Ok(FrameworkCapability::Event(e.source_name.clone()))
}
_ => {
return Err(Error::InvalidScopedFrameworkCapability {});
}
}
}
pub fn framework_from_expose_decl(decl: &ExposeDecl) -> Result<Self, Error> {
match decl {
ExposeDecl::Protocol(d) if d.source == ExposeSource::Framework => {
Ok(FrameworkCapability::Protocol(d.source_path.clone()))
}
ExposeDecl::Directory(d) if d.source == ExposeSource::Framework => {
Ok(FrameworkCapability::Directory(d.source_path.clone()))
}
_ => {
return Err(Error::InvalidScopedFrameworkCapability {});
}
}
}
}
impl fmt::Display for FrameworkCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} '{}' from framework", self.type_name(), self.source_id())
}
}
/// The server-side of a capability implements this trait.
/// Multiple `CapabilityProvider` objects can compose with one another for a single
/// capability request. For example, a `CapabitilityProvider` can be interposed
/// between the primary `CapabilityProvider and the client for the purpose of
/// logging and testing. A `CapabilityProvider` is typically provided by a
/// corresponding `Hook` in response to the `CapabilityRouted` event.
/// A capability provider is used exactly once as a result of exactly one route.
#[async_trait]
pub trait CapabilityProvider: Send + Sync {
// Called to bind a server end of a zx::Channel to the provided capability.
// If the capability is a directory, then |flags|, |open_mode| and |relative_path|
// will be propagated along to open the appropriate directory.
async fn open(
self: Box<Self>,
flags: u32,
open_mode: u32,
relative_path: PathBuf,
server_end: &mut zx::Channel,
) -> Result<(), ModelError>;
}
/// A capability being routed from a component.
#[derive(Clone, Debug)]
pub enum ComponentCapability {
Use(UseDecl),
Expose(ExposeDecl),
/// Models a capability hosted from the exposed dir which is used at runtime.
UsedExpose(ExposeDecl),
Offer(OfferDecl),
Storage(StorageDecl),
Runner(RunnerDecl),
}
impl ComponentCapability {
/// Returns a name for the capability type.
pub fn type_name(&self) -> &'static str {
match self {
ComponentCapability::Use(use_) => match use_ {
UseDecl::Protocol(_) => "protocol",
UseDecl::Directory(_) => "directory",
UseDecl::Service(_) => "service",
UseDecl::Storage(_) => "storage",
UseDecl::Runner(_) => "runner",
UseDecl::Event(_) => "event",
},
ComponentCapability::Expose(expose) | ComponentCapability::UsedExpose(expose) => {
match expose {
ExposeDecl::Protocol(_) => "protocol",
ExposeDecl::Directory(_) => "directory",
ExposeDecl::Service(_) => "service",
ExposeDecl::Runner(_) => "runner",
ExposeDecl::Resolver(_) => "resolver",
}
}
ComponentCapability::Offer(offer) => match offer {
OfferDecl::Protocol(_) => "protocol",
OfferDecl::Directory(_) => "directory",
OfferDecl::Service(_) => "service",
OfferDecl::Storage(_) => "storage",
OfferDecl::Runner(_) => "runner",
OfferDecl::Resolver(_) => "resolver",
OfferDecl::Event(_) => "event",
},
ComponentCapability::Storage(_) => "storage",
ComponentCapability::Runner(_) => "runner",
}
}
/// Returns the source path of the capability, if one exists.
pub fn source_path(&self) -> Option<&CapabilityPath> {
match self {
ComponentCapability::Use(use_) => match use_ {
UseDecl::Protocol(UseProtocolDecl { source_path, .. }) => Some(source_path),
UseDecl::Directory(UseDirectoryDecl { source_path, .. }) => Some(source_path),
_ => None,
},
ComponentCapability::Expose(expose) => match expose {
ExposeDecl::Protocol(ExposeProtocolDecl { source_path, .. }) => Some(source_path),
ExposeDecl::Directory(ExposeDirectoryDecl { source_path, .. }) => Some(source_path),
_ => None,
},
ComponentCapability::UsedExpose(expose) => {
// A UsedExpose needs to be matched to the ExposeDecl the UsedExpose wraps at the
// same component. This is accomplished by returning the ExposeDecl's target path.
// Effectively, it's as if the UsedExposed were a UseDecl with both the source and
// target path equal to `target_path`.
match expose {
ExposeDecl::Protocol(ExposeProtocolDecl { target_path, .. }) => {
Some(target_path)
}
ExposeDecl::Directory(ExposeDirectoryDecl { target_path, .. }) => {
Some(target_path)
}
_ => None,
}
}
ComponentCapability::Offer(offer) => match offer {
OfferDecl::Protocol(OfferProtocolDecl { source_path, .. }) => Some(source_path),
OfferDecl::Directory(OfferDirectoryDecl { source_path, .. }) => Some(source_path),
_ => None,
},
ComponentCapability::Runner(RunnerDecl { source_path, .. }) => Some(source_path),
ComponentCapability::Storage(_) => None,
}
}
/// Return the source name of the capability, if one exists.
pub fn source_name<'a>(&self) -> Option<&CapabilityName> {
match self {
ComponentCapability::Expose(ExposeDecl::Runner(ExposeRunnerDecl {
source_name,
..
})) => Some(source_name),
ComponentCapability::Offer(OfferDecl::Runner(OfferRunnerDecl {
source_name, ..
})) => Some(source_name),
ComponentCapability::Use(UseDecl::Event(UseEventDecl { source_name, .. })) => {
Some(source_name)
}
ComponentCapability::Offer(OfferDecl::Event(OfferEventDecl {
source_name, ..
})) => Some(source_name),
ComponentCapability::Use(UseDecl::Storage(d)) => Some(d.type_name()),
ComponentCapability::Offer(OfferDecl::Storage(d)) => Some(d.type_name()),
_ => None,
}
}
/// Returns the source path or name of the capability as a string, useful for debugging.
pub fn source_id(&self) -> String {
self.source_path()
.map(|p| format!("{}", p))
.or_else(|| self.source_name().map(|n| format!("{}", n)))
.unwrap_or_default()
}
/// Returns the `ExposeDecl` that exposes the capability, if it exists.
pub fn find_expose_source<'a>(&self, decl: &'a ComponentDecl) -> Option<&'a ExposeDecl> {
decl.exposes.iter().find(|&expose| match (self, expose) {
// Protocol exposed to me that has a matching `expose` or `offer`.
(
ComponentCapability::Offer(OfferDecl::Protocol(parent_offer)),
ExposeDecl::Protocol(expose),
) => parent_offer.source_path == expose.target_path,
(
ComponentCapability::Expose(ExposeDecl::Protocol(parent_expose)),
ExposeDecl::Protocol(expose),
) => parent_expose.source_path == expose.target_path,
(
ComponentCapability::UsedExpose(ExposeDecl::Protocol(used_expose)),
ExposeDecl::Protocol(expose),
) => used_expose.target_path == expose.target_path,
// Directory exposed to me that matches a directory `expose` or `offer`.
(
ComponentCapability::Offer(OfferDecl::Directory(parent_offer)),
ExposeDecl::Directory(expose),
) => parent_offer.source_path == expose.target_path,
(
ComponentCapability::Expose(ExposeDecl::Directory(parent_expose)),
ExposeDecl::Directory(expose),
) => parent_expose.source_path == expose.target_path,
(
ComponentCapability::UsedExpose(ExposeDecl::Directory(used_expose)),
ExposeDecl::Directory(expose),
) => used_expose.target_path == expose.target_path,
// Runner exposed to me that has a matching `expose` or `offer`.
(
ComponentCapability::Offer(OfferDecl::Runner(parent_offer)),
ExposeDecl::Runner(expose),
) => parent_offer.source_name == expose.target_name,
(
ComponentCapability::Expose(ExposeDecl::Runner(parent_expose)),
ExposeDecl::Runner(expose),
) => parent_expose.source_name == expose.target_name,
// Directory exposed to me that matches a `storage` declaration which consumes it.
(ComponentCapability::Storage(parent_storage), ExposeDecl::Directory(expose)) => {
parent_storage.source_path == expose.target_path
}
_ => false,
})
}
/// Returns the set of `ExposeServiceDecl`s that expose the service capability, if they exist.
#[allow(unused)]
pub fn find_expose_service_sources<'a>(
&self,
decl: &'a ComponentDecl,
) -> Vec<&'a ExposeServiceDecl> {
let paths: HashSet<_> = match self {
ComponentCapability::Offer(OfferDecl::Service(parent_offer)) => {
parent_offer.sources.iter().map(|s| &s.source_path).collect()
}
ComponentCapability::Expose(ExposeDecl::Service(parent_expose)) => {
parent_expose.sources.iter().map(|s| &s.source_path).collect()
}
_ => panic!("Expected an offer or expose of a service capability, found: {:?}", self),
};
decl.exposes
.iter()
.filter_map(|expose| match expose {
ExposeDecl::Service(expose) if paths.contains(&expose.target_path) => Some(expose),
_ => None,
})
.collect()
}
/// Given a parent ComponentDecl, returns the `OfferDecl` that offers this capability to
/// `child_moniker`, if it exists.
pub fn find_offer_source<'a>(
&self,
decl: &'a ComponentDecl,
child_moniker: &ChildMoniker,
) -> Option<&'a OfferDecl> {
decl.offers.iter().find(|&offer| match (self, offer) {
// Protocol offered to me that matches a service `use` or `offer` declaration.
(
ComponentCapability::Use(UseDecl::Protocol(child_use)),
OfferDecl::Protocol(offer),
) => Self::is_offer_protocol_or_directory_match(
child_moniker,
&child_use.source_path,
&offer.target,
&offer.target_path,
),
(
ComponentCapability::Offer(OfferDecl::Protocol(child_offer)),
OfferDecl::Protocol(offer),
) => Self::is_offer_protocol_or_directory_match(
child_moniker,
&child_offer.source_path,
&offer.target,
&offer.target_path,
),
// Directory offered to me that matches a directory `use` or `offer` declaration.
(
ComponentCapability::Use(UseDecl::Directory(child_use)),
OfferDecl::Directory(offer),
) => Self::is_offer_protocol_or_directory_match(
child_moniker,
&child_use.source_path,
&offer.target,
&offer.target_path,
),
(
ComponentCapability::Offer(OfferDecl::Directory(child_offer)),
OfferDecl::Directory(offer),
) => Self::is_offer_protocol_or_directory_match(
child_moniker,
&child_offer.source_path,
&offer.target,
&offer.target_path,
),
// Directory offered to me that matches a `storage` declaration which consumes it.
(ComponentCapability::Storage(child_storage), OfferDecl::Directory(offer)) => {
Self::is_offer_protocol_or_directory_match(
child_moniker,
&child_storage.source_path,
&offer.target,
&offer.target_path,
)
}
// Storage offered to me.
(ComponentCapability::Use(UseDecl::Storage(child_use)), OfferDecl::Storage(offer)) => {
Self::is_offer_storage_match(
child_moniker,
child_use.type_(),
offer.target(),
offer.type_(),
)
}
(
ComponentCapability::Offer(OfferDecl::Storage(child_offer)),
OfferDecl::Storage(offer),
) => Self::is_offer_storage_match(
child_moniker,
child_offer.type_(),
offer.target(),
offer.type_(),
),
// Runners offered from parent.
(ComponentCapability::Use(UseDecl::Runner(child_use)), OfferDecl::Runner(offer)) => {
Self::is_offer_runner_or_event_match(
child_moniker,
&child_use.source_name,
&offer.target,
&offer.target_name,
)
}
(
ComponentCapability::Offer(OfferDecl::Runner(child_offer)),
OfferDecl::Runner(parent_offer),
) => Self::is_offer_runner_or_event_match(
child_moniker,
&child_offer.source_name,
&parent_offer.target,
&parent_offer.target_name,
),
// Events offered from parent.
(ComponentCapability::Use(UseDecl::Event(child_use)), OfferDecl::Event(offer)) => {
Self::is_offer_runner_or_event_match(
child_moniker,
&child_use.source_name,
&offer.target,
&offer.target_name,
)
}
(
ComponentCapability::Offer(OfferDecl::Event(child_offer)),
OfferDecl::Event(parent_offer),
) => Self::is_offer_runner_or_event_match(
child_moniker,
&child_offer.source_name,
&parent_offer.target,
&parent_offer.target_name,
),
_ => false,
})
}
/// Returns the set of `OfferServiceDecl`s that offer the service capability, if they exist.
#[allow(unused)]
pub fn find_offer_service_sources<'a>(
&self,
decl: &'a ComponentDecl,
child_moniker: &ChildMoniker,
) -> Vec<&'a OfferServiceDecl> {
let paths: HashSet<_> = match self {
ComponentCapability::Use(UseDecl::Service(child_use)) => {
vec![&child_use.source_path].into_iter().collect()
}
ComponentCapability::Offer(OfferDecl::Service(child_offer)) => {
child_offer.sources.iter().map(|s| &s.source_path).collect()
}
_ => panic!("Expected a use or offer of a service capability, found: {:?}", self),
};
decl.offers
.iter()
.filter_map(|offer| match offer {
OfferDecl::Service(offer)
if Self::is_offer_service_match(
child_moniker,
&paths,
&offer.target,
&offer.target_path,
) =>
{
Some(offer)
}
_ => None,
})
.collect()
}
/// Given a offer/expose of a Runner from `Self`, return the associated RunnerDecl,
/// if it exists.
pub fn find_runner_source<'a>(&self, decl: &'a ComponentDecl) -> Option<&'a RunnerDecl> {
decl.find_runner_source(self.source_name()?.str())
}
fn is_offer_service_match(
child_moniker: &ChildMoniker,
paths: &HashSet<&CapabilityPath>,
target: &OfferTarget,
target_path: &CapabilityPath,
) -> bool {
paths.contains(target_path) && target_matches_moniker(target, child_moniker)
}
fn is_offer_protocol_or_directory_match(
child_moniker: &ChildMoniker,
path: &CapabilityPath,
target: &OfferTarget,
target_path: &CapabilityPath,
) -> bool {
path == target_path && target_matches_moniker(target, child_moniker)
}
fn is_offer_storage_match(
child_moniker: &ChildMoniker,
child_type: fsys::StorageType,
parent_target: &OfferTarget,
parent_type: fsys::StorageType,
) -> bool {
// The types must match...
parent_type == child_type &&
// ...and the child/collection names must match.
target_matches_moniker(parent_target, child_moniker)
}
fn is_offer_runner_or_event_match(
child_moniker: &ChildMoniker,
source_name: &CapabilityName,
target: &OfferTarget,
target_name: &CapabilityName,
) -> bool {
source_name == target_name && target_matches_moniker(target, child_moniker)
}
}
impl fmt::Display for ComponentCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} '{}' from component", self.type_name(), self.source_id())
}
}
/// Returns if `parent_target` refers to a the child `child_moniker`.
fn target_matches_moniker(parent_target: &OfferTarget, child_moniker: &ChildMoniker) -> bool {
match (parent_target, child_moniker.collection()) {
(OfferTarget::Child(target_child_name), None) => target_child_name == child_moniker.name(),
(OfferTarget::Collection(target_collection_name), Some(collection)) => {
target_collection_name == collection
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use cm_rust::OfferServiceDecl;
use {
super::*,
crate::model::testing::test_helpers::default_component_decl,
cm_rust::{
ExposeRunnerDecl, ExposeSource, ExposeTarget, OfferServiceSource, ServiceSource,
StorageDirectorySource, UseRunnerDecl,
},
};
#[test]
fn find_expose_service_sources() {
let capability = ComponentCapability::Expose(ExposeDecl::Service(ExposeServiceDecl {
sources: vec![
ServiceSource {
source: ExposeServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "net".to_string(),
},
},
ServiceSource {
source: ExposeServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "log".to_string(),
},
},
ServiceSource {
source: ExposeServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "unmatched-source".to_string(),
},
},
],
target: ExposeTarget::Realm,
target_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
}));
let net_service = ExposeServiceDecl {
sources: vec![],
target: ExposeTarget::Realm,
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "net".to_string(),
},
};
let log_service = ExposeServiceDecl {
sources: vec![],
target: ExposeTarget::Realm,
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "log".to_string(),
},
};
let unmatched_service = ExposeServiceDecl {
sources: vec![],
target: ExposeTarget::Realm,
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "unmatched-target".to_string(),
},
};
let decl = ComponentDecl {
exposes: vec![
ExposeDecl::Service(net_service.clone()),
ExposeDecl::Service(log_service.clone()),
ExposeDecl::Service(unmatched_service.clone()),
],
..default_component_decl()
};
let sources = capability.find_expose_service_sources(&decl);
assert_eq!(sources, vec![&net_service, &log_service])
}
#[test]
#[should_panic]
#[ignore] // fxb/40189
fn find_expose_service_sources_with_unexpected_capability() {
let capability = ComponentCapability::Storage(StorageDecl {
name: "".to_string(),
source: StorageDirectorySource::Realm,
source_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
});
capability.find_expose_service_sources(&default_component_decl());
}
#[test]
fn find_offer_service_sources() {
let capability = ComponentCapability::Offer(OfferDecl::Service(OfferServiceDecl {
sources: vec![
ServiceSource {
source: OfferServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "net".to_string(),
},
},
ServiceSource {
source: OfferServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "log".to_string(),
},
},
ServiceSource {
source: OfferServiceSource::Self_,
source_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "unmatched-source".to_string(),
},
},
],
target: OfferTarget::Child("".to_string()),
target_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
}));
let net_service = OfferServiceDecl {
sources: vec![],
target: OfferTarget::Child("child".to_string()),
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "net".to_string(),
},
};
let log_service = OfferServiceDecl {
sources: vec![],
target: OfferTarget::Child("child".to_string()),
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "log".to_string(),
},
};
let unmatched_service = OfferServiceDecl {
sources: vec![],
target: OfferTarget::Child("child".to_string()),
target_path: CapabilityPath {
dirname: "/svc".to_string(),
basename: "unmatched-target".to_string(),
},
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Service(net_service.clone()),
OfferDecl::Service(log_service.clone()),
OfferDecl::Service(unmatched_service.clone()),
],
..default_component_decl()
};
let moniker = ChildMoniker::new("child".to_string(), None, 0);
let sources = capability.find_offer_service_sources(&decl, &moniker);
assert_eq!(sources, vec![&net_service, &log_service])
}
#[test]
fn find_offer_source_runner() {
// Parents offers runner named "elf" to "child".
let parent_decl = ComponentDecl {
offers: vec![
// Offer as "elf" to child "child".
OfferDecl::Runner(cm_rust::OfferRunnerDecl {
source: cm_rust::OfferRunnerSource::Self_,
source_name: "source".into(),
target: cm_rust::OfferTarget::Child("child".to_string()),
target_name: "elf".into(),
}),
],
..default_component_decl()
};
// A child named "child" uses a runner "elf" offered by its parent. Should successfully
// match the declaration.
let child_cap =
ComponentCapability::Use(UseDecl::Runner(UseRunnerDecl { source_name: "elf".into() }));
assert_eq!(
child_cap.find_offer_source(&parent_decl, &"child:0".into()),
Some(&parent_decl.offers[0])
);
// Mismatched child name.
assert_eq!(child_cap.find_offer_source(&parent_decl, &"other-child:0".into()), None);
// Mismatched cap name.
let misnamed_child_cap = ComponentCapability::Use(UseDecl::Runner(UseRunnerDecl {
source_name: "dwarf".into(),
}));
assert_eq!(misnamed_child_cap.find_offer_source(&parent_decl, &"child:0".into()), None);
}
#[test]
fn find_offer_source_event() {
// Parent offers event named "started" to "child"
let parent_decl = ComponentDecl {
offers: vec![OfferDecl::Event(cm_rust::OfferEventDecl {
source: cm_rust::OfferEventSource::Realm,
source_name: "started".into(),
target: cm_rust::OfferTarget::Child("child".to_string()),
target_name: "started".into(),
filter: None,
})],
..default_component_decl()
};
// A child named "child" uses the event "started" offered by its parent. Should
// successfully match the declaration.
let child_cap = ComponentCapability::Use(UseDecl::Event(UseEventDecl {
source: cm_rust::UseSource::Realm,
source_name: "started".into(),
target_name: "started-x".into(),
filter: None,
}));
assert_eq!(
child_cap.find_offer_source(&parent_decl, &"child:0".into()),
Some(&parent_decl.offers[0])
);
// Mismatched child name.
assert_eq!(child_cap.find_offer_source(&parent_decl, &"other-child:0".into()), None);
// Mismatched capability name.
let misnamed_child_cap = ComponentCapability::Use(UseDecl::Event(UseEventDecl {
source: cm_rust::UseSource::Realm,
source_name: "foo".into(),
target_name: "started".into(),
filter: None,
}));
assert_eq!(misnamed_child_cap.find_offer_source(&parent_decl, &"child:0".into()), None);
}
#[test]
fn find_expose_source_runner() {
// A child named "child" exposes a runner "elf" to its parent.
let child_decl = ComponentDecl {
exposes: vec![
// Expose as "elf" to Realm.
ExposeDecl::Runner(cm_rust::ExposeRunnerDecl {
source: cm_rust::ExposeSource::Self_,
source_name: "source".into(),
target: cm_rust::ExposeTarget::Realm,
target_name: "elf".into(),
}),
],
..default_component_decl()
};
// A parent exposes a runner "elf" with a child as its source. Should successfully match the
// declaration.
let parent_cap = ComponentCapability::Expose(ExposeDecl::Runner(ExposeRunnerDecl {
source: ExposeSource::Child("child".into()),
source_name: "elf".into(),
target: ExposeTarget::Realm,
target_name: "parent_elf".into(),
}));
assert_eq!(parent_cap.find_expose_source(&child_decl), Some(&child_decl.exposes[0]));
// If the name is mismatched, we shouldn't find anything though.
let misnamed_parent_cap =
ComponentCapability::Expose(ExposeDecl::Runner(ExposeRunnerDecl {
source: ExposeSource::Child("child".into()),
source_name: "dwarf".into(),
target: ExposeTarget::Realm,
target_name: "parent_elf".into(),
}));
assert_eq!(misnamed_parent_cap.find_expose_source(&child_decl), None);
}
#[test]
#[should_panic]
#[ignore] // fxb/40189
fn find_offer_service_sources_with_unexpected_capability() {
let capability = ComponentCapability::Storage(StorageDecl {
name: "".to_string(),
source: StorageDirectorySource::Realm,
source_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
});
let moniker = ChildMoniker::new("".to_string(), None, 0);
capability.find_offer_service_sources(&default_component_decl(), &moniker);
}
}