Google is committed to advancing racial equity for Black communities. See how.

Connecting components

A protocol handle is a well-known object that provides an implementation of a FIDL protocol that is discoverable using component namespaces. The component framework facilitates protocol discovery between components using capabilities. Capability routing describes which component should act as the provider for any given client. Once the proper components are identified, the Component Manager initiates connections between components using handles found in each component's namespace.

Consider the following example for a fuchsia.example.Foo protocol:

capability routing and protocol serving

The diagram highlights the main elements involved in performing the connection:

  1. The provider component statically declares the protocol in the capabilities section of the manifest. This enables the component framework to perform capability routing.
  2. A client component statically requests the protocol in the use section of the manifest. This creates the /svc/fuchsia.example.Foo protocol entry in the client's namespace if capability routing is successful.
  3. The provider code publishes the implementation at runtime. This creates a protocol entry at /svc/fuchsia.example.Foo in the provider's outgoing directory.
  4. The client code connects to the protocol handle at runtime. This opens a FIDL connection to the implementation running in the provider component.

Publishing a protocol implementation

Components that implement a FIDL protocol declare and expose that protocol as a capability in their component manifest. This enables the component framework to perform capability routing from this component to others in the topology that request the capability.

{
    // ...
    capabilities: [
        { protocol: "fuchsia.example.Foo" },
    ],
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}

Capability routing describes the access rights for the protocol, but it does not establish the necessary endpoints for a connection. Components must publish the implementation as an /svc/ handle in the outgoing directory using the fuchsia.io protocol. The generated FIDL bindings wrap this handle and enable the provider to connect a request handle to begin receiving FIDL messages.

async fn main() -> Result<(), anyhow::Error> {
    let mut service_fs = ServiceFs::new_local();

    // Serve the protocol
    service_fs.dir("svc").add_fidl_service(PROTOCOL_NAME);
    service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;

    // ...

    Ok(())
}

Connecting to a protocol implementation

Client components declare the protocol as a required capability in their component manifest. This allows the component framework to determine whether the component has the rights to access protocol implementation. If a valid route exists, the component's namespace contains a corresponding /svc/ handle.

{
    // ...
    use: [
        { protocol: "fuchsia.example.Foo" },
    ],
}

The client component uses the fuchsia.io protocol to establish a connection to the protocol implementation and open a channel. The generated FIDL bindings wrap this channel and enable the client to begin sending messages to the provider.

async fn main() -> Result<(), anyhow::Error> {
    // Connect to FIDL protocol
    let protocol = connect_to_protocol::<FooMarker>().expect("error connecting to echo");

    // ...

    Ok(())
}

Exercise: Echo server and client

In this section, you'll use the generated FIDL bindings for fidl.examples.routing.echo to implement client and server components in Rust.

Start the emulator

If you do not already have an instance running, start FEMU with networking support:

fx vdl start -N --start-package-server

When startup is complete, the emulator prints the following message and opens a shell prompt:

To support fx tools on emulator, please run "fx set-device fuchsia-5254-0063-5e7a"
$

Create the server component

Begin by creating a new component project to implement the echo server. This component will serve the Echo protocol and handle incoming requests.

fx create component --path vendor/fuchsia-codelab/echo-server --lang rust

Add the generated Rust bindings to the BUILD.gn file as a dependency:

echo-server/BUILD.gn:

rustc_binary("bin") {
  output_name = "echo-server"

  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo-rustc",
    ...
  ]

  sources = [ "src/main.rs" ]
}

Declare the Echo protocol as a capability provided by the server component, and expose it for use by the parent realm:

echo-server/meta/echo_server.cml:

{
    // ...

    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
    expose: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "self",
        },

        // ...
    ],
}

Implement the server

Open the main.rs source file and replace the import statements with the following code:

echo-server/src/main.rs:

use anyhow::{self, Context};
use fidl_fidl_examples_routing_echo::{EchoRequest, EchoRequestStream};
use fuchsia_component::server::ServiceFs;
use fuchsia_inspect::{component, health::Reporter};
use futures::prelude::*;

Add the following code to main() to serve the Echo protocol:

echo-server/src/main.rs:

// Wrap protocol requests being served.
enum IncomingRequest {
    Echo(EchoRequestStream),
}

#[fuchsia::component(logging = false)]
async fn main() -> Result<(), anyhow::Error> {
    let mut service_fs = ServiceFs::new_local();

    // Initialize inspect
    component::health().set_starting_up();
    inspect_runtime::serve(component::inspector(), &mut service_fs)?;

    // Serve the Echo protocol
    service_fs.dir("svc").add_fidl_service(IncomingRequest::Echo);
    service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;

    // Component is serving and ready to handle incoming requests
    component::health().set_ok();

    // Attach request handler for incoming requests
    service_fs
        .for_each_concurrent(None, |_request: IncomingRequest| async move {
            match _request {
                IncomingRequest::Echo(stream) => handle_echo_request(stream).await,
            }
        })
        .await;

    Ok(())
}

This code performs the following steps to serve the Echo protocol:

  1. Initialize ServiceFs and add an entry under /svc/fidl.examples.routing.echo.Echo in the outgoing directory.
  2. Serve the directory and begin listening for incoming connections.
  3. Attach the handle_echo_request() function as a request handler for any matching Echo requests.

Add the following code to implement handle_echo_request() and handle incoming requests:

echo-server/src/main.rs:

// Handler for incoming service requests
async fn handle_echo_request(mut stream: EchoRequestStream) {
    while let Some(event) = stream.try_next().await.expect("failed to serve echo service") {
        let EchoRequest::EchoString { value, responder } = event;
        responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
    }
}

Each request in the EchoRequestStream is typed by the method name (EchoString) and includes a responder interface to send back the return value. This implementation simply "echoes" the same string value from the request back in the response payload.

Create the client component

Create another new component project to implement the echo client. This component will connect to the protocol implementation and send requests.

fx create component --path vendor/fuchsia-codelab/echo-client --lang rust

Add the generated Rust bindings to the BUILD.gn file as a dependency:

echo-client/BUILD.gn:

rustc_binary("bin") {
  output_name = "echo-client"

  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo-rustc",
    ...
  ]

  sources = [ "src/main.rs" ]
}

Configure the client's component manifest to request the fidl.examples.routing.echo.Echo capability exposed by the server:

echo-client/meta/echo_client.cml:

{
    // ...

    // Capabilities used by this component.
    use: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
}

Implement the client

Similar to echo-args, the client passes the program arguments as a message to the server. Add the following program arguments to echo_client.cml:

echo-client/meta/echo_client.cml:

// Information about the program to run.
program: {
    // Use the built-in ELF runner.
    runner: "elf",

    // The binary to run for this component.
    binary: "bin/echo_client",

    // Program arguments
    args: [ "Hello Fuchsia!" ],
},

Open the main.rs source file and replace the import statements with the following code:

echo-client/src/main.rs:

use anyhow;
use fidl_fidl_examples_routing_echo::EchoMarker;
use fuchsia_component::client::connect_to_protocol;
use tracing;

Add the following code to main() to connect to the Echo protocol and send a request:

echo-client/src/main.rs:

#[fuchsia::component]
async fn main() -> Result<(), anyhow::Error> {
    // Parse arguments, removing binary name
    let mut args: Vec<String> = std::env::args().collect();
    args.remove(0);

    // Connect to FIDL protocol
    let echo = connect_to_protocol::<EchoMarker>().expect("error connecting to echo");

    // Send messages over FIDL interface
    for message in args {
        let out = echo.echo_string(Some(&message)).await.expect("echo_string failed");
        tracing::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
    }

    Ok(())
}

The EchoMarker provides a wrapper to connect to the exposed capability by name and return a handle to the open EchoProxy interface. This proxy contains the echo_string() FIDL protocol method. Since echo_string() is a two-way method, the client blocks waiting for a response after sending the message to the server.

Integrate the components

The capabilities provided by the server must be routed to the client through the component framework. To enable this, you will implement a realm component to act as the parent and manage capability routing.

Create a new project directory for the realm product definition:

mkdir vendor/fuchsia-codelab/echo-realm

Create a new component manifest file meta/echo_realm.cml with the following contents:

echo-realm/meta/echo_realm.cml:

// Example realm that provisions an Echo client and service and eagerly runs the client.
{
    // Two children: a server and client.
    children: [
        {
            name: "echo_server",
            url: "#meta/echo_server.cm",
        },
        {
            name: "echo_client",
            url: "#meta/echo_client.cm",
        },
    ],

    // Route Echo service from server to client.
    offer: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "#echo_server",
            to: "#echo_client",
        },
        {
            protocol: "fuchsia.logger.LogSink",
            from: "parent",
            to: [
                "#echo_client",
                "#echo_server",
            ],
        },
    ],
}

This creates a component realm with the server and client as child components, and routes the fidl.examples.routing.echo.Echo protocol capability to the client.

Add a BUILD.gn file to create a build target for the realm component:

echo-realm/BUILD.gn:

import("//build/components.gni")

fuchsia_component("echo_realm") {
  manifest = "meta/echo_realm.cml"
}

fuchsia_package("echo-realm") {
  deps = [
    ":echo_realm",
    "//vendor/fuchsia-codelab/echo-server:component",
    "//vendor/fuchsia-codelab/echo-client:component",
  ]
}

Update the build configuration to include the new components:

fx set workstation.qemu-x64 \
    --with //vendor/fuchsia-codelab/echo-fidl:echo \
    --with //vendor/fuchsia-codelab/echo-server \
    --with //vendor/fuchsia-codelab/echo-client \
    --with //vendor/fuchsia-codelab/echo-realm

Run fx build again to build the components:

fx build

Add the components to the topology

You will add your component to the ffx-laboratory — a restricted collection used for development inside the product's core realm. Collections enable components to be dynamically created and destroyed at runtime.

Create the component instances by passing the echo-realm component URL and an appropriate moniker inside ffx-laboratory to ffx component create:

ffx component create /core/ffx-laboratory:echo-realm \
    fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm

Verify that instances of the server and client were also created as child components using ffx component show:

​​ffx component show echo
Moniker: /core/ffx-laboratory:echo-realm/echo_client
URL: #meta/echo_client.cm
Type: CML static component
Component State: Unresolved
Execution State: Stopped

Moniker: /core/ffx-laboratory:echo-realm/echo_server
URL: #meta/echo_server.cm
Type: CML static component
Component State: Unresolved
Execution State: Stopped

Moniker: /core/ffx-laboratory:echo-realm
URL: fuchsia-pkg://fuchsia.com/echo-realm#meta/echo_realm.cm
Type: CML dynamic component
Component State: Resolved
Incoming Capabilities (0):
Exposed Capabilities (0):
Execution State: Running
Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b

Verify the component interactions

Start the existing client component instance using ffx component bind:

ffx component bind /core/ffx-laboratory:echo-realm/echo_client

Open another terminal window and verify the log output from the client component:

fx log --only echo

You should see the following output in the device logs:

[echo_client][I] Server response: Hello, Fuchsia!

The server component starts once the client makes a connection to the fidl.examples.routing.echo.Echo capability and continues running to serve additional FIDL requests.

Use ffx component show the see the echo server running in the component instance tree:

ffx component show echo_server
Moniker: /core/ffx-laboratory:echo-realm/echo_server
URL: #meta/echo_server.cm
Type: CML static component
Component State: Resolved
Incoming Capabilities (1):
  fuchsia.logger.LogSink
Exposed Capabilities (2):
  diagnostics
  fidl.examples.routing.echo.Echo
Execution State: Running
Job ID: 474691
Process ID: 474712
Process Start Time (ticks): 2026280474361
Process Start Time (UTC estimate): (not available)
Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
Outgoing Capabilities (2):
  diagnostics
  fidl.examples.routing.echo.Echo