| // 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::{ |
| capability::{ |
| CapabilityProvider, CapabilitySource, ComponentCapability, InternalCapability, |
| OptionalTask, |
| }, |
| channel, |
| config::{CapabilityAllowlistKey, CapabilityAllowlistSource}, |
| framework::REALM_SERVICE, |
| model::{ |
| actions::{ActionSet, DeleteChildAction, DestroyAction, ShutdownAction}, |
| error::ModelError, |
| events::registry::EventSubscription, |
| hooks::{Event, EventPayload, EventType, Hook, HooksRegistration}, |
| rights, |
| routing::{self, RouteRequest, RouteSource, RoutingError}, |
| testing::{routing_test_helpers::*, test_helpers::*}, |
| }, |
| }, |
| ::routing::error::ComponentInstanceError, |
| anyhow::Error, |
| async_trait::async_trait, |
| cm_rust::*, |
| fidl::endpoints::ServerEnd, |
| fidl_fidl_examples_echo::{self as echo}, |
| fidl_fuchsia_component_runner as fcrunner, fidl_fuchsia_mem as fmem, fidl_fuchsia_sys2 as fsys, |
| fuchsia_async as fasync, fuchsia_zircon as zx, |
| futures::{join, lock::Mutex, StreamExt, TryStreamExt}, |
| log::*, |
| maplit::hashmap, |
| matches::assert_matches, |
| moniker::{AbsoluteMoniker, ExtendedMoniker}, |
| std::{ |
| collections::HashSet, |
| convert::{TryFrom, TryInto}, |
| path::{Path, PathBuf}, |
| sync::{Arc, Weak}, |
| }, |
| vfs::pseudo_directory, |
| }; |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// b: uses framework service /svc/fuchsia.sys2.Realm |
| #[fuchsia::test] |
| async fn use_framework_service() { |
| pub struct MockRealmCapabilityProvider { |
| scope_moniker: AbsoluteMoniker, |
| host: MockRealmCapabilityHost, |
| } |
| |
| impl MockRealmCapabilityProvider { |
| pub fn new(scope_moniker: AbsoluteMoniker, host: MockRealmCapabilityHost) -> Self { |
| Self { scope_moniker, host } |
| } |
| } |
| |
| #[async_trait] |
| impl CapabilityProvider for MockRealmCapabilityProvider { |
| async fn open( |
| self: Box<Self>, |
| _flags: u32, |
| _open_mode: u32, |
| _relative_path: PathBuf, |
| server_end: &mut zx::Channel, |
| ) -> Result<OptionalTask, ModelError> { |
| let server_end = channel::take_channel(server_end); |
| let stream = ServerEnd::<fsys::RealmMarker>::new(server_end) |
| .into_stream() |
| .expect("could not convert channel into stream"); |
| let scope_moniker = self.scope_moniker.clone(); |
| let host = self.host.clone(); |
| Ok(fasync::Task::spawn(async move { |
| if let Err(e) = host.serve(scope_moniker, stream).await { |
| // TODO: Set an epitaph to indicate this was an unexpected error. |
| warn!("serve_realm failed: {}", e); |
| } |
| }) |
| .into()) |
| } |
| } |
| |
| #[async_trait] |
| impl Hook for MockRealmCapabilityHost { |
| async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> { |
| if let Ok(EventPayload::CapabilityRouted { |
| source: CapabilitySource::Framework { capability, component }, |
| capability_provider, |
| }) = &event.result |
| { |
| let mut capability_provider = capability_provider.lock().await; |
| *capability_provider = self |
| .on_scoped_framework_capability_routed_async( |
| component.moniker.clone(), |
| &capability, |
| capability_provider.take(), |
| ) |
| .await?; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct MockRealmCapabilityHost { |
| /// List of calls to `BindChild` with component's relative moniker. |
| bind_calls: Arc<Mutex<Vec<String>>>, |
| } |
| |
| impl MockRealmCapabilityHost { |
| pub fn new() -> Self { |
| Self { bind_calls: Arc::new(Mutex::new(vec![])) } |
| } |
| |
| pub fn bind_calls(&self) -> Arc<Mutex<Vec<String>>> { |
| self.bind_calls.clone() |
| } |
| |
| async fn serve( |
| &self, |
| scope_moniker: AbsoluteMoniker, |
| mut stream: fsys::RealmRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(request) = stream.try_next().await? { |
| match request { |
| fsys::RealmRequest::BindChild { responder, .. } => { |
| self.bind_calls.lock().await.push( |
| scope_moniker |
| .path() |
| .last() |
| .expect("did not expect root component") |
| .name() |
| .to_string(), |
| ); |
| responder.send(&mut Ok(()))?; |
| } |
| _ => {} |
| } |
| } |
| Ok(()) |
| } |
| |
| pub async fn on_scoped_framework_capability_routed_async<'a>( |
| &'a self, |
| scope_moniker: AbsoluteMoniker, |
| capability: &'a InternalCapability, |
| capability_provider: Option<Box<dyn CapabilityProvider>>, |
| ) -> Result<Option<Box<dyn CapabilityProvider>>, ModelError> { |
| // If some other capability has already been installed, then there's nothing to |
| // do here. |
| if capability.matches_protocol(&REALM_SERVICE) { |
| Ok(Some(Box::new(MockRealmCapabilityProvider::new( |
| scope_moniker.clone(), |
| self.clone(), |
| )) as Box<dyn CapabilityProvider>)) |
| } else { |
| Ok(capability_provider) |
| } |
| } |
| } |
| |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| // RoutingTest installs the real RealmCapabilityHost. Installing the |
| // MockRealmCapabilityHost here overrides the previously installed one. |
| let realm_service_host = Arc::new(MockRealmCapabilityHost::new()); |
| test.model |
| .root |
| .hooks |
| .install(vec![HooksRegistration::new( |
| "MockRealmCapabilityHost", |
| vec![EventType::CapabilityRouted], |
| Arc::downgrade(&realm_service_host) as Weak<dyn Hook>, |
| )]) |
| .await; |
| test.check_use_realm(vec!["b:0"].into(), realm_service_host.bind_calls()).await; |
| } |
| |
| /// 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. |
| #[fuchsia::test] |
| async fn use_from_parent() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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::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::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "device".into(), |
| target_path: CapabilityPath::try_from("/svc/device").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["b:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| test.check_open_file(vec!["b:0"].into(), "/svc/device".try_into().unwrap()).await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: offers service /svc/foo from self as /svc/bar |
| /// b: uses service /svc/bar as /svc/hippo |
| /// |
| /// This test verifies that the parent, if subscribed to the CapabilityRequested event will receive |
| /// if when the child connects to /svc/hippo. |
| #[fuchsia::test] |
| async fn capability_requested_event_at_parent() { |
| 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: "bar_svc".into(), |
| target: OfferTarget::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "capability_requested".into(), |
| target_name: "capability_requested".into(), |
| filter: Some(hashmap!{"name".to_string() => DictionaryValue::Str("foo_svc".to_string())}), |
| mode: cm_rust::EventMode::Async, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| |
| let namespace_root = test.bind_and_get_namespace(AbsoluteMoniker::root()).await; |
| let mut event_stream = capability_util::subscribe_to_events( |
| &namespace_root, |
| &CapabilityPath::try_from("/svc/fuchsia.sys2.EventSource").unwrap(), |
| vec![EventSubscription::new("capability_requested".into(), EventMode::Async)], |
| ) |
| .await |
| .unwrap(); |
| |
| let namespace_b = test.bind_and_get_namespace(vec!["b:0"].into()).await; |
| let _echo_proxy = capability_util::connect_to_svc_in_namespace::<echo::EchoMarker>( |
| &namespace_b, |
| &"/svc/hippo".try_into().unwrap(), |
| ) |
| .await; |
| |
| let event = match event_stream.next().await { |
| Some(Ok(fsys::EventStreamRequest::OnEvent { event, .. })) => event, |
| _ => panic!("Event not found"), |
| }; |
| |
| // 'b' is the target and 'a' is receiving the event so the relative moniker |
| // is './b:0'. |
| assert_matches!(&event, |
| fsys::Event { |
| header: Some(fsys::EventHeader { |
| moniker: Some(moniker), .. }), .. |
| } if *moniker == "./b:0".to_string() ); |
| |
| assert_matches!(&event, |
| fsys::Event { |
| header: Some(fsys::EventHeader { |
| component_url: Some(component_url), .. }), .. |
| } if *component_url == "test:///b".to_string() ); |
| |
| assert_matches!(&event, |
| fsys::Event { |
| event_result: Some( |
| fsys::EventResult::Payload( |
| fsys::EventPayload::CapabilityRequested( |
| fsys::CapabilityRequestedPayload { name: Some(name), .. }))), ..} |
| |
| if *name == "foo_svc".to_string() |
| ); |
| } |
| |
| /// 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 |
| #[fuchsia::test] |
| async fn use_from_grandparent() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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::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::Child("c".to_string()), |
| rights: Some(*rights::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::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "baz_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["b:0", "c:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].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 |
| #[fuchsia::test] |
| async fn use_builtin_from_grandparent() { |
| 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::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::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(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0", "c:0"].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 |
| #[fuchsia::test] |
| async fn use_from_sibling_no_root() { |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("d".to_string()), |
| source_name: "bar_data".into(), |
| target_name: "foobar_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("d".to_string()), |
| source_name: "bar_svc".into(), |
| target_name: "foobar_svc".into(), |
| target: OfferTarget::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foobar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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(*rights::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 test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["b:0", "c:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].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 |
| #[fuchsia::test] |
| async fn use_from_sibling_root() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "bar_data".into(), |
| target_name: "baz_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "bar_svc".into(), |
| target_name: "baz_svc".into(), |
| target: OfferTarget::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(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "baz_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["c:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await; |
| test.check_use( |
| vec!["c:0"].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 |
| #[fuchsia::test] |
| async fn use_from_niece() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "baz_data".into(), |
| target_name: "foobar_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "baz_svc".into(), |
| target_name: "foobar_svc".into(), |
| target: OfferTarget::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(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foobar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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(*rights::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 test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["c:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await; |
| test.check_use( |
| vec!["c:0"].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 |
| #[fuchsia::test] |
| async fn use_kitchen_sink() { |
| 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::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "foo_from_d_data".into(), |
| target_name: "foo_from_d_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::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::Child("d".to_string()), |
| source_name: "foo_from_d_data".into(), |
| target_name: "foo_from_d_data".into(), |
| target: OfferTarget::Child("e".to_string()), |
| rights: Some(*rights::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::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(*rights::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::Child("f".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("g".to_string()), |
| source_name: "foo_from_h_svc".into(), |
| target_name: "foo_from_h_svc".into(), |
| target: OfferTarget::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(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foo_from_a_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foo_from_h_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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 test = RoutingTest::new("a", components).await; |
| test.check_use(vec!["b:0", "e:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)) |
| .await; |
| test.check_use( |
| vec!["b:0", "e:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| test.check_use(vec!["c:0", "f:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)) |
| .await; |
| test.check_use( |
| vec!["c:0", "f:0"].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 |
| #[fuchsia::test] |
| async fn use_from_component_manager_namespace() { |
| 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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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 test = RoutingTestBuilder::new("a", components) |
| .set_namespace_capabilities(namespace_capabilities) |
| .build() |
| .await; |
| |
| let _ns_dir = ScopedNamespaceDir::new(&test, "/use_from_cm_namespace"); |
| test.check_use(vec![].into(), CheckUse::default_directory(ExpectedResult::Ok)).await; |
| test.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 |
| #[fuchsia::test] |
| async fn offer_from_component_manager_namespace() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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 test = RoutingTestBuilder::new("a", components) |
| .set_namespace_capabilities(namespace_capabilities) |
| .build() |
| .await; |
| let _ns_dir = ScopedNamespaceDir::new(&test, "/offer_from_cm_namespace"); |
| test.check_use(vec!["b:0"].into(), CheckUse::default_directory(ExpectedResult::Ok)).await; |
| test.check_use( |
| vec!["b:0"].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 |
| #[fuchsia::test] |
| async fn use_not_offered() { |
| 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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0"].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 |
| #[fuchsia::test] |
| async fn use_offer_source_not_exposed() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new_empty_component() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source_name: "hippo_data".into(), |
| source: OfferSource::Child("b".to_string()), |
| target_name: "hippo_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source_name: "hippo_svc".into(), |
| source: OfferSource::Child("b".to_string()), |
| target_name: "hippo_svc".into(), |
| target: OfferTarget::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["c:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["c:0"].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 |
| #[fuchsia::test] |
| async fn use_offer_source_not_offered() { |
| 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::Child("c".to_string()), |
| rights: Some(*rights::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::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].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 |
| #[fuchsia::test] |
| async fn use_from_expose() { |
| 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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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(*rights::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 test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// 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 |
| #[fuchsia::test] |
| async fn use_from_expose_to_framework() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "bar_data".into(), |
| target_name: "baz_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "bar_svc".into(), |
| target_name: "baz_svc".into(), |
| target: OfferTarget::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(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "baz_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["c:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["c:0"].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 |
| #[fuchsia::test] |
| async fn offer_from_non_executable() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// / \ |
| /// [c] [d] |
| /// a: offers service /svc/hippo to b |
| /// b: offers service /svc/hippo to collection, creates [c] |
| /// [c]: instance in collection uses service /svc/hippo |
| /// [d]: ditto, but with /data/hippo |
| #[fuchsia::test] |
| async fn use_in_collection() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .directory(DirectoryDeclBuilder::new("foo_data").build()) |
| .protocol(ProtocolDeclBuilder::new("foo_svc").build()) |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source_name: "foo_data".into(), |
| source: OfferSource::Self_, |
| target_name: "hippo_data".into(), |
| target: OfferTarget::Child("b".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source_name: "foo_svc".into(), |
| source: OfferSource::Self_, |
| target_name: "hippo_svc".into(), |
| target: OfferTarget::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source_name: "hippo_data".into(), |
| source: OfferSource::Parent, |
| target_name: "hippo_data".into(), |
| target: OfferTarget::Collection("coll".to_string()), |
| rights: Some(*rights::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::Collection("coll".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_transient_collection("coll") |
| .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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_dynamic_child( |
| vec!["b:0"].into(), |
| "coll", |
| ChildDecl { |
| name: "c".to_string(), |
| url: "test:///c".to_string(), |
| startup: fsys::StartupMode::Lazy, |
| environment: None, |
| }, |
| ) |
| .await; |
| test.create_dynamic_child( |
| vec!["b:0"].into(), |
| "coll", |
| ChildDecl { |
| name: "d".to_string(), |
| url: "test:///d".to_string(), |
| startup: fsys::StartupMode::Lazy, |
| environment: None, |
| }, |
| ) |
| .await; |
| test.check_use(vec!["b:0", "coll:c:1"].into(), CheckUse::default_directory(ExpectedResult::Ok)) |
| .await; |
| test.check_use( |
| vec!["b:0", "coll:d:2"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// [c] |
| /// a: offers service /svc/hippo to b |
| /// b: creates [c] |
| /// [c]: tries to use /svc/hippo, but can't because service was not offered to its collection |
| #[fuchsia::test] |
| async fn use_in_collection_not_offered() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .directory(DirectoryDeclBuilder::new("foo_data").build()) |
| .protocol(ProtocolDeclBuilder::new("foo_svc").build()) |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source_name: "foo_data".into(), |
| source: OfferSource::Self_, |
| target_name: "hippo_data".into(), |
| target: OfferTarget::Child("b".to_string()), |
| rights: Some(*rights::READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source_name: "foo_svc".into(), |
| source: OfferSource::Self_, |
| target_name: "hippo_svc".into(), |
| target: OfferTarget::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .add_transient_collection("coll") |
| .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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_dynamic_child( |
| vec!["b:0"].into(), |
| "coll", |
| ChildDecl { |
| name: "c".to_string(), |
| url: "test:///c".to_string(), |
| startup: fsys::StartupMode::Lazy, |
| environment: None, |
| }, |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "coll:c:1"].into(), |
| CheckUse::default_directory(ExpectedResult::Err(zx::Status::UNAVAILABLE)), |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "coll:c:1"].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 |
| #[fuchsia::test] |
| async fn use_directory_with_subdir_from_grandparent() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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::Child("c".to_string()), |
| rights: Some(*rights::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: *rights::READ_RIGHTS, |
| subdir: Some(PathBuf::from("s4")), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_static_file(Path::new("foo/s1/s2/s3/s4/inner"), "hello") |
| .await |
| .expect("failed to create file"); |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Directory { |
| path: default_directory_capability(), |
| file: PathBuf::from("inner"), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| } |
| |
| #[fuchsia::test] |
| async fn destroying_instance_kills_framework_service_task() { |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| |
| // Connect to `Realm`, which is a framework service. |
| let namespace = test.bind_and_get_namespace(vec!["b:0"].into()).await; |
| let proxy = capability_util::connect_to_svc_in_namespace::<fsys::RealmMarker>( |
| &namespace, |
| &"/svc/fuchsia.sys2.Realm".try_into().unwrap(), |
| ) |
| .await; |
| |
| // Destroy `b`. This should cause the task hosted for `Realm` to be cancelled. |
| let root = test.model.look_up(&vec![].into()).await.unwrap(); |
| ActionSet::register(root.clone(), DeleteChildAction::new("b:0".into())) |
| .await |
| .expect("destroy failed"); |
| let mut event_stream = proxy.take_event_stream(); |
| assert_matches!(event_stream.next().await, None); |
| } |
| |
| /// 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 |
| #[fuchsia::test] |
| async fn use_directory_with_subdir_from_sibling() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "foo_data".into(), |
| target: OfferTarget::Child("c".to_string()), |
| target_name: "foo_data".into(), |
| rights: Some(*rights::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(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_static_file(Path::new("foo/s1/s2/s3/inner"), "hello") |
| .await |
| .expect("failed to create file"); |
| test.check_use( |
| vec!["c:0"].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 |
| #[fuchsia::test] |
| async fn expose_directory_with_subdir() { |
| 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(*rights::READ_RIGHTS), |
| subdir: None, |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_static_file(Path::new("foo/s1/s2/s3/inner"), "hello") |
| .await |
| .expect("failed to create file"); |
| test.check_use_exposed_dir( |
| vec![].into(), |
| CheckUse::Directory { |
| path: "/hippo_data".try_into().unwrap(), |
| file: PathBuf::from("inner"), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| } |
| |
| /// 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". |
| #[fuchsia::test] |
| async fn use_runner_from_parent_environment() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "hobbit".into(), |
| }) |
| .build(), |
| ) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: CapabilityPath::try_from("/svc/runner").unwrap(), |
| }) |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, mut receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "b" exposes a runner service. |
| .add_outgoing_path("a", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "b:0". We expect to see a call to our runner service for the new component. |
| async move { |
| universe.bind_instance(&vec!["b:0"].into()).await.unwrap(); |
| }, |
| // Wait for a request, and ensure it has the correct URL. |
| async move { |
| assert_eq!( |
| wait_for_runner_request(&mut receiver).await.resolved_url, |
| Some("test:///b_resolved".to_string()) |
| ); |
| } |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// [b] |
| /// |
| /// a: declares runner "elf" with service "/svc/runner" from "self". |
| /// a: registers runner "elf" from self in environment as "hobbit". |
| /// b: instance in collection uses runner "hobbit". |
| #[fuchsia::test] |
| async fn use_runner_from_environment_in_collection() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_collection( |
| CollectionDeclBuilder::new_transient_collection("coll") |
| .environment("env") |
| .build(), |
| ) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "hobbit".into(), |
| }) |
| .build(), |
| ) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: CapabilityPath::try_from("/svc/runner").unwrap(), |
| }) |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, mut receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a runner service. |
| .add_outgoing_path("a", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| universe |
| .create_dynamic_child( |
| AbsoluteMoniker::root(), |
| "coll", |
| ChildDecl { |
| name: "b".to_string(), |
| url: "test:///b".to_string(), |
| startup: fsys::StartupMode::Lazy, |
| environment: None, |
| }, |
| ) |
| .await; |
| |
| join!( |
| // Bind "coll:b:1". We expect to see a call to our runner service for the new component. |
| async move { |
| universe.bind_instance(&vec!["coll:b:1"].into()).await.unwrap(); |
| }, |
| // Wait for a request, and ensure it has the correct URL. |
| async move { |
| assert_eq!( |
| wait_for_runner_request(&mut receiver).await.resolved_url, |
| Some("test:///b_resolved".to_string()) |
| ); |
| } |
| ); |
| } |
| |
| /// 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". |
| #[fuchsia::test] |
| async fn use_runner_from_grandparent_environment() { |
| 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::Child("b".to_string()), |
| target_name: CapabilityName("dwarf".to_string()), |
| })) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: 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(fsys::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()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, mut receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a runner service. |
| .add_outgoing_path("a", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "c:0". We expect to see a call to our runner service for the new component. |
| async move { |
| universe.bind_instance(&vec!["b:0", "c:0"].into()).await.unwrap(); |
| }, |
| // Wait for a request, and ensure it has the correct URL. |
| async move { |
| assert_eq!( |
| wait_for_runner_request(&mut receiver).await.resolved_url, |
| Some("test:///c_resolved".to_string()) |
| ); |
| } |
| ); |
| } |
| |
| /// 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". |
| #[fuchsia::test] |
| async fn use_runner_from_sibling_environment() { |
| 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(fsys::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: CapabilityPath::try_from("/svc/runner").unwrap(), |
| }) |
| .build(), |
| ), |
| ("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, mut receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a runner service. |
| .add_outgoing_path("b", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "c:0". We expect to see a call to our runner service for the new component. |
| async move { |
| universe.bind_instance(&vec!["c:0"].into()).await.unwrap(); |
| }, |
| // Wait for a request, and ensure it has the correct URL. |
| async move { |
| assert_eq!( |
| wait_for_runner_request(&mut receiver).await.resolved_url, |
| Some("test:///c_resolved".to_string()) |
| ); |
| } |
| ); |
| } |
| |
| /// 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". |
| #[fuchsia::test] |
| async fn use_runner_from_inherited_environment() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "hobbit".into(), |
| }) |
| .build(), |
| ) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: 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(fsys::EnvironmentExtends::Realm) |
| .build(), |
| ) |
| .build(), |
| ), |
| ("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, mut receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a runner service. |
| .add_outgoing_path("a", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "c:0". We expect to see a call to our runner service for the new component. |
| async move { |
| universe.bind_instance(&vec!["b:0", "c:0"].into()).await.unwrap(); |
| }, |
| // Wait for a request, and ensure it has the correct URL. |
| async move { |
| assert_eq!( |
| wait_for_runner_request(&mut receiver).await.resolved_url, |
| Some("test:///c_resolved".to_string()) |
| ); |
| } |
| ); |
| } |
| |
| /// 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. |
| #[fuchsia::test] |
| async fn use_runner_from_environment_not_found() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "dwarf".into(), |
| }) |
| .build(), |
| ) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: CapabilityPath::try_from("/svc/runner").unwrap(), |
| }) |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| // Set up the system. |
| let (runner_service, _receiver) = |
| create_service_directory_entry::<fcrunner::ComponentRunnerMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a runner service. |
| .add_outgoing_path("a", CapabilityPath::try_from("/svc/runner").unwrap(), runner_service) |
| .build() |
| .await; |
| |
| // Bind "b:0". We expect it to fail because routing failed. |
| assert_matches!( |
| universe.bind_instance(&vec!["b:0"].into()).await, |
| Err(ModelError::RoutingError { |
| err: RoutingError::UseFromEnvironmentNotFound { |
| moniker, |
| capability_type, |
| capability_name, |
| } |
| }) |
| if moniker == AbsoluteMoniker::from(vec!["b:0"]) && |
| capability_type == "runner" && |
| capability_name == CapabilityName("hobbit".to_string())); |
| } |
| |
| // TODO: Write a test for environment that extends from None. Currently, this is not |
| // straightforward because resolver routing is not implemented yet, which makes it impossible to |
| // register a new resolver and have it be usable. |
| |
| #[fuchsia::test] |
| async fn expose_from_self_and_child() { |
| 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(*rights::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(*rights::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 test = RoutingTest::new("a", components).await; |
| test.check_use_exposed_dir( |
| vec!["b:0"].into(), |
| CheckUse::Directory { |
| path: "/hippo_bar_data".try_into().unwrap(), |
| file: PathBuf::from("hippo"), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { |
| path: "/hippo_bar_svc".try_into().unwrap(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Directory { |
| path: "/hippo_data".try_into().unwrap(), |
| file: PathBuf::from("hippo"), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { |
| path: "/hippo_svc".try_into().unwrap(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| } |
| |
| #[fuchsia::test] |
| async fn use_not_exposed() { |
| 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(*rights::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 test = RoutingTest::new("a", components).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. |
| test.check_use_exposed_dir( |
| vec!["b:0"].into(), |
| CheckUse::Directory { |
| path: "/hippo_data".try_into().unwrap(), |
| file: PathBuf::from("hippo"), |
| expected_res: ExpectedResult::Err(zx::Status::NOT_FOUND), |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { |
| path: "/hippo_svc".try_into().unwrap(), |
| expected_res: ExpectedResult::Err(zx::Status::NOT_FOUND), |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Directory { |
| path: "/hippo_data".try_into().unwrap(), |
| file: PathBuf::from("hippo"), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use_exposed_dir( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { |
| path: "/hippo_svc".try_into().unwrap(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// [b] |
| /// \ |
| /// c |
| /// |
| /// a: offers service /svc/foo from self |
| /// [b]: offers service /svc/foo to c |
| /// [b]: is destroyed |
| /// c: uses service /svc/foo, which should fail |
| #[fuchsia::test] |
| async fn use_with_destroyed_parent() { |
| let use_protocol_decl = UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "foo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| }; |
| let use_decl = UseDecl::Protocol(use_protocol_decl.clone()); |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .protocol(ProtocolDeclBuilder::new("foo_svc").build()) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Self_, |
| source_name: "foo_svc".into(), |
| target_name: "foo_svc".into(), |
| target: OfferTarget::Collection("coll".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_transient_collection("coll") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Parent, |
| source_name: "foo_svc".into(), |
| target_name: "foo_svc".into(), |
| target: OfferTarget::Child("c".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ("c", ComponentDeclBuilder::new().use_(use_decl.clone()).build()), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.create_dynamic_child( |
| vec![].into(), |
| "coll", |
| ChildDecl { |
| name: "b".to_string(), |
| url: "test:///b".to_string(), |
| startup: fsys::StartupMode::Lazy, |
| environment: None, |
| }, |
| ) |
| .await; |
| |
| // Confirm we can use service from "c". |
| test.check_use( |
| vec!["coll:b:1", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| // Destroy "b", but preserve a reference to "c" so we can route from it below. |
| let moniker = vec!["coll:b:1", "c:0"].into(); |
| let realm_c = test.model.look_up(&moniker).await.expect("failed to look up realm b"); |
| test.destroy_dynamic_child(vec![].into(), "coll", "b").await; |
| |
| // Now attempt to route the service from "c". Should fail because "b" does not exist so we |
| // cannot follow it. |
| let err = routing::route_capability(RouteRequest::UseProtocol(use_protocol_decl), &realm_c) |
| .await |
| .expect_err("routing unexpectedly succeeded"); |
| assert_matches!( |
| err, |
| RoutingError::ComponentInstanceError( |
| ComponentInstanceError::InstanceNotFound { moniker } |
| ) if moniker == vec!["coll:b:1"].into() |
| ); |
| } |
| |
| /// 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, which was destroyed (but not removed |
| /// from the tree yet) |
| /// c: uses /data/baz as /data/hippo |
| #[fuchsia::test] |
| async fn use_from_destroyed_but_not_removed() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Child("b".to_string()), |
| source_name: "bar_svc".into(), |
| target_name: "baz_svc".into(), |
| target: OfferTarget::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::Protocol(ExposeProtocolDecl { |
| source: ExposeSource::Self_, |
| source_name: "foo_svc".into(), |
| target_name: "bar_svc".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "baz_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| let component_b = |
| test.model.look_up(&vec!["b:0"].into()).await.expect("failed to look up realm b"); |
| // Destroy `b` but keep alive its reference from the parent. |
| // TODO: If we had a "pre-destroy" event we could delete the child through normal means and |
| // block on the event instead of explicitly registering actions. |
| ActionSet::register(component_b.clone(), ShutdownAction::new()).await.expect("shutdown failed"); |
| ActionSet::register(component_b, DestroyAction::new()).await.expect("destroy failed"); |
| test.check_use( |
| vec!["c:0"].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// (cm) |
| /// | |
| /// a |
| /// |
| /// a: uses an invalid service from the component manager. |
| #[fuchsia::test] |
| async fn invalid_use_from_component_manager() { |
| 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(), |
| })) |
| .build(), |
| )]; |
| |
| // Try and use the service. We expect a failure. |
| let universe = RoutingTest::new("a", components).await; |
| universe |
| .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 |
| #[fuchsia::test] |
| async fn invalid_offer_from_component_manager() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Protocol(OfferProtocolDecl { |
| source_name: "invalid".into(), |
| source: OfferSource::Parent, |
| target_name: "valid".into(), |
| target: OfferTarget::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(), |
| })) |
| .build(), |
| ), |
| ]; |
| |
| // Try and use the service. We expect a failure. |
| let universe = RoutingTest::new("a", components).await; |
| universe |
| .check_use( |
| vec!["b:0"].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" |
| #[fuchsia::test] |
| async fn use_event_from_framework() { |
| 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::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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "capability_requested".into(), |
| target_name: "capability_requested".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "started".into(), |
| target_name: "started".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![ |
| EventSubscription::new("capability_requested".into(), EventMode::Sync), |
| EventSubscription::new("started".into(), EventMode::Sync), |
| ], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a; attempts to offer event "capability_requested" to b. |
| #[fuchsia::test] |
| async fn can_offer_capability_requested_event() { |
| 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::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::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(), |
| })) |
| .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, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![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" |
| #[fuchsia::test] |
| async fn use_event_from_parent() { |
| 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::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::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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "started_on_a".into(), |
| target_name: "started".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![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 |
| #[fuchsia::test] |
| async fn use_event_from_grandparent() { |
| 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::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::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::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::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::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::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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "started_on_a".into(), |
| target_name: "started".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .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, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "stopped_on_a".into(), |
| target_name: "stopped".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![ |
| EventSubscription::new("started".into(), EventMode::Sync), |
| EventSubscription::new("destroyed".into(), EventMode::Sync), |
| ], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new("stopped".into(), EventMode::Sync)], |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// | |
| /// b |
| /// / \ |
| /// c d |
| /// |
| /// a: offer framework event "capability_ready" with filters "/foo", "/bar", "/baz" to b |
| /// b: uses realm event "capability_ready" with filters "/foo" |
| /// b: offers realm event "capabilty_ready" with filters "/foo", "/bar" to c, d |
| /// c: uses realm event "capability_ready" with filters "/foo", "/bar" |
| /// d: uses realm event "capability_ready" with filters "/baz" (fails) |
| #[fuchsia::test] |
| async fn event_filter_routing() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Framework, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::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::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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready_foo".into(), |
| filter: Some(hashmap! { |
| "name".to_string() => DictionaryValue::Str("foo".into()), |
| }), |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| 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::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::Child("d".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::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: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::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 { |
| source: UseSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_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 { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| 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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready_baz".into(), |
| filter: Some(hashmap! { |
| "name".to_string() => DictionaryValue::Str("baz".into()), |
| }), |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new("capability_ready_foo".into(), EventMode::Sync)], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new( |
| "capability_ready_foo_bar".into(), |
| EventMode::Sync, |
| )], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "d:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new("capability_ready_baz".into(), EventMode::Sync)], |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// | |
| /// b |
| /// | |
| /// c |
| /// |
| /// a: offer framework event "capability_ready" with mode "async". |
| /// b: offers parent event "capabilty_ready" with mode "sync". |
| /// c: uses realm event "capability_ready" with mode "sync" |
| #[fuchsia::test] |
| async fn event_mode_routing_failure() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Framework, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::Child("b".to_string()), |
| filter: None, |
| mode: cm_rust::EventMode::Async, |
| })) |
| .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::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::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::Child("c".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready_foo_bar".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new( |
| "capability_ready_foo_bar".into(), |
| EventMode::Sync, |
| )], |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// | |
| /// b |
| /// | |
| /// c |
| /// |
| /// a: offer framework event "capability_ready" with mode "sync". |
| /// b: offers parent event "capabilty_ready" with mode "async". |
| /// c: uses realm event "capability_ready" with mode "async" |
| #[fuchsia::test] |
| async fn event_mode_routing_success() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Framework, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::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::Child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready".into(), |
| target: OfferTarget::Child("c".to_string()), |
| filter: None, |
| mode: cm_rust::EventMode::Async, |
| })) |
| .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::Child("c".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "capability_ready".into(), |
| target_name: "capability_ready_foo_bar".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Async, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTest::new("a", components).await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new( |
| "capability_ready_foo_bar".into(), |
| EventMode::Async, |
| )], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new( |
| "capability_ready_foo_bar".into(), |
| EventMode::Sync, |
| )], |
| expected_res: ExpectedResult::Err(zx::Status::UNAVAILABLE), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// |
| /// a: creates environment "env" and registers resolver "base" from c. |
| /// b: resolved by resolver "base" through "env". |
| /// b: exposes resolver "base" from self. |
| #[fuchsia::test] |
| async fn use_resolver_from_parent_environment() { |
| // Note that we do not define a component "b". This will be resolved by our custom resolver. |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new_empty_component() |
| .add_child(ChildDeclBuilder::new().name("b").url("base://b").environment("env")) |
| .add_child(ChildDeclBuilder::new_lazy_child("c")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Child("c".into()), |
| scheme: "base".into(), |
| }), |
| ) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Resolver(ExposeResolverDecl { |
| source: ExposeSource::Self_, |
| source_name: "base".into(), |
| target: ExposeTarget::Parent, |
| target_name: "base".into(), |
| })) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: "/svc/fuchsia.sys2.ComponentResolver".parse().unwrap(), |
| }) |
| .build(), |
| ), |
| ]; |
| |
| // Set up the system. |
| let (resolver_service, mut receiver) = |
| create_service_directory_entry::<fsys::ComponentResolverMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "c" exposes a resolver service. |
| .add_outgoing_path( |
| "c", |
| CapabilityPath::try_from("/svc/fuchsia.sys2.ComponentResolver").unwrap(), |
| resolver_service, |
| ) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "b:0". We expect to see a call to our resolver service for the new component. |
| async move { |
| universe |
| .bind_instance(&vec!["b:0"].into()) |
| .await |
| .expect("failed to bind to instance b:0"); |
| }, |
| // Wait for a request, and resolve it. |
| async { |
| while let Some(fsys::ComponentResolverRequest::Resolve { component_url, responder }) = |
| receiver.next().await |
| { |
| assert_eq!(component_url, "base://b"); |
| responder |
| .send(&mut Ok(fsys::Component { |
| resolved_url: Some("test://b".into()), |
| decl: Some(fmem::Data::Bytes( |
| fidl::encoding::encode_persistent( |
| &mut default_component_decl().native_into_fidl(), |
| ) |
| .unwrap(), |
| )), |
| package: None, |
| ..fsys::Component::EMPTY |
| })) |
| .expect("failed to send resolve response"); |
| } |
| } |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// c |
| /// a: creates environment "env" and registers resolver "base" from self. |
| /// b: has environment "env". |
| /// c: is resolved by resolver from grandarent. |
| #[fuchsia::test] |
| async fn use_resolver_from_grandparent_environment() { |
| // Note that we do not define a component "c". This will be resolved by our custom resolver. |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }), |
| ) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: "/svc/fuchsia.sys2.ComponentResolver".parse().unwrap(), |
| }) |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new_empty_component() |
| .add_child(ChildDeclBuilder::new().name("c").url("base://c")) |
| .build(), |
| ), |
| ]; |
| |
| // Set up the system. |
| let (resolver_service, mut receiver) = |
| create_service_directory_entry::<fsys::ComponentResolverMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "c" exposes a resolver service. |
| .add_outgoing_path( |
| "a", |
| CapabilityPath::try_from("/svc/fuchsia.sys2.ComponentResolver").unwrap(), |
| resolver_service, |
| ) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "c:0". We expect to see a call to our resolver service for the new component. |
| async move { |
| universe |
| .bind_instance(&vec!["b:0", "c:0"].into()) |
| .await |
| .expect("failed to bind to instance c:0"); |
| }, |
| // Wait for a request, and resolve it. |
| async { |
| while let Some(fsys::ComponentResolverRequest::Resolve { component_url, responder }) = |
| receiver.next().await |
| { |
| assert_eq!(component_url, "base://c"); |
| responder |
| .send(&mut Ok(fsys::Component { |
| resolved_url: Some("test://c".into()), |
| decl: Some(fmem::Data::Bytes( |
| fidl::encoding::encode_persistent( |
| &mut default_component_decl().native_into_fidl(), |
| ) |
| .unwrap(), |
| )), |
| package: None, |
| ..fsys::Component::EMPTY |
| })) |
| .expect("failed to send resolve response"); |
| } |
| } |
| ); |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// a: creates environment "env" and registers resolver "base" from self. |
| /// b: has environment "env". |
| /// c: does NOT have environment "env". |
| #[fuchsia::test] |
| async fn resolver_is_not_available() { |
| // Note that we do not define a component "b" or "c". This will be resolved by our custom resolver. |
| let components = vec![( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url("base://b").environment("env")) |
| .add_child(ChildDeclBuilder::new().name("c").url("base://c")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }), |
| ) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: "/svc/fuchsia.sys2.ComponentResolver".parse().unwrap(), |
| }) |
| .build(), |
| )]; |
| |
| // Set up the system. |
| let (resolver_service, mut receiver) = |
| create_service_directory_entry::<fsys::ComponentResolverMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "c" exposes a resolver service. |
| .add_outgoing_path( |
| "a", |
| CapabilityPath::try_from("/svc/fuchsia.sys2.ComponentResolver").unwrap(), |
| resolver_service, |
| ) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "c:0". We expect to see a failure that the scheme is not registered. |
| async move { |
| match universe.bind_instance(&vec!["c:0"].into()).await { |
| Err(ModelError::ComponentInstanceError { |
| err: ComponentInstanceError::ResolveFailed { err: resolve_error, .. }, |
| }) => { |
| assert_eq!( |
| resolve_error.to_string(), |
| "failed to resolve \"base://c\": scheme not registered" |
| ); |
| } |
| _ => { |
| panic!("expected ModelError wrapping ComponentInstanceError::ResolveFailed"); |
| } |
| }; |
| }, |
| // Wait for a request, and resolve it. |
| async { |
| while let Some(fsys::ComponentResolverRequest::Resolve { component_url, responder }) = |
| receiver.next().await |
| { |
| assert_eq!(component_url, "base://b"); |
| responder |
| .send(&mut Ok(fsys::Component { |
| resolved_url: Some("test://b".into()), |
| decl: Some(fmem::Data::Bytes( |
| fidl::encoding::encode_persistent( |
| &mut default_component_decl().native_into_fidl(), |
| ) |
| .unwrap(), |
| )), |
| package: None, |
| ..fsys::Component::EMPTY |
| })) |
| .expect("failed to send resolve response"); |
| } |
| } |
| ); |
| } |
| |
| /// a |
| /// / |
| /// b |
| /// a: creates environment "env" and registers resolver "base" from self. |
| /// b: has environment "env". |
| #[fuchsia::test] |
| async fn resolver_component_decl_is_validated() { |
| // Note that we do not define a component "b". This will be resolved by our custom resolver. |
| let components = vec![( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url("base://b").environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fsys::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }), |
| ) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: "/svc/fuchsia.sys2.ComponentResolver".parse().unwrap(), |
| }) |
| .build(), |
| )]; |
| |
| // Set up the system. |
| let (resolver_service, mut receiver) = |
| create_service_directory_entry::<fsys::ComponentResolverMarker>(); |
| let universe = RoutingTestBuilder::new("a", components) |
| // Component "a" exposes a resolver service. |
| .add_outgoing_path( |
| "a", |
| CapabilityPath::try_from("/svc/fuchsia.sys2.ComponentResolver").unwrap(), |
| resolver_service, |
| ) |
| .build() |
| .await; |
| |
| join!( |
| // Bind "b:0". We expect to see a ResolverError. |
| async move { |
| match universe.bind_instance(&vec!["b:0"].into()).await { |
| Err(ModelError::ComponentInstanceError { |
| err: ComponentInstanceError::ResolveFailed { err: resolve_error, .. }, |
| }) => { |
| assert!(resolve_error |
| .to_string() |
| .starts_with("failed to resolve \"base://b\": component manifest invalid")); |
| } |
| _ => { |
| panic!("expected ModelError wrapping ComponentInstanceError::ResolveFailed"); |
| } |
| }; |
| }, |
| // Wait for a request, and resolve it. |
| async { |
| while let Some(fsys::ComponentResolverRequest::Resolve { component_url, responder }) = |
| receiver.next().await |
| { |
| assert_eq!(component_url, "base://b"); |
| responder |
| .send(&mut Ok(fsys::Component { |
| resolved_url: Some("test://b".into()), |
| decl: Some(fmem::Data::Bytes({ |
| let mut fidl = fsys::ComponentDecl { |
| exposes: Some(vec![fsys::ExposeDecl::Protocol( |
| fsys::ExposeProtocolDecl { |
| source: Some(fsys::Ref::Self_(fsys::SelfRef {})), |
| ..fsys::ExposeProtocolDecl::EMPTY |
| }, |
| )]), |
| ..fsys::ComponentDecl::EMPTY |
| }; |
| fidl::encoding::encode_persistent(&mut fidl).unwrap() |
| })), |
| package: None, |
| ..fsys::Component::EMPTY |
| })) |
| .expect("failed to send resolve response"); |
| } |
| } |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// b: uses service /svc/hippo as /svc/hippo. |
| /// a: provides b with the service but policy prevents it. |
| #[fuchsia::test] |
| async fn use_protocol_denied_by_capability_policy() { |
| 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::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: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTestBuilder::new("a", components) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()), |
| source_name: CapabilityName::from("hippo_svc"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| HashSet::new(), |
| ) |
| .build() |
| .await; |
| test.check_use( |
| vec!["b:0"].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. |
| #[fuchsia::test] |
| async fn use_directory_with_alias_denied_by_capability_policy() { |
| 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::Child("b".to_string()), |
| rights: Some(*rights::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: *rights::READ_RIGHTS, |
| subdir: None, |
| })) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTestBuilder::new("a", components) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()), |
| source_name: CapabilityName::from("foo_data"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Directory, |
| }, |
| HashSet::new(), |
| ) |
| .build() |
| .await; |
| test.check_use( |
| vec!["b:0"].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. |
| #[fuchsia::test] |
| async fn use_protocol_partial_chain_allowed_by_capability_policy() { |
| 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::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::Child("c".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| 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 { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| |
| let mut allowlist = HashSet::new(); |
| allowlist.insert(AbsoluteMoniker::from(vec!["b:0"])); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()), |
| source_name: CapabilityName::from("hippo_svc"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].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 |
| #[fuchsia::test] |
| async fn use_protocol_component_provided_capability_policy() { |
| 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::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::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::Child("d".to_string()), |
| dependency_type: DependencyType::Strong, |
| })) |
| .add_lazy_child("c") |
| .add_lazy_child("d") |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "hippo_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ]; |
| |
| let mut allowlist = HashSet::new(); |
| allowlist.insert(AbsoluteMoniker::from(vec!["b:0"])); |
| allowlist.insert(AbsoluteMoniker::from(vec!["b:0", "c:0"])); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()), |
| source_name: CapabilityName::from("hippo_svc"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "d:0"].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. |
| #[fuchsia::test] |
| async fn use_event_from_framework_denied_by_capabiilty_policy() { |
| 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::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(), |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "capability_requested".into(), |
| target_name: "capability_requested".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Async, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "started".into(), |
| target_name: "started".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Async, |
| })) |
| .use_(UseDecl::Event(UseEventDecl { |
| source: UseSource::Framework, |
| source_name: "resolved".into(), |
| target_name: "resolved".into(), |
| filter: None, |
| mode: cm_rust::EventMode::Sync, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| name: CapabilityName::try_from("StartComponentTree").unwrap(), |
| subscriptions: vec![cm_rust::EventSubscription { |
| event_name: "resolved".into(), |
| mode: cm_rust::EventMode::Sync, |
| }], |
| })) |
| .build(), |
| ), |
| ]; |
| |
| let mut allowlist = HashSet::new(); |
| allowlist.insert(AbsoluteMoniker::from(vec!["b:0"])); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![ |
| "b:0", |
| ])), |
| source_name: CapabilityName::from("started"), |
| source: CapabilityAllowlistSource::Framework, |
| capability: CapabilityTypeName::Event, |
| }, |
| HashSet::new(), |
| ) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![ |
| "b:0", |
| ])), |
| source_name: CapabilityName::from("capability_requested"), |
| source: CapabilityAllowlistSource::Framework, |
| capability: CapabilityTypeName::Event, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new("capability_requested".into(), EventMode::Async)], |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0"].into(), |
| CheckUse::Event { |
| requests: vec![EventSubscription::new("started".into(), EventMode::Async)], |
| 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 |
| #[fuchsia::test] |
| async fn use_from_component_manager_namespace_denied_by_policy() { |
| let components = vec![( |
| "a", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| 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 test = RoutingTestBuilder::new("a", components) |
| .set_namespace_capabilities(namespace_capabilities) |
| .add_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentManager, |
| source_name: CapabilityName::from("foo_svc"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| HashSet::new(), |
| ) |
| .build() |
| .await; |
| |
| let _ns_dir = ScopedNamespaceDir::new(&test, "/use_from_cm_namespace"); |
| test.check_use( |
| vec![].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED), |
| }, |
| ) |
| .await; |
| } |
| |
| // a |
| // \ |
| // b |
| // |
| // a: exposes "foo" to parent from child |
| // b: exposes "foo" to parent from self |
| #[fuchsia::test] |
| async fn route_protocol_from_expose() { |
| 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: "/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 test = RoutingTestBuilder::new("a", components).build().await; |
| let root_instance = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance"); |
| let expected_source_moniker = AbsoluteMoniker::parse_string_without_instances("/b").unwrap(); |
| assert_matches!( |
| routing::route_capability(RouteRequest::ExposeProtocol(expose_decl), &root_instance).await, |
| Ok(RouteSource::Protocol( |
| CapabilitySource::Component { |
| capability: ComponentCapability::Protocol(protocol_decl), |
| component, |
| }) |
| ) if protocol_decl == expected_protocol_decl && component.moniker == expected_source_moniker |
| ); |
| } |
| |
| /// 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 |
| #[fuchsia::test] |
| async fn use_protocol_component_provided_debug_capability_policy_at_root_from_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(fsys::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 { |
| source: UseSource::Debug, |
| source_name: "svc_allowed".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| 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 test = RoutingTestBuilder::new("a", components) |
| .add_debug_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::root()), |
| source_name: CapabilityName::from("svc_allowed"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "d:0"].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 |
| #[fuchsia::test] |
| async fn use_protocol_component_provided_debug_capability_policy_from_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(fsys::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 { |
| source: UseSource::Debug, |
| source_name: "svc_allowed".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| 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:0"]), "env_b".to_owned())); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_debug_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![ |
| "b:0", |
| ])), |
| source_name: CapabilityName::from("svc_allowed"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "d:0"].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 |
| #[fuchsia::test] |
| async fn use_protocol_component_provided_debug_capability_policy_from_child() { |
| 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(fsys::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".into()), |
| }, |
| )) |
| .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".into()), |
| }, |
| )), |
| ) |
| .add_child(ChildDeclBuilder::new_lazy_child("c").environment("env_b")) |
| .add_child(ChildDeclBuilder::new_lazy_child("d").environment("env_b")) |
| .add_child(ChildDeclBuilder::new_lazy_child("e").environment("env_b")) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| 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 { |
| 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:0"]), "env_b".to_owned())); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_debug_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![ |
| "b:0", "d:0", |
| ])), |
| source_name: CapabilityName::from("svc_allowed"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "e:0"].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// / \ |
| /// c d |
| /// \ |
| /// e |
| /// b: provides d 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. |
| /// e: exposes the service to b |
| /// Tests component provided debug caps in the middle of a path |
| #[fuchsia::test] |
| async fn use_protocol_component_provided_debug_capability_policy_from_grandchild() { |
| 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(fsys::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".into()), |
| }, |
| )) |
| .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".into()), |
| }, |
| )), |
| ) |
| .add_child(ChildDeclBuilder::new_lazy_child("c").environment("env_b")) |
| .add_child(ChildDeclBuilder::new_lazy_child("d").environment("env_b")) |
| .add_child(ChildDeclBuilder::new_lazy_child("e").environment("env_b")) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Debug, |
| source_name: "svc_allowed".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Debug, |
| source_name: "svc_not_allowed".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| })) |
| .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:0"]), "env_b".to_owned())); |
| |
| let test = RoutingTestBuilder::new("a", components) |
| .add_debug_capability_policy( |
| CapabilityAllowlistKey { |
| source_moniker: ExtendedMoniker::ComponentInstance(AbsoluteMoniker::from(vec![ |
| "b:0", "d:0", "e:0", |
| ])), |
| source_name: CapabilityName::from("svc_allowed"), |
| source: CapabilityAllowlistSource::Self_, |
| capability: CapabilityTypeName::Protocol, |
| }, |
| allowlist, |
| ) |
| .build() |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "c:0"].into(), |
| CheckUse::Protocol { path: default_service_capability(), expected_res: ExpectedResult::Ok }, |
| ) |
| .await; |
| |
| test.check_use( |
| vec!["b:0", "d:0"].into(), |
| CheckUse::Protocol { |
| path: default_service_capability(), |
| expected_res: ExpectedResult::Err(zx::Status::ACCESS_DENIED), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// / |
| /// b |
| /// |
| /// a: offer to b from self |
| /// b: use from parent |
| #[fuchsia::test] |
| async fn use_service_from_parent() { |
| let use_decl = UseServiceDecl { |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| sources: vec![ServiceSource { |
| source: OfferSource::Self_, |
| source_name: "foo".into(), |
| }], |
| target_name: "foo".into(), |
| target: OfferTarget::Child("b".to_string()), |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: "/svc/foo".try_into().unwrap(), |
| }) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ]; |
| let test = RoutingTestBuilder::new("a", components).build().await; |
| let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance"); |
| let a_component = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance"); |
| let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component) |
| .await |
| .expect("failed to route service"); |
| match source { |
| RouteSource::Service(CapabilitySource::Component { |
| capability: ComponentCapability::Service(ServiceDecl { name, source_path }), |
| component, |
| }) => { |
| assert_eq!(name, CapabilityName("foo".into())); |
| assert_eq!(source_path, "/svc/foo".parse::<CapabilityPath>().unwrap()); |
| assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component)); |
| } |
| _ => panic!("bad capability source"), |
| }; |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// |
| /// a: offer to b from child c |
| /// b: use from parent |
| /// c: expose from self |
| #[fuchsia::test] |
| async fn route_service_from_sibling() { |
| let use_decl = UseServiceDecl { |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| sources: vec![ServiceSource { |
| source: OfferSource::Child("c".into()), |
| source_name: "foo".into(), |
| }], |
| target_name: "foo".into(), |
| target: OfferTarget::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 { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Self_, |
| source_name: "foo".into(), |
| }], |
| target_name: "foo".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: "/svc/foo".try_into().unwrap(), |
| }) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTestBuilder::new("a", components).build().await; |
| let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance"); |
| let c_component = test.model.look_up(&vec!["c:0"].into()).await.expect("c instance"); |
| let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component) |
| .await |
| .expect("failed to route service"); |
| |
| // Verify this source comes from `c`. |
| match source { |
| RouteSource::Service(CapabilitySource::Component { |
| capability: ComponentCapability::Service(ServiceDecl { name, source_path }), |
| component, |
| }) => { |
| assert_eq!(name, CapabilityName("foo".into())); |
| assert_eq!(source_path, "/svc/foo".parse::<CapabilityPath>().unwrap()); |
| assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &c_component)); |
| } |
| _ => panic!("bad capability source"), |
| }; |
| } |
| |
| #[fuchsia::test] |
| async fn route_service_from_parent_collection() { |
| let use_decl = UseServiceDecl { |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| sources: vec![ServiceSource { |
| source: OfferSource::Collection("coll".to_string()), |
| source_name: "foo".into(), |
| }], |
| target_name: "foo".into(), |
| target: OfferTarget::Child("b".to_string()), |
| })) |
| .add_collection(CollectionDeclBuilder::new_transient_collection("coll")) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ]; |
| let test = RoutingTestBuilder::new("a", components).build().await; |
| let b_component = test.model.look_up(&vec!["b:0"].into()).await.expect("b instance"); |
| let a_component = test.model.look_up(&AbsoluteMoniker::root()).await.expect("root instance"); |
| let source = routing::route_capability(RouteRequest::UseService(use_decl), &b_component) |
| .await |
| .expect("failed to route service"); |
| match source { |
| RouteSource::Service(CapabilitySource::Collection { |
| collection_name, |
| source_name, |
| component, |
| .. |
| }) => { |
| assert_eq!(collection_name, "coll"); |
| assert_eq!(source_name, CapabilityName("foo".into())); |
| assert!(Arc::ptr_eq(&component.upgrade().unwrap(), &a_component)); |
| } |
| _ => panic!("bad capability source"), |
| }; |
| } |
| |
| #[fuchsia::test] |
| async fn list_service_instances_from_collection() { |
| let use_decl = UseServiceDecl { |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| }; |
| let components = vec![ |
| ( |
| "root", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.Realm").unwrap(), |
| })) |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| sources: vec![ServiceSource { |
| source: OfferSource::Collection("coll".to_string()), |
| source_name: "foo".into(), |
| }], |
| target_name: "foo".into(), |
| target: OfferTarget::Child("client".to_string()), |
| })) |
| .add_collection(CollectionDeclBuilder::new_transient_collection("coll")) |
| .add_lazy_child("client") |
| .build(), |
| ), |
| ("client", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ( |
| "service_child_a", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Self_, |
| source_name: "foo".into(), |
| }], |
| target: ExposeTarget::Parent, |
| target_name: "foo".into(), |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: "/svc/foo".try_into().unwrap(), |
| }) |
| .build(), |
| ), |
| ( |
| "service_child_b", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Self_, |
| source_name: "foo".into(), |
| }], |
| target: ExposeTarget::Parent, |
| target_name: "foo".into(), |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: "/svc/foo".try_into().unwrap(), |
| }) |
| .build(), |
| ), |
| ("non_service_child", ComponentDeclBuilder::new().build()), |
| ]; |
| let test = RoutingTestBuilder::new("root", components).build().await; |
| |
| // Start a few dynamic children in the collection "coll". |
| test.create_dynamic_child( |
| AbsoluteMoniker::root(), |
| "coll", |
| ChildDeclBuilder::new_lazy_child("service_child_a"), |
| ) |
| .await; |
| test.create_dynamic_child( |
| AbsoluteMoniker::root(), |
| "coll", |
| ChildDeclBuilder::new_lazy_child("non_service_child"), |
| ) |
| .await; |
| test.create_dynamic_child( |
| AbsoluteMoniker::root(), |
| "coll", |
| ChildDeclBuilder::new_lazy_child("service_child_b"), |
| ) |
| .await; |
| |
| let client_component = |
| test.model.look_up(&vec!["client:0"].into()).await.expect("client instance"); |
| let source = routing::route_capability(RouteRequest::UseService(use_decl), &client_component) |
| .await |
| .expect("failed to route service"); |
| let capability_provider = match source { |
| RouteSource::Service(CapabilitySource::Collection { capability_provider, .. }) => { |
| capability_provider |
| } |
| _ => panic!("bad capability source"), |
| }; |
| |
| // Check that only the instances that expose the service are listed. |
| let instances: HashSet<String> = |
| capability_provider.list_instances().await.unwrap().into_iter().collect(); |
| assert_eq!(instances.len(), 2); |
| assert!(instances.contains("service_child_a")); |
| assert!(instances.contains("service_child_b")); |
| |
| // Try routing to one of the instances. |
| let source = capability_provider |
| .route_instance("service_child_a") |
| .await |
| .expect("failed to route to child"); |
| match source { |
| CapabilitySource::Component { |
| capability: ComponentCapability::Service(ServiceDecl { name, source_path }), |
| component, |
| } => { |
| assert_eq!(name, CapabilityName("foo".into())); |
| assert_eq!(source_path, "/svc/foo".parse::<CapabilityPath>().unwrap()); |
| assert_eq!(component.moniker, vec!["coll:service_child_a:1"].into()); |
| } |
| _ => panic!("bad child capability source"), |
| } |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// |
| /// a: offer service from c to b |
| /// b: use service |
| /// c: expose service from collection |
| #[fuchsia::test] |
| async fn use_service_from_sibling_collection() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| sources: vec![ServiceSource { |
| source: OfferSource::Child("c".to_string()), |
| source_name: "my.service.Service".into(), |
| }], |
| target: OfferTarget::Child("b".to_string()), |
| target_name: "my.service.Service".into(), |
| })) |
| .add_child(ChildDeclBuilder::new_lazy_child("b")) |
| .add_child(ChildDeclBuilder::new_lazy_child("c")) |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Service(UseServiceDecl { |
| source: UseSource::Parent, |
| source_name: "my.service.Service".into(), |
| target_path: "/svc/my.service.Service".try_into().unwrap(), |
| })) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Framework, |
| source_name: "fuchsia.sys2.Realm".into(), |
| target_path: "/svc/fuchsia.sys2.Realm".try_into().unwrap(), |
| })) |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Collection("coll".to_string()), |
| source_name: "my.service.Service".into(), |
| }], |
| target_name: "my.service.Service".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .add_collection(CollectionDeclBuilder::new_transient_collection("coll")) |
| .build(), |
| ), |
| ( |
| "foo", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Self_, |
| source_name: "my.service.Service".into(), |
| }], |
| target_name: "my.service.Service".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .service(ServiceDecl { |
| name: "my.service.Service".into(), |
| source_path: "/svc/my.service.Service".try_into().unwrap(), |
| }) |
| .build(), |
| ), |
| ( |
| "bar", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| sources: vec![ServiceSource { |
| source: ExposeSource::Self_, |
| source_name: "my.service.Service".into(), |
| }], |
| target_name: "my.service.Service".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .service(ServiceDecl { |
| name: "my.service.Service".into(), |
| source_path: "/svc/my.service.Service".try_into().unwrap(), |
| }) |
| .build(), |
| ), |
| ("baz", ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let (directory_entry, mut receiver) = create_service_directory_entry::<echo::EchoMarker>(); |
| let instance_dir = pseudo_directory! { |
| "echo" => directory_entry, |
| }; |
| let test = RoutingTestBuilder::new("a", components) |
| .add_outgoing_path( |
| "foo", |
| "/svc/my.service.Service/default".try_into().unwrap(), |
| instance_dir, |
| ) |
| .build() |
| .await; |
| |
| // Populate the collection with dynamic children. |
| test.create_dynamic_child(vec!["c:0"].into(), "coll", ChildDeclBuilder::new_lazy_child("foo")) |
| .await; |
| test.create_dynamic_child(vec!["c:0"].into(), "coll", ChildDeclBuilder::new_lazy_child("bar")) |
| .await; |
| test.create_dynamic_child(vec!["c:0"].into(), "coll", ChildDeclBuilder::new_lazy_child("baz")) |
| .await; |
| |
| let target: AbsoluteMoniker = vec!["b:0"].into(); |
| let namespace = test.bind_and_get_namespace(target.clone()).await; |
| let dir = capability_util::take_dir_from_namespace(&namespace, "/svc").await; |
| let service_dir = io_util::directory::open_directory( |
| &dir, |
| "my.service.Service", |
| io_util::OPEN_RIGHT_READABLE | io_util::OPEN_RIGHT_WRITABLE, |
| ) |
| .await |
| .expect("failed to open service"); |
| let entries: HashSet<String> = files_async::readdir(&service_dir) |
| .await |
| .expect("failed to read entries") |
| .into_iter() |
| .map(|d| d.name) |
| .collect(); |
| assert_eq!(entries.len(), 2); |
| assert!(entries.contains("foo")); |
| assert!(entries.contains("bar")); |
| capability_util::add_dir_to_namespace(&namespace, "/svc", dir).await; |
| |
| join!( |
| async move { |
| test.check_use( |
| target.clone(), |
| CheckUse::Service { |
| path: "/svc/my.service.Service".try_into().unwrap(), |
| instance: "foo".to_string(), |
| member: "echo".to_string(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await; |
| }, |
| async move { |
| while let Some(echo::EchoRequest::EchoString { value, responder }) = |
| receiver.next().await |
| { |
| responder.send(value.as_ref().map(|v| v.as_str())).expect("failed to send reply") |
| } |
| } |
| ); |
| } |