blob: a05574e63c21a3943c3d22fc7587e782bc6bcaa7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
capability_routing::{
error::CapabilityRouteError,
source::CapabilitySourceType,
verifier::{CapabilityRouteState, CapabilityRouteVerifier},
},
component_tree::ComponentNode,
},
cm_rust::{
CapabilityDecl, CapabilityName, DirectoryDecl, ExposeDecl, ExposeDirectoryDecl,
ExposeTarget, OfferDecl, OfferDirectoryDecl, OfferTarget, UseDecl, UseDirectoryDecl,
},
fidl_fuchsia_io2::Operations,
moniker::PartialMoniker,
};
/// A verifier for directory capability routes and rights.
pub struct DirectoryCapabilityRouteVerifier {}
impl DirectoryCapabilityRouteVerifier {
pub fn new() -> Self {
DirectoryCapabilityRouteVerifier {}
}
}
pub struct DirectoryFields {
rights: Operations,
}
impl<'a> CapabilityRouteVerifier<'a> for DirectoryCapabilityRouteVerifier {
type UseDeclType = UseDirectoryDecl;
type OfferDeclType = OfferDirectoryDecl;
type ExposeDeclType = ExposeDirectoryDecl;
type CapabilityDeclType = DirectoryDecl;
type FieldsType = DirectoryFields;
/// Checks that the directory rights specified in `target_state` are a
/// subset of those specified in `source_state`.
fn verify_route_segment(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
source_state: &CapabilityRouteState<'a, DirectoryFields>,
) -> Result<(), CapabilityRouteError> {
if !source_state.fields.rights.contains(target_state.fields.rights) {
return Err(CapabilityRouteError::InvalidDirectoryRights(
source_state.node.short_display(),
));
}
Ok(())
}
// Implementations specific to `UseDirectoryDecl`
fn get_use_info(&self, use_decl: &UseDirectoryDecl) -> (CapabilityName, CapabilitySourceType) {
(use_decl.source_name.clone(), CapabilitySourceType::from(&use_decl.source))
}
fn get_use_decls(&self, node: &'a ComponentNode) -> Vec<&'a UseDirectoryDecl> {
let mut use_dir_decls = Vec::<&UseDirectoryDecl>::new();
for decl in node.decl.uses.iter() {
if let UseDecl::Directory(use_dir_decl) = decl {
use_dir_decls.push(use_dir_decl);
}
}
use_dir_decls
}
// Implementations specific to `OfferDirectoryDecl`
fn get_offer_info(
&self,
offer_decl: &OfferDirectoryDecl,
) -> (CapabilityName, CapabilityName, CapabilitySourceType) {
(
offer_decl.target_name.clone(),
offer_decl.source_name.clone(),
CapabilitySourceType::from(&offer_decl.source),
)
}
fn get_offer_decls(&self, node: &'a ComponentNode) -> Vec<&'a OfferDirectoryDecl> {
let mut offer_dir_decls = Vec::<&OfferDirectoryDecl>::new();
for decl in node.decl.offers.iter() {
if let OfferDecl::Directory(offer_dir_decl) = decl {
offer_dir_decls.push(offer_dir_decl);
}
}
offer_dir_decls
}
fn is_matching_offer(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
target_moniker: &'a PartialMoniker,
offer_decl: &'a OfferDirectoryDecl,
) -> bool {
if let OfferTarget::Child(child_name) = &offer_decl.target {
return (&child_name.as_str(), &offer_decl.target_name)
== (&target_moniker.as_str(), &target_state.name);
}
false
}
// Implementations specific to `ExposeDirectoryDecl`
fn get_expose_info(
&self,
expose_decl: &ExposeDirectoryDecl,
) -> (CapabilityName, CapabilityName, CapabilitySourceType) {
(
expose_decl.target_name.clone(),
expose_decl.source_name.clone(),
CapabilitySourceType::from(&expose_decl.source),
)
}
fn get_expose_decls(&self, node: &'a ComponentNode) -> Vec<&'a ExposeDirectoryDecl> {
let mut expose_dir_decls = Vec::<&ExposeDirectoryDecl>::new();
for decl in node.decl.exposes.iter() {
if let ExposeDecl::Directory(expose_dir_decl) = decl {
expose_dir_decls.push(expose_dir_decl);
}
}
expose_dir_decls
}
fn is_matching_expose(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
expose_decl: &'a ExposeDirectoryDecl,
) -> bool {
if ExposeTarget::Parent == expose_decl.target {
return expose_decl.target_name == target_state.name;
}
false
}
// Implementations specific to `DirectoryDecl`
fn get_declare_info(&self, capability_decl: &DirectoryDecl) -> CapabilityName {
capability_decl.name.clone()
}
fn get_capability_decls(&self, node: &'a ComponentNode) -> Vec<&'a DirectoryDecl> {
let mut dir_decls = Vec::<&DirectoryDecl>::new();
for decl in node.decl.capabilities.iter() {
if let CapabilityDecl::Directory(dir_decl) = decl {
dir_decls.push(dir_decl);
}
}
dir_decls
}
fn is_matching_declare(
&self,
route_state: &CapabilityRouteState<'a, DirectoryFields>,
decl: &'a DirectoryDecl,
) -> bool {
decl.name == route_state.name
}
// Implementations specific to `DirectoryFields`
fn fields_from_use(
&self,
use_decl: &UseDirectoryDecl,
) -> Result<DirectoryFields, CapabilityRouteError> {
Ok(DirectoryFields { rights: use_decl.rights })
}
fn fields_from_offer(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
offer_decl: &'a OfferDirectoryDecl,
) -> Result<DirectoryFields, CapabilityRouteError> {
if let Some(offer_rights) = offer_decl.rights {
return Ok(DirectoryFields { rights: offer_rights });
}
Ok(DirectoryFields { rights: target_state.fields.rights })
}
fn fields_from_expose(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
expose_decl: &'a ExposeDirectoryDecl,
) -> Result<DirectoryFields, CapabilityRouteError> {
if let Some(expose_rights) = expose_decl.rights {
return Ok(DirectoryFields { rights: expose_rights });
}
Ok(DirectoryFields { rights: target_state.fields.rights })
}
fn fields_from_declare(
&self,
capability_decl: &'a DirectoryDecl,
) -> Result<DirectoryFields, CapabilityRouteError> {
Ok(DirectoryFields { rights: capability_decl.rights })
}
fn fields_from_framework(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
) -> Result<DirectoryFields, CapabilityRouteError> {
Ok(DirectoryFields { rights: target_state.fields.rights })
}
fn fields_from_root_parent(
&self,
target_state: &CapabilityRouteState<'a, DirectoryFields>,
) -> Result<DirectoryFields, CapabilityRouteError> {
Ok(DirectoryFields { rights: target_state.fields.rights })
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
capability_routing::testing::*,
component_tree::{BuildTreeResult, ComponentTreeBuilder, NodePath},
},
cm_rust::{CapabilityPath, DependencyType, ExposeSource, OfferSource, UseSource},
moniker::PartialMoniker,
std::collections::HashMap,
};
fn new_use_directory_decl(
source: UseSource,
source_name: CapabilityName,
rights: Operations,
) -> UseDirectoryDecl {
UseDirectoryDecl {
source,
source_name,
target_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
rights,
subdir: None,
}
}
fn new_offer_directory_decl(
source: OfferSource,
source_name: CapabilityName,
target: OfferTarget,
target_name: CapabilityName,
rights: Option<Operations>,
) -> OfferDirectoryDecl {
OfferDirectoryDecl {
source,
source_name,
target,
target_name,
rights,
subdir: None,
dependency_type: DependencyType::Strong,
}
}
fn new_expose_directory_decl(
source: ExposeSource,
source_name: CapabilityName,
target: ExposeTarget,
target_name: CapabilityName,
rights: Option<Operations>,
) -> ExposeDirectoryDecl {
ExposeDirectoryDecl { source, source_name, target, target_name, rights, subdir: None }
}
fn new_directory_decl(name: CapabilityName, rights: Operations) -> DirectoryDecl {
DirectoryDecl {
name,
source_path: CapabilityPath { dirname: "".to_string(), basename: "".to_string() },
rights,
}
}
// Builds a `ComponentTree` with 4 nodes and the following structure:
//
// root
// / \
// foo bar
// /
// baz
//
// In addition, adds routing for the directory capability specified in `use_decl` with
// rights specified by the arguments.
//
// The capability is provided by `bar` and used by `baz` via `root` and `foo`. Each node
// defines its own alias for the capability.
fn build_tree_with_directory_route(
baz_rights: Operations,
foo_rights: Option<Operations>,
root_rights: Option<Operations>,
bar_rights: Option<Operations>,
) -> BuildTreeResult {
let root_url = "root_url".to_string();
let foo_url = "foo_url".to_string();
let bar_url = "bar_url".to_string();
let baz_url = "baz_url".to_string();
let foo_name = "foo".to_string();
let bar_name = "bar".to_string();
let baz_name = "baz".to_string();
let root_dir_name = CapabilityName("root_dir_name".to_string());
let foo_dir_name = CapabilityName("foo_dir_name".to_string());
let bar_dir_name = CapabilityName("bar_dir_name".to_string());
let baz_dir_name = CapabilityName("baz_dir_name".to_string());
let bar_decl = new_component_decl(
vec![],
vec![ExposeDecl::Directory(new_expose_directory_decl(
ExposeSource::Self_,
bar_dir_name.clone(),
ExposeTarget::Parent,
root_dir_name.clone(),
bar_rights,
))],
vec![],
vec![CapabilityDecl::Directory(new_directory_decl(bar_dir_name, bar_rights.unwrap()))],
vec![],
);
let root_decl = new_component_decl(
vec![],
vec![],
vec![OfferDecl::Directory(new_offer_directory_decl(
OfferSource::Child(bar_name.clone()),
root_dir_name,
OfferTarget::Child(foo_name.clone()),
foo_dir_name.clone(),
root_rights,
))],
vec![],
vec![new_child_decl(&foo_name, &foo_url), new_child_decl(&bar_name, &bar_url)],
);
let foo_decl = new_component_decl(
vec![],
vec![],
vec![OfferDecl::Directory(new_offer_directory_decl(
OfferSource::Parent,
foo_dir_name,
OfferTarget::Child(baz_name.clone()),
baz_dir_name.clone(),
foo_rights,
))],
vec![],
vec![new_child_decl(&baz_name, &baz_url)],
);
let baz_decl = new_component_decl(
vec![UseDecl::Directory(new_use_directory_decl(
UseSource::Parent,
baz_dir_name,
baz_rights,
))],
vec![],
vec![],
vec![],
vec![],
);
let mut decls = HashMap::new();
decls.insert(root_url.to_string(), root_decl.clone());
decls.insert(foo_url.to_string(), foo_decl.clone());
decls.insert(bar_url.to_string(), bar_decl.clone());
decls.insert(baz_url.to_string(), baz_decl.clone());
ComponentTreeBuilder::new(decls).build(root_url)
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` accepts a valid 2-node route
// where source and target specify the same set of rights.
#[test]
fn directory_verify_offer_from_parent() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let child_name = "child".to_string();
let dir_name = CapabilityName("dir_name".to_string());
let offer_rights = Operations::Connect;
let root_offer_dir = new_offer_directory_decl(
OfferSource::Self_,
dir_name.clone(),
OfferTarget::Child(child_name.clone()),
dir_name.clone(),
Some(offer_rights),
);
let root_dir_decl = new_directory_decl(dir_name.clone(), offer_rights);
let child_use_dir = new_use_directory_decl(UseSource::Parent, dir_name, offer_rights);
let build_tree_result = build_two_node_tree(
vec![],
vec![OfferDecl::Directory(root_offer_dir)],
vec![CapabilityDecl::Directory(root_dir_decl)],
vec![UseDecl::Directory(child_use_dir.clone())],
vec![],
vec![],
);
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let child_node =
tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?;
let route = verifier.verify_route(&tree, &child_use_dir, &child_node)?;
assert_eq!(route.len(), 3);
assert_eq!(route[0].to_string(), "use by `/child` as `dir_name` from parent");
assert_eq!(route[1].to_string(), "offer by `/` as `dir_name` from self");
assert_eq!(route[2].to_string(), "declare by `/` as `dir_name`");
Ok(())
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` rejects a route when
// a child node has a `UseDirectoryDecl` with a source of Parent, but its parent has no
// matching `OfferDirectoryDecl`.
#[test]
fn directory_verify_missing_offer() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let child_name = "child".to_string();
let dir_name = CapabilityName("dir_name".to_string());
let child_use_dir =
new_use_directory_decl(UseSource::Parent, dir_name.clone(), Operations::Connect);
let build_tree_result = build_two_node_tree(
vec![],
vec![],
vec![],
vec![UseDecl::Directory(child_use_dir.clone())],
vec![],
vec![],
);
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let child_node =
tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?;
let verify_result = verifier.verify_route(&tree, &child_use_dir, &child_node);
assert!(verify_result.is_err());
assert_eq!(
verify_result.err().unwrap(),
CapabilityRouteError::OfferDeclNotFound("/".to_string(), dir_name.to_string())
);
Ok(())
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` rejects a route when
// a child node has a `UseDirectoryDecl` with a source of Parent, and the parent node
// has a matching `OfferDirectoryDecl` with a source of Self but no matching
// `DirectoryDecl.`
#[test]
fn directory_verify_missing_capability() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let child_name = "child".to_string();
let dir_name = CapabilityName("dir_name".to_string());
let root_offer_dir = new_offer_directory_decl(
OfferSource::Self_,
dir_name.clone(),
OfferTarget::Child(child_name.clone()),
dir_name.clone(),
None,
);
let child_use_dir =
new_use_directory_decl(UseSource::Parent, dir_name.clone(), Operations::Connect);
let build_tree_result = build_two_node_tree(
vec![],
vec![OfferDecl::Directory(root_offer_dir)],
vec![],
vec![UseDecl::Directory(child_use_dir.clone())],
vec![],
vec![],
);
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let child_node =
tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?;
let verify_result = verifier.verify_route(&tree, &child_use_dir, &child_node);
assert!(verify_result.is_err());
assert_eq!(
verify_result.err().unwrap(),
CapabilityRouteError::CapabilityDeclNotFound("/".to_string(), dir_name.to_string())
);
Ok(())
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` rejects a 2-node route when
// the target claims broader rights than the source.
#[test]
fn directory_verify_invalid_rights() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let child_name = "child".to_string();
let dir_name = CapabilityName("dir_name".to_string());
let offer_rights = Operations::Connect;
let use_rights = offer_rights | Operations::ReadBytes;
let root_offer_dir = new_offer_directory_decl(
OfferSource::Self_,
dir_name.clone(),
OfferTarget::Child(child_name.clone()),
dir_name.clone(),
Some(offer_rights),
);
let child_use_dir = new_use_directory_decl(UseSource::Parent, dir_name, use_rights);
let build_tree_result = build_two_node_tree(
vec![],
vec![OfferDecl::Directory(root_offer_dir)],
vec![],
vec![UseDecl::Directory(child_use_dir.clone())],
vec![],
vec![],
);
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let child_node =
tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?;
let verify_result = verifier.verify_route(&tree, &child_use_dir, &child_node);
assert!(verify_result.is_err());
assert_eq!(
verify_result.err().unwrap(),
CapabilityRouteError::InvalidDirectoryRights("/".to_string())
);
Ok(())
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` accepts a 4-node route consisting of
// a use, two offers, and one expose when each node on the route specifies the same rights.
#[test]
fn directory_verify_route_same_rights() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let rights = Operations::Connect;
let build_tree_result =
build_tree_with_directory_route(rights, Some(rights), Some(rights), Some(rights));
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let using_node = tree.get_node(&NodePath::new(vec![
PartialMoniker::new("foo".to_string(), None),
PartialMoniker::new("baz".to_string(), None),
]))?;
assert_eq!(using_node.decl.uses.len(), 1);
let use_decl = &using_node.decl.uses[0];
if let UseDecl::Directory(use_dir_decl) = use_decl {
let route = verifier.verify_route(&tree, &use_dir_decl, &using_node)?;
assert_eq!(route.len(), 5);
assert_eq!(route[0].to_string(), "use by `/foo/baz` as `baz_dir_name` from parent");
assert_eq!(route[1].to_string(), "offer by `/foo` as `baz_dir_name` from parent");
assert_eq!(route[2].to_string(), "offer by `/` as `foo_dir_name` from child `bar`");
assert_eq!(route[3].to_string(), "expose by `/bar` as `root_dir_name` from self");
assert_eq!(route[4].to_string(), "declare by `/bar` as `bar_dir_name`");
Ok(())
} else {
panic!["Failed precondition: expected UseDecl of type Directory"]
}
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` accepts a 4-node route consisting of
// a use, two offers, and one expose when the using node specifies narrower rights than the
// ultimate source node.
#[test]
fn directory_verify_route_narrowed_rights() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let use_rights = Operations::Connect;
let provide_rights = use_rights | Operations::ReadBytes;
let build_tree_result =
build_tree_with_directory_route(use_rights, None, None, Some(provide_rights));
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let using_node = tree.get_node(&NodePath::new(vec![
PartialMoniker::new("foo".to_string(), None),
PartialMoniker::new("baz".to_string(), None),
]))?;
assert_eq!(using_node.decl.uses.len(), 1);
let use_decl = &using_node.decl.uses[0];
if let UseDecl::Directory(use_dir_decl) = use_decl {
let route = verifier.verify_route(&tree, &use_dir_decl, &using_node)?;
assert_eq!(route.len(), 5);
assert_eq!(route[0].to_string(), "use by `/foo/baz` as `baz_dir_name` from parent");
assert_eq!(route[1].to_string(), "offer by `/foo` as `baz_dir_name` from parent");
assert_eq!(route[2].to_string(), "offer by `/` as `foo_dir_name` from child `bar`");
assert_eq!(route[3].to_string(), "expose by `/bar` as `root_dir_name` from self");
assert_eq!(route[4].to_string(), "declare by `/bar` as `bar_dir_name`");
Ok(())
} else {
panic!["Failed precondition: expected UseDecl of type Directory"]
}
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_route()` rejects a 4-node route consisting of
// a use, two offers, and one expose when the ultimate source node specifies narrower rights than
// using node.
#[test]
fn directory_verify_route_expanded_rights() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let provide_rights = Operations::Connect;
let use_rights = provide_rights | Operations::ReadBytes;
let build_tree_result =
build_tree_with_directory_route(use_rights, None, None, Some(provide_rights));
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let using_node = tree.get_node(&NodePath::new(vec![
PartialMoniker::new("foo".to_string(), None),
PartialMoniker::new("baz".to_string(), None),
]))?;
assert_eq!(using_node.decl.uses.len(), 1);
let use_decl = &using_node.decl.uses[0];
if let UseDecl::Directory(use_dir_decl) = use_decl {
let verify_result = verifier.verify_route(&tree, &use_dir_decl, &using_node);
assert!(verify_result.is_err());
assert_eq!(
verify_result.err().unwrap(),
CapabilityRouteError::InvalidDirectoryRights("/bar".to_string())
);
Ok(())
} else {
panic!["Failed precondition: expected UseDecl of type Directory"]
}
}
// Checks that `DirectoryCapabilityRouteVerifier::verify_all_routes()` accepts a valid route and
// rejects an invalid route.
#[test]
fn directory_verify_all_routes() -> Result<(), CapabilityRouteError> {
let verifier = DirectoryCapabilityRouteVerifier::new();
let child_name = "child".to_string();
let offer_rights = Operations::Connect;
let bad_use_rights = offer_rights | Operations::ReadBytes;
let good_dir_name = CapabilityName("good_dir".to_string());
let bad_dir_name = CapabilityName("bad_dir".to_string());
let good_offer_dir = new_offer_directory_decl(
OfferSource::Self_,
good_dir_name.clone(),
OfferTarget::Child(child_name.clone()),
good_dir_name.clone(),
Some(offer_rights),
);
let bad_offer_dir = new_offer_directory_decl(
OfferSource::Self_,
bad_dir_name.clone(),
OfferTarget::Child(child_name.clone()),
bad_dir_name.clone(),
Some(offer_rights),
);
let good_directory_decl = new_directory_decl(good_dir_name.clone(), offer_rights);
let bad_directory_decl = new_directory_decl(bad_dir_name.clone(), offer_rights);
let good_use_dir =
new_use_directory_decl(UseSource::Parent, good_dir_name.clone(), offer_rights);
let bad_use_dir =
new_use_directory_decl(UseSource::Parent, bad_dir_name.clone(), bad_use_rights);
let build_tree_result = build_two_node_tree(
vec![],
vec![OfferDecl::Directory(good_offer_dir), OfferDecl::Directory(bad_offer_dir)],
vec![
CapabilityDecl::Directory(good_directory_decl),
CapabilityDecl::Directory(bad_directory_decl),
],
vec![UseDecl::Directory(good_use_dir), UseDecl::Directory(bad_use_dir)],
vec![],
vec![],
);
assert!(build_tree_result.tree.is_some());
let tree = build_tree_result.tree.unwrap();
let child_node =
tree.get_node(&NodePath::new(vec![PartialMoniker::new(child_name, None)]))?;
let mut results = verifier.verify_all_routes(&tree, &child_node);
assert_eq!(results.len(), 2);
let good_result = results.remove(0);
let bad_result = results.remove(0);
assert_eq!(good_result.using_node.to_string(), "/child");
assert_eq!(good_result.capability, good_dir_name);
assert!(good_result.result.is_ok());
let good_route = good_result.result.as_ref().unwrap();
assert_eq!(good_route.len(), 3);
assert_eq!(good_route[0].to_string(), "use by `/child` as `good_dir` from parent");
assert_eq!(good_route[1].to_string(), "offer by `/` as `good_dir` from self");
assert_eq!(good_route[2].to_string(), "declare by `/` as `good_dir`");
assert_eq!(bad_result.using_node.to_string(), "/child");
assert_eq!(bad_result.capability, bad_dir_name);
assert!(bad_result.result.is_err());
assert_eq!(
*bad_result.result.err().as_ref().unwrap(),
CapabilityRouteError::InvalidDirectoryRights("/".to_string())
);
Ok(())
}
}