[componentmgr] Hub: Implement in directory

This CL implements an exec/in directory in Hub v2
that corresponds to the incoming namespace of a component.

Bug: CF-541

Change-Id: I30170e2f789224366396aa4f8012e36732421b99
diff --git a/examples/components/routing/meta/echo_client.cml b/examples/components/routing/meta/echo_client.cml
index fa9f88c..865c536 100644
--- a/examples/components/routing/meta/echo_client.cml
+++ b/examples/components/routing/meta/echo_client.cml
@@ -3,6 +3,7 @@
 {
     "program": {
         "binary": "bin/echo_client",
+        "args": ["Hippos", "rule!"],
     },
     "use": [
         {
diff --git a/src/sys/component_manager/src/directory_broker.rs b/src/sys/component_manager/src/directory_broker.rs
index ba36b99..a47469f 100644
--- a/src/sys/component_manager/src/directory_broker.rs
+++ b/src/sys/component_manager/src/directory_broker.rs
@@ -12,6 +12,8 @@
     void::Void,
 };
 
+pub type RoutingFn = Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>;
+
 // TODO(ZX-3606): move this into the pseudo dir fs crate.
 /// DirectoryBroker exists to hold a slot in a fuchsia_vfs_pseudo_fs directory and proxy open
 /// requests. A DirectoryBroker holds a closure provided at creation time, and whenever an open
@@ -23,15 +25,13 @@
     ///  mode: u32
     ///  relative_path: String
     ///  server_end: ServerEnd<NodeMarker>
-    route_open: Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>,
+    route_open: RoutingFn,
     entry_info: fvfs::directory::entry::EntryInfo,
 }
 
 impl DirectoryBroker {
     /// new will create a new DirectoryBroker to forward directory open requests.
-    pub fn new(
-        route_open: Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>,
-    ) -> DirectoryBroker {
+    pub fn new(route_open: RoutingFn) -> DirectoryBroker {
         return DirectoryBroker {
             route_open,
             entry_info: fvfs::directory::entry::EntryInfo::new(INO_UNKNOWN, DIRENT_TYPE_SERVICE),
diff --git a/src/sys/component_manager/src/model/dir_tree.rs b/src/sys/component_manager/src/model/dir_tree.rs
new file mode 100644
index 0000000..787d820
--- /dev/null
+++ b/src/sys/component_manager/src/model/dir_tree.rs
@@ -0,0 +1,180 @@
+// 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::directory_broker::{DirectoryBroker, RoutingFn},
+    crate::model::{
+        error::ModelError, moniker::AbsoluteMoniker, routing_fn_factory::RoutingFnFactory,
+    },
+    cm_rust::{Capability, ComponentDecl},
+    fuchsia_vfs_pseudo_fs::directory,
+    log::*,
+    std::collections::HashMap,
+};
+
+/// Represents the directory hierarchy of the exposed directory, not including the nodes for the
+/// capabilities themselves.
+pub struct DirTree {
+    directory_nodes: HashMap<String, Box<DirTree>>,
+    broker_nodes: HashMap<String, RoutingFn>,
+}
+
+impl DirTree {
+    /// Builds a directory hierarchy from a component's `uses` declarations.
+    pub fn build_from_uses(
+        route_fn_factory: RoutingFnFactory,
+        abs_moniker: &AbsoluteMoniker,
+        decl: ComponentDecl,
+    ) -> Result<Self, ModelError> {
+        let mut tree = DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() };
+        for use_ in decl.uses {
+            let capability = match use_ {
+                cm_rust::UseDecl::Directory(d) => Capability::Directory(d.target_path),
+                cm_rust::UseDecl::Service(d) => Capability::Service(d.target_path),
+                cm_rust::UseDecl::Storage(_) => {
+                    error!("storage capabilities are not supported");
+                    return Err(ModelError::ComponentInvalid);
+                }
+            };
+            tree.add_capability(&route_fn_factory, abs_moniker, capability);
+        }
+        Ok(tree)
+    }
+
+    /// Installs the directory tree into `root_dir`.
+    pub fn install(
+        self,
+        abs_moniker: &AbsoluteMoniker,
+        root_dir: &mut directory::simple::Simple<'static>,
+    ) -> Result<(), ModelError> {
+        for (name, subtree) in self.directory_nodes {
+            let mut subdir = directory::simple::empty();
+            subtree.install(abs_moniker, &mut subdir)?;
+            root_dir
+                .add_entry(&name, subdir)
+                .map_err(|_| ModelError::add_entry_error(abs_moniker.clone(), &name as &str))?;
+        }
+        for (name, route_fn) in self.broker_nodes {
+            let node = DirectoryBroker::new(route_fn);
+            root_dir
+                .add_entry(&name, node)
+                .map_err(|_| ModelError::add_entry_error(abs_moniker.clone(), &name as &str))?;
+        }
+        Ok(())
+    }
+
+    fn add_capability(
+        &mut self,
+        route_fn_factory: &RoutingFnFactory,
+        abs_moniker: &AbsoluteMoniker,
+        capability: Capability,
+    ) {
+        let path = capability.path().expect("missing path");
+        let components = path.dirname.split("/");
+        let mut tree = self;
+        for component in components {
+            if !component.is_empty() {
+                tree = tree.directory_nodes.entry(component.to_string()).or_insert(Box::new(
+                    DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() },
+                ));
+            }
+        }
+        tree.broker_nodes.insert(
+            path.basename.to_string(),
+            route_fn_factory.create_route_fn(&abs_moniker, capability),
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use {
+        super::*,
+        crate::model::testing::{mocks, routing_test_helpers::default_component_decl, test_utils},
+        cm_rust::{CapabilityPath, UseDecl, UseDirectoryDecl, UseServiceDecl},
+        fidl::endpoints::{ClientEnd, ServerEnd},
+        fidl_fuchsia_io::{DirectoryMarker, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE},
+        fuchsia_async as fasync,
+        fuchsia_vfs_pseudo_fs::{
+            directory::{self, entry::DirectoryEntry},
+            file::simple::read_only,
+        },
+        fuchsia_zircon as zx,
+        std::{convert::TryFrom, iter, sync::Arc},
+    };
+
+    #[fuchsia_async::run_singlethreaded(test)]
+    async fn make_in_directory() {
+        // Make a directory tree that will be forwarded to by ProxyingRoutingFnFactory.
+        let mut sub_dir = directory::simple::empty();
+        let (sub_dir_client, sub_dir_server) = zx::Channel::create().unwrap();
+        sub_dir.open(
+            OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
+            0,
+            &mut iter::empty(),
+            ServerEnd::<NodeMarker>::new(sub_dir_server.into()),
+        );
+
+        // Add a 'hello' file in the subdirectory for testing purposes.
+        sub_dir
+            .add_entry("hello", { read_only(move || Ok(b"friend".to_vec())) })
+            .map_err(|(s, _)| s)
+            .expect("Failed to add 'hello' entry");
+
+        let sub_dir_proxy = ClientEnd::<DirectoryMarker>::new(sub_dir_client)
+            .into_proxy()
+            .expect("failed to create directory proxy");
+        fasync::spawn(async move {
+            let _ = await!(sub_dir);
+        });
+
+        let route_fn_factory = Arc::new(mocks::ProxyingRoutingFnFactory::new(sub_dir_proxy));
+        let decl = ComponentDecl {
+            uses: vec![
+                UseDecl::Directory(UseDirectoryDecl {
+                    source_path: CapabilityPath::try_from("/data/baz").unwrap(),
+                    target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
+                }),
+                UseDecl::Service(UseServiceDecl {
+                    source_path: CapabilityPath::try_from("/svc/baz").unwrap(),
+                    target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
+                }),
+                UseDecl::Directory(UseDirectoryDecl {
+                    source_path: CapabilityPath::try_from("/data/foo").unwrap(),
+                    target_path: CapabilityPath::try_from("/data/bar").unwrap(),
+                }),
+            ],
+            ..default_component_decl()
+        };
+        let abs_moniker = AbsoluteMoniker::root();
+        let tree = DirTree::build_from_uses(route_fn_factory, &abs_moniker, decl.clone())
+            .expect("Unable to build 'uses' directory");
+
+        let mut in_dir = directory::simple::empty();
+        tree.install(&abs_moniker, &mut in_dir).expect("Unable to build pseudodirectory");
+        let (in_dir_client, in_dir_server) = zx::Channel::create().unwrap();
+        in_dir.open(
+            OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
+            0,
+            &mut iter::empty(),
+            ServerEnd::<NodeMarker>::new(in_dir_server.into()),
+        );
+        fasync::spawn(async move {
+            let _ = await!(in_dir);
+        });
+        let in_dir_proxy = ClientEnd::<DirectoryMarker>::new(in_dir_client)
+            .into_proxy()
+            .expect("failed to create directory proxy");
+        assert_eq!(
+            vec!["data/bar", "data/hippo", "svc/hippo"],
+            await!(test_utils::list_directory_recursive(&in_dir_proxy))
+        );
+
+        // All entries in the in directory lead to foo as a result of ProxyingRoutingFnFactory.
+        assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "data/bar/hello")));
+        assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "data/hippo/hello")));
+        assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "svc/hippo/hello")));
+    }
+
+}
diff --git a/src/sys/component_manager/src/model/error.rs b/src/sys/component_manager/src/model/error.rs
index a051b58..c5aeebd 100644
--- a/src/sys/component_manager/src/model/error.rs
+++ b/src/sys/component_manager/src/model/error.rs
@@ -56,6 +56,8 @@
         #[fail(cause)]
         err: Error,
     },
+    #[fail(display = "add entry error")]
+    AddEntryError { moniker: AbsoluteMoniker, entry_name: String },
 }
 
 impl ModelError {
@@ -86,6 +88,10 @@
     pub fn capability_discovery_error(err: impl Into<Error>) -> ModelError {
         ModelError::CapabilityDiscoveryError { err: err.into() }
     }
+
+    pub fn add_entry_error(moniker: AbsoluteMoniker, entry_name: impl Into<String>) -> ModelError {
+        ModelError::AddEntryError { moniker, entry_name: entry_name.into() }
+    }
 }
 
 impl From<HubError> for ModelError {
diff --git a/src/sys/component_manager/src/model/hub.rs b/src/sys/component_manager/src/model/hub.rs
index 6428331..d4e8f57 100644
--- a/src/sys/component_manager/src/model/hub.rs
+++ b/src/sys/component_manager/src/model/hub.rs
@@ -179,6 +179,7 @@
         &'a self,
         realm: Arc<model::Realm>,
         realm_state: &'a model::RealmState,
+        route_fn_factory: model::RoutingFnFactory,
     ) -> Result<(), ModelError> {
         let component_url = realm.component_url.clone();
         let abs_moniker = realm.abs_moniker.clone();
@@ -216,6 +217,16 @@
                         HubError::add_directory_entry_error(abs_moniker.clone(), "resolved_url")
                     })?;
 
+                // Add an 'in' directory.
+                let decl = realm_state.decl.as_ref().expect("ComponentDecl unavailable.");
+                let tree =
+                    model::DirTree::build_from_uses(route_fn_factory, &abs_moniker, decl.clone())?;
+                let mut in_dir = directory::simple::empty();
+                tree.install(&abs_moniker, &mut in_dir)?;
+                controlled
+                    .add_entry("in", in_dir)
+                    .map_err(|_| HubError::add_directory_entry_error(abs_moniker.clone(), "in"))?;
+
                 // Install the out directory if we can successfully clone it.
                 // TODO(fsamuel): We should probably preserve the original error messages
                 // instead of dropping them.
@@ -271,8 +282,9 @@
         &'a self,
         realm: Arc<model::Realm>,
         realm_state: &'a model::RealmState,
+        route_fn_factory: model::RoutingFnFactory,
     ) -> BoxFuture<Result<(), ModelError>> {
-        Box::pin(self.on_bind_instance_async(realm, realm_state))
+        Box::pin(self.on_bind_instance_async(realm, realm_state, route_fn_factory))
     }
 
     fn on_add_dynamic_child(&self, _realm: Arc<model::Realm>) -> BoxFuture<Result<(), ModelError>> {
@@ -286,18 +298,27 @@
     use {
         super::*,
         crate::model::{
-            self, hub::Hub, testing::mocks, testing::routing_test_helpers::default_component_decl,
+            self,
+            hub::Hub,
+            testing::mocks,
+            testing::{
+                routing_test_helpers::default_component_decl,
+                test_utils::{dir_contains, list_directory_recursive, read_file},
+            },
         },
-        cm_rust::{self, ChildDecl, ComponentDecl},
+        cm_rust::{
+            self, CapabilityPath, ChildDecl, ComponentDecl, UseDecl, UseDirectoryDecl,
+            UseServiceDecl,
+        },
         fidl::endpoints::{ClientEnd, ServerEnd},
         fidl_fuchsia_io::{
             DirectoryMarker, DirectoryProxy, NodeMarker, MODE_TYPE_DIRECTORY, OPEN_RIGHT_READABLE,
             OPEN_RIGHT_WRITABLE,
         },
-        fidl_fuchsia_sys2 as fsys, files_async,
+        fidl_fuchsia_sys2 as fsys,
         fuchsia_vfs_pseudo_fs::directory::entry::DirectoryEntry,
         fuchsia_zircon as zx,
-        std::{iter, path::PathBuf},
+        std::{convert::TryFrom, iter, path::PathBuf},
     };
 
     /// Hosts an out directory with a 'foo' file.
@@ -355,25 +376,6 @@
         })
     }
 
-    async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
-        let file_proxy =
-            io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
-        let res = await!(io_util::read_file(&file_proxy));
-        res.expect("Unable to read file.")
-    }
-
-    async fn dir_contains<'a>(
-        root_proxy: &'a DirectoryProxy,
-        path: &'a str,
-        entry_name: &'a str,
-    ) -> bool {
-        let dir = io_util::open_directory(&root_proxy, &PathBuf::from(path))
-            .expect("Failed to open directory");
-        let entries = await!(files_async::readdir(&dir)).expect("readdir failed");
-        let listing = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
-        listing.contains(&String::from(entry_name))
-    }
-
     type DirectoryCallback = Box<Fn(ServerEnd<DirectoryMarker>) + Send + Sync>;
 
     struct ComponentDescriptor {
@@ -383,17 +385,6 @@
         pub runtime_host_fn: Option<DirectoryCallback>,
     }
 
-    impl ComponentDescriptor {
-        pub fn new(
-            name: &str,
-            decl: ComponentDecl,
-            host_fn: Option<DirectoryCallback>,
-            runtime_host_fn: Option<DirectoryCallback>,
-        ) -> Self {
-            ComponentDescriptor { name: name.to_string(), decl, host_fn, runtime_host_fn }
-        }
-    }
-
     async fn start_component_manager_with_hub(
         root_component_url: String,
         components: Vec<ComponentDescriptor>,
@@ -449,14 +440,14 @@
     }
 
     #[fuchsia_async::run_singlethreaded(test)]
-    async fn run_hub_basic() {
+    async fn hub_basic() {
         let root_component_url = "test:///root".to_string();
         let (_model, hub_proxy) = await!(start_component_manager_with_hub(
             root_component_url.clone(),
             vec![
-                ComponentDescriptor::new(
-                    "root",
-                    ComponentDecl {
+                ComponentDescriptor {
+                    name: "root".to_string(),
+                    decl: ComponentDecl {
                         children: vec![ChildDecl {
                             name: "a".to_string(),
                             url: "test:///a".to_string(),
@@ -464,15 +455,15 @@
                         }],
                         ..default_component_decl()
                     },
-                    None,
-                    None
-                ),
-                ComponentDescriptor::new(
-                    "a",
-                    ComponentDecl { children: vec![], ..default_component_decl() },
-                    None,
-                    None
-                )
+                    host_fn: None,
+                    runtime_host_fn: None
+                },
+                ComponentDescriptor {
+                    name: "a".to_string(),
+                    decl: ComponentDecl { children: vec![], ..default_component_decl() },
+                    host_fn: None,
+                    runtime_host_fn: None
+                }
             ]
         ));
 
@@ -485,13 +476,13 @@
     }
 
     #[fuchsia_async::run_singlethreaded(test)]
-    async fn run_hub_out_directory() {
+    async fn hub_out_directory() {
         let root_component_url = "test:///root".to_string();
         let (_model, hub_proxy) = await!(start_component_manager_with_hub(
             root_component_url.clone(),
-            vec![ComponentDescriptor::new(
-                "root",
-                ComponentDecl {
+            vec![ComponentDescriptor {
+                name: "root".to_string(),
+                decl: ComponentDecl {
                     children: vec![ChildDecl {
                         name: "a".to_string(),
                         url: "test:///a".to_string(),
@@ -499,9 +490,9 @@
                     }],
                     ..default_component_decl()
                 },
-                Some(foo_out_dir_fn()),
-                None
-            )]
+                host_fn: Some(foo_out_dir_fn()),
+                runtime_host_fn: None
+            }]
         ));
 
         assert!(await!(dir_contains(&hub_proxy, "self/exec", "out")));
@@ -512,13 +503,13 @@
     }
 
     #[fuchsia_async::run_singlethreaded(test)]
-    async fn run_hub_runtime_directory() {
+    async fn hub_runtime_directory() {
         let root_component_url = "test:///root".to_string();
         let (_model, hub_proxy) = await!(start_component_manager_with_hub(
             root_component_url.clone(),
-            vec![ComponentDescriptor::new(
-                "root",
-                ComponentDecl {
+            vec![ComponentDescriptor {
+                name: "root".to_string(),
+                decl: ComponentDecl {
                     children: vec![ChildDecl {
                         name: "a".to_string(),
                         url: "test:///a".to_string(),
@@ -526,11 +517,53 @@
                     }],
                     ..default_component_decl()
                 },
-                None,
-                Some(bleep_runtime_dir_fn())
-            )]
+                host_fn: None,
+                runtime_host_fn: Some(bleep_runtime_dir_fn())
+            }]
         ));
 
         assert_eq!("blah", await!(read_file(&hub_proxy, "self/exec/runtime/bleep")));
     }
+
+    #[fuchsia_async::run_singlethreaded(test)]
+    async fn hub_in_directory() {
+        let root_component_url = "test:///root".to_string();
+        let (_model, hub_proxy) = await!(start_component_manager_with_hub(
+            root_component_url.clone(),
+            vec![ComponentDescriptor {
+                name: "root".to_string(),
+                decl: ComponentDecl {
+                    children: vec![ChildDecl {
+                        name: "a".to_string(),
+                        url: "test:///a".to_string(),
+                        startup: fsys::StartupMode::Lazy,
+                    }],
+                    uses: vec![
+                        UseDecl::Directory(UseDirectoryDecl {
+                            source_path: CapabilityPath::try_from("/data/baz").unwrap(),
+                            target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
+                        }),
+                        UseDecl::Service(UseServiceDecl {
+                            source_path: CapabilityPath::try_from("/svc/baz").unwrap(),
+                            target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
+                        }),
+                        UseDecl::Directory(UseDirectoryDecl {
+                            source_path: CapabilityPath::try_from("/data/foo").unwrap(),
+                            target_path: CapabilityPath::try_from("/data/bar").unwrap(),
+                        }),
+                    ],
+                    ..default_component_decl()
+                },
+                host_fn: None,
+                runtime_host_fn: None,
+            }]
+        ));
+
+        let in_dir = io_util::open_directory(&hub_proxy, &PathBuf::from("self/exec/in"))
+            .expect("Failed to open directory");
+        assert_eq!(
+            vec!["data/bar", "data/hippo", "svc/hippo"],
+            await!(list_directory_recursive(&in_dir))
+        );
+    }
 }
diff --git a/src/sys/component_manager/src/model/mod.rs b/src/sys/component_manager/src/model/mod.rs
index 1f8f1fe..a7d67ca 100644
--- a/src/sys/component_manager/src/model/mod.rs
+++ b/src/sys/component_manager/src/model/mod.rs
@@ -4,6 +4,7 @@
 
 pub mod ambient;
 mod component;
+pub mod dir_tree;
 pub mod error;
 pub mod hub;
 mod model;
@@ -11,12 +12,13 @@
 mod namespace;
 mod resolver;
 mod routing;
+pub mod routing_fn_factory;
 mod runner;
 pub mod testing;
 #[cfg(test)]
 pub(crate) mod tests;
 
 pub use self::{
-    ambient::*, component::*, error::*, hub::*, model::*, moniker::*, namespace::*, resolver::*,
-    routing::*, runner::*,
+    ambient::*, component::*, dir_tree::*, error::*, hub::*, model::*, moniker::*, namespace::*,
+    resolver::*, routing::*, routing_fn_factory::*, runner::*,
 };
diff --git a/src/sys/component_manager/src/model/model.rs b/src/sys/component_manager/src/model/model.rs
index 10380f3..1c7a263 100644
--- a/src/sys/component_manager/src/model/model.rs
+++ b/src/sys/component_manager/src/model/model.rs
@@ -29,6 +29,7 @@
         &'a self,
         realm: Arc<Realm>,
         realm_state: &'a RealmState,
+        route_fn_factory: Arc<dyn CapabilityRoutingFnFactory + Send + Sync>,
     ) -> BoxFuture<Result<(), ModelError>>;
 
     // Called when a dynamic instance is added with `realm`.
@@ -217,8 +218,9 @@
     async fn bind_instance<'a>(&'a self, realm: Arc<Realm>) -> Result<Vec<Arc<Realm>>, ModelError> {
         let mut state = await!(realm.state.lock());
         let eager_children = await!(self.populate_realm_state(&mut *state, realm.clone()))?;
+        let route_fn_factory = Arc::new(ModelCapabilityRoutingFnFactory::new(&self));
         for hook in self.hooks.iter() {
-            await!(hook.on_bind_instance(realm.clone(), &*state))?;
+            await!(hook.on_bind_instance(realm.clone(), &*state, route_fn_factory.clone()))?;
         }
 
         Ok(eager_children)
diff --git a/src/sys/component_manager/src/model/routing_fn_factory.rs b/src/sys/component_manager/src/model/routing_fn_factory.rs
new file mode 100644
index 0000000..12c3f82
--- /dev/null
+++ b/src/sys/component_manager/src/model/routing_fn_factory.rs
@@ -0,0 +1,65 @@
+// 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::{
+        directory_broker::RoutingFn,
+        model::{moniker::AbsoluteMoniker, routing::*, Model},
+    },
+    cm_rust::Capability,
+    fidl::endpoints::ServerEnd,
+    fidl_fuchsia_io::NodeMarker,
+    fuchsia_async as fasync,
+    log::*,
+    std::sync::Arc,
+};
+
+pub trait CapabilityRoutingFnFactory {
+    fn create_route_fn(&self, abs_moniker: &AbsoluteMoniker, capability: Capability) -> RoutingFn;
+}
+
+pub type RoutingFnFactory = Arc<dyn CapabilityRoutingFnFactory + Send + Sync>;
+
+pub struct ModelCapabilityRoutingFnFactory {
+    pub model: Model,
+}
+
+impl ModelCapabilityRoutingFnFactory {
+    pub fn new(model: &Model) -> Self {
+        ModelCapabilityRoutingFnFactory { model: model.clone() }
+    }
+}
+
+impl CapabilityRoutingFnFactory for ModelCapabilityRoutingFnFactory {
+    fn create_route_fn(&self, abs_moniker: &AbsoluteMoniker, capability: Capability) -> RoutingFn {
+        let abs_moniker = abs_moniker.clone();
+        let model = self.model.clone();
+        Box::new(
+            move |_flags: u32,
+                  _mode: u32,
+                  _relative_path: String,
+                  server_end: ServerEnd<NodeMarker>| {
+                let model = model.clone();
+                let abs_moniker = abs_moniker.clone();
+                let capability = capability.clone();
+                fasync::spawn(async move {
+                    // `route_service` is used for directories as well. The directory capability
+                    // is modeled as a service node of the containing directory.
+                    // TODO(fsamuel): we might want to get rid of `route_directory` and
+                    // `route_service`; instead, we can expose `route_use_capability` and have the
+                    // caller be responsible for passing in the right mode.
+                    let res = await!(route_service(
+                        &model,
+                        &capability,
+                        abs_moniker.clone(),
+                        server_end.into_channel()
+                    ));
+                    if let Err(e) = res {
+                        error!("failed to route service for exposed dir {}: {:?}", abs_moniker, e);
+                    }
+                });
+            },
+        )
+    }
+}
diff --git a/src/sys/component_manager/src/model/testing/mocks.rs b/src/sys/component_manager/src/model/testing/mocks.rs
index 4ef4d05..e3d2bcf 100644
--- a/src/sys/component_manager/src/model/testing/mocks.rs
+++ b/src/sys/component_manager/src/model/testing/mocks.rs
@@ -4,17 +4,60 @@
 
 use {
     crate::model::*,
-    cm_rust::ComponentDecl,
+    cm_rust::{Capability, ComponentDecl},
     failure::{format_err, Error},
-    fidl::endpoints::ServerEnd,
-    fidl_fuchsia_io::DirectoryMarker,
-    fidl_fuchsia_sys2 as fsys,
+    fidl::endpoints::{ClientEnd, ServerEnd},
+    fidl_fuchsia_io::{
+        DirectoryMarker, DirectoryProxy, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
+    },
+    fidl_fuchsia_sys2 as fsys, fuchsia_zircon as zx,
     futures::future::FutureObj,
     futures::lock::Mutex,
     futures::prelude::*,
     std::{collections::HashMap, convert::TryFrom, sync::Arc},
 };
 
+pub struct ProxyingRoutingFnFactory {
+    pub root_dir: DirectoryProxy,
+}
+
+impl ProxyingRoutingFnFactory {
+    pub fn new(root_dir: DirectoryProxy) -> Self {
+        ProxyingRoutingFnFactory { root_dir }
+    }
+}
+
+impl CapabilityRoutingFnFactory for ProxyingRoutingFnFactory {
+    fn create_route_fn(
+        &self,
+        _abs_moniker: &AbsoluteMoniker,
+        _capability: Capability,
+    ) -> Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send> {
+        // Create a DirectoryProxy for use by every route function we stamp out.
+        let (client_chan, server_chan) = zx::Channel::create().unwrap();
+        let flags = OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE;
+        self.root_dir
+            .clone(flags, ServerEnd::<NodeMarker>::new(server_chan.into()))
+            .expect("Unable to clone root directory.");
+        let dir = ClientEnd::<DirectoryMarker>::new(client_chan)
+            .into_proxy()
+            .expect("failed to create directory proxy");
+        Box::new(
+            move |flags: u32,
+                  mode: u32,
+                  relative_path: String,
+                  server_end: ServerEnd<NodeMarker>| {
+                if !relative_path.is_empty() {
+                    dir.open(flags, mode, &relative_path, server_end)
+                        .expect("Unable to open 'dir'.");
+                } else {
+                    dir.clone(flags, server_end).expect("Unable to clone 'dir'.");
+                }
+            },
+        )
+    }
+}
+
 pub struct MockResolver {
     components: HashMap<String, ComponentDecl>,
 }
diff --git a/src/sys/component_manager/src/model/testing/mod.rs b/src/sys/component_manager/src/model/testing/mod.rs
index 68e3b56..fe7660b 100644
--- a/src/sys/component_manager/src/model/testing/mod.rs
+++ b/src/sys/component_manager/src/model/testing/mod.rs
@@ -5,3 +5,4 @@
 pub mod mocks;
 pub mod routing_test_helpers;
 pub mod test_hook;
+pub mod test_utils;
diff --git a/src/sys/component_manager/src/model/testing/test_hook.rs b/src/sys/component_manager/src/model/testing/test_hook.rs
index 77421fc..e26afd4 100644
--- a/src/sys/component_manager/src/model/testing/test_hook.rs
+++ b/src/sys/component_manager/src/model/testing/test_hook.rs
@@ -103,6 +103,7 @@
         &'a self,
         realm: Arc<Realm>,
         realm_state: &'a RealmState,
+        _route_fn_factory: RoutingFnFactory,
     ) -> Result<(), ModelError> {
         await!(self.create_instance_if_necessary(realm.abs_moniker.clone()))?;
         for child_realm in
@@ -150,8 +151,9 @@
         &'a self,
         realm: Arc<Realm>,
         realm_state: &'a RealmState,
+        route_fn_factory: RoutingFnFactory,
     ) -> BoxFuture<Result<(), ModelError>> {
-        Box::pin(self.on_bind_instance_async(realm, &realm_state))
+        Box::pin(self.on_bind_instance_async(realm, &realm_state, route_fn_factory))
     }
 
     fn on_add_dynamic_child(&self, realm: Arc<Realm>) -> BoxFuture<Result<(), ModelError>> {
diff --git a/src/sys/component_manager/src/model/testing/test_utils.rs b/src/sys/component_manager/src/model/testing/test_utils.rs
new file mode 100644
index 0000000..3f849cc
--- /dev/null
+++ b/src/sys/component_manager/src/model/testing/test_utils.rs
@@ -0,0 +1,39 @@
+// 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 {fidl_fuchsia_io::DirectoryProxy, std::path::PathBuf};
+
+pub async fn dir_contains<'a>(
+    root_proxy: &'a DirectoryProxy,
+    path: &'a str,
+    entry_name: &'a str,
+) -> bool {
+    let dir = io_util::open_directory(&root_proxy, &PathBuf::from(path))
+        .expect("Failed to open directory");
+    let entries = await!(files_async::readdir(&dir)).expect("readdir failed");
+    let listing = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+    listing.contains(&String::from(entry_name))
+}
+
+pub async fn list_directory<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
+    let entries = await!(files_async::readdir(&root_proxy)).expect("readdir failed");
+    let mut items = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+    items.sort();
+    items
+}
+
+pub async fn list_directory_recursive<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
+    let dir = io_util::clone_directory(&root_proxy).expect("Failed to clone DirectoryProxy");
+    let entries = await!(files_async::readdir_recursive(dir)).expect("readdir failed");
+    let mut items = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+    items.sort();
+    items
+}
+
+pub async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
+    let file_proxy =
+        io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
+    let res = await!(io_util::read_file(&file_proxy));
+    res.expect("Unable to read file.")
+}
diff --git a/src/sys/component_manager/tests/BUILD.gn b/src/sys/component_manager/tests/BUILD.gn
index 973622f..a2b4724 100644
--- a/src/sys/component_manager/tests/BUILD.gn
+++ b/src/sys/component_manager/tests/BUILD.gn
@@ -11,6 +11,7 @@
   edition = "2018"
   source_root = "hub_integration_test.rs"
   deps = [
+    "//examples/components/routing/fidl:echo-rustc",
     "//garnet/lib/rust/cm_fidl_translator",
     "//garnet/lib/rust/cm_rust",
     "//garnet/lib/rust/io_util",
@@ -37,7 +38,8 @@
   deps = [
     ":hub_integration_test_bin",
     ":mock_pkg_resolver_bin",
-    "//examples/components/basic:echo_args_bin",
+    "//examples/components/routing/echo_client",
+    "//examples/components/routing/echo_server",
   ]
 
   meta = [
@@ -46,8 +48,16 @@
       dest = "mock_pkg_resolver.cmx"
     },
     {
-      path = rebase_path("//examples/components/basic/meta/echo_args.cml")
-      dest = "echo_args.cm"
+      path = rebase_path("//examples/components/routing/meta/echo_server.cml")
+      dest = "echo_server.cm"
+    },
+    {
+      path = rebase_path("//examples/components/routing/meta/echo_client.cml")
+      dest = "echo_client.cm"
+    },
+    {
+      path = rebase_path("meta/echo_realm_for_hub_integration.cml")
+      dest = "echo_realm.cm"
     },
   ]
 
@@ -56,7 +66,10 @@
       name = "mock_pkg_resolver"
     },
     {
-      name = "echo_args"
+      name = "echo_server"
+    },
+    {
+      name = "echo_client"
     },
   ]
 
diff --git a/src/sys/component_manager/tests/hub_integration_test.rs b/src/sys/component_manager/tests/hub_integration_test.rs
index dab32a2..529f993 100644
--- a/src/sys/component_manager/tests/hub_integration_test.rs
+++ b/src/sys/component_manager/tests/hub_integration_test.rs
@@ -8,26 +8,20 @@
     component_manager_lib::{
         ambient::RealAmbientEnvironment,
         elf_runner::{ElfRunner, ProcessLauncherConnector},
-        model::{self, Hub, Model, ModelParams},
+        model::{self, Hub, Model, ModelParams, testing::test_utils::{list_directory, read_file}},
         startup,
     },
     failure::{self, Error},
     fidl::endpoints::{ClientEnd, ServerEnd},
+    fidl_fidl_examples_routing_echo as fecho,
     fidl_fuchsia_io::{
-        DirectoryMarker, DirectoryProxy, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
+        DirectoryMarker, MODE_TYPE_SERVICE, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
     },
     fuchsia_vfs_pseudo_fs::directory::{self, entry::DirectoryEntry},
     fuchsia_zircon as zx,
-    std::{iter, path::PathBuf, sync::Arc, vec::Vec},
+    std::{iter, sync::Arc, vec::Vec, path::PathBuf},
 };
 
-async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
-    let file_proxy =
-        io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
-    let res = await!(io_util::read_file(&file_proxy));
-    res.expect("Unable to read file.")
-}
-
 #[fuchsia_async::run_singlethreaded]
 async fn main() -> Result<(), Error> {
     let args = startup::Arguments { use_builtin_process_launcher: false, ..Default::default() };
@@ -36,7 +30,7 @@
     let runner = ElfRunner::new(launcher_connector);
     let resolver_registry = startup::available_resolvers()?;
     let root_component_url =
-        "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_args.cm".to_string();
+        "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_realm.cm".to_string();
 
     let (client_chan, server_chan) = zx::Channel::create().unwrap();
     let mut root_directory = directory::simple::empty();
@@ -68,9 +62,29 @@
         .into_proxy()
         .expect("failed to create directory proxy");
 
-    // These args are from echo_args.cml.
-    assert_eq!("Hippos", await!(read_file(&hub_proxy, "self/exec/runtime/args/0")));
-    assert_eq!("rule!", await!(read_file(&hub_proxy, "self/exec/runtime/args/1")));
+    // Verify that echo_realm has two children.
+    let children_dir_proxy = io_util::open_directory(&hub_proxy, &PathBuf::from("self/children"))
+        .expect("Failed to open directory");
+    assert_eq!(vec!["echo_client", "echo_server"], await!(list_directory(&children_dir_proxy)));
+
+    // These args are from echo_client.cml.
+    assert_eq!("Hippos", await!(read_file(&hub_proxy, "self/children/echo_client/exec/runtime/args/0")));
+    assert_eq!("rule!", await!(read_file(&hub_proxy, "self/children/echo_client/exec/runtime/args/1")));
+
+    let svc_dir = "self/children/echo_client/exec/in/svc";
+    let svc_dir_proxy = io_util::open_directory(&hub_proxy, &PathBuf::from(svc_dir))
+        .expect("Failed to open directory");
+    let echo_service_name = "fidl.examples.routing.echo.Echo";
+    assert_eq!(vec![echo_service_name], await!(list_directory(&svc_dir_proxy)));
+
+    // Verify that we can connect to the echo service from the hub.
+    let echo_service = format!("{}/{}", svc_dir, echo_service_name);
+    let node_proxy =
+        io_util::open_node(&hub_proxy, &PathBuf::from(echo_service), MODE_TYPE_SERVICE)
+            .expect("failed to open echo service");
+    let echo_proxy = fecho::EchoProxy::new(node_proxy.into_channel().unwrap());
+    let res = await!(echo_proxy.echo_string(Some("hippos")));
+    assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string()));
 
     Ok(())
 }
diff --git a/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml b/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml
new file mode 100644
index 0000000..9edc121
--- /dev/null
+++ b/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml
@@ -0,0 +1,30 @@
+// Realm for integration test that provisions an Echo client and service and eagerly runs the
+// client. We don't use the routing examples's echo_realm.cml because we need the component URLs
+// to refer to the test package.
+{
+    // Route Echo service from server to client.
+    "offer": [
+        {
+            "service": "/svc/fidl.examples.routing.echo.Echo",
+            "from": "#echo_server",
+            "to": [
+                {
+                    "dest": "#echo_client",
+                },
+            ],
+        },
+    ],
+    // Two children: a server and client. "echo_client" has "eager" startup so it
+    // will be started along with the realm.
+    "children": [
+        {
+            "name": "echo_server",
+            "url": "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_server.cm",
+        },
+        {
+            "name": "echo_client",
+            "url": "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_client.cm",
+            "startup": "eager",
+        },
+    ],
+}