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:
The diagram highlights the main elements involved in performing the connection:
- The provider component statically declares the protocol in the
capabilities
section of the manifest. This enables the component framework to perform capability routing. - 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. - The provider code publishes the implementation at runtime. This creates a protocol entry at
/svc/fuchsia.example.Foo
in the provider's outgoing directory. - 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.
Rust
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")?;
C++
// Serve the protocol FooImplementation instance; fidl::Binding<fuchsia::example::Foo> binding(&instance); instance.event_sender_ = &binding.events(); fidl::InterfaceRequestHandler<fuchsia::example::Foo> handler = [&](fidl::InterfaceRequest<fuchsia::example::Foo> request) { binding.Bind(std::move(request)); }; context->outgoing()->AddPublicService(std::move(handler));
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.
Rust
// Connect to FIDL protocol let protocol = connect_to_protocol::<FooMarker>().expect("error connecting to echo");
C++
// Connect to FIDL protocol fuchsia::example::FooSyncPtr proxy; auto context = sys::ComponentContext::Create(); context->svc()->Connect(proxy.NewRequest());
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 the emulator:
Start a new emulator instance:
ffx emu start --headless
When startup is complete, the emulator prints the following message and returns:
Logging to "$HOME/.local/share/Fuchsia/ffx/emu/instances/fuchsia-emulator/emulator.log" Waiting for Fuchsia to start (up to 60 seconds)........ Emulator is ready.
Start a package server to enable the emulator to load software packages:
fx serve
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.
Create a project scaffold for a new component called echo-server
in the //vendor/fuchsia-codelab
directory:
mkdir -p vendor/fuchsia-codelab/echo-server
Create the following file and directory structure in the new project directory:
Rust
//vendor/fuchsia-codelab/echo-server |- BUILD.gn |- meta | |- echo.cml | |- src |- main.rs
C++
//vendor/fuchsia-codelab/echo-server |- BUILD.gn |- meta | |- echo.cml | |- main.cc
Add the following build rules to your BUILD.gn
file to build and package the server component:
Rust
echo-server/BUILD.gn
:
import("//build/components.gni") import("//build/rust/rustc_binary.gni") rustc_binary("bin") { output_name = "echo-server" edition = "2021" deps = [ "//vendor/fuchsia-codelab/echo-fidl:echo_rust", "//src/lib/diagnostics/inspect/runtime/rust", "//src/lib/diagnostics/inspect/rust", "//src/lib/fuchsia", "//src/lib/fuchsia-component", "//third_party/rust_crates:anyhow", "//third_party/rust_crates:futures", ] sources = [ "src/main.rs" ] } # Unpackaged component "#meta/echo_server.cm" fuchsia_component("echo_server_cmp") { component_name = "echo_server" manifest = "meta/echo_server.cml" deps = [ ":bin" ] } fuchsia_package("echo-server") { package_name = "echo-server" deps = [ ":component" ] }
C++
echo-server/BUILD.gn
:
import("//build/components.gni") executable("bin") { output_name = "echo-server" sources = [ "main.cc" ] deps = [ "//vendor/fuchsia-codelab/echo-fidl:echo_cpp", "//sdk/lib/async-loop:async-loop-cpp", "//sdk/lib/async-loop:async-loop-default", "//sdk/lib/inspect/component/cpp", "//sdk/lib/sys/cpp", "//sdk/lib/syslog/cpp", ] } # Unpackaged component "#meta/echo_server.cm" fuchsia_component("echo_server_cmp") { component_name = "echo_server" manifest = "meta/echo_server.cml" deps = [ ":bin" ] } fuchsia_package("echo-server") { package_name = "echo-server" deps = [ ":component" ] }
Declare the Echo
protocol as a capability provided by the server component, and expose it for use by the parent realm:
Rust
echo-server/meta/echo_server.cml
:
{ include: [ "inspect/client.shard.cml", "syslog/client.shard.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-server", }, // Capabilities provided by this component. capabilities: [ { protocol: "fidl.examples.routing.echo.Echo" }, ], expose: [ { protocol: "fidl.examples.routing.echo.Echo", from: "self", }, ], }
C++
echo-server/meta/echo_server.cml
:
{ include: [ "inspect/client.shard.cml", "syslog/client.shard.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-server", }, // 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 source file and replace the import statements with the following code:
Rust
echo-server/src/main.rs
:
use anyhow::Context; use fidl_fidl_examples_routing_echo::{EchoRequest, EchoRequestStream}; use fuchsia_component::server::ServiceFs; use fuchsia_inspect::component; use fuchsia_inspect::health::Reporter; use futures::prelude::*;
C++
echo-server/main.cc
:
#include <fidl/fidl.examples.routing.echo/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> #include <lib/async-loop/default.h> #include <lib/fidl/cpp/binding.h> #include <lib/inspect/component/cpp/component.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/log_settings.h> #include <lib/syslog/cpp/macros.h>
Add the following code to main()
to serve the Echo
protocol:
Rust
echo-server/src/main.rs
:
// Wrap protocol requests being served. enum IncomingRequest { Echo(EchoRequestStream), } #[fuchsia::main(logging = false)] async fn main() -> Result<(), anyhow::Error> { let mut service_fs = ServiceFs::new_local(); // Initialize inspect component::health().set_starting_up(); let _inspect_server_task = inspect_runtime::publish( component::inspector(), inspect_runtime::PublishOptions::default(), ); // 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:
- Initialize
ServiceFs
and add an entry under/svc/fidl.examples.routing.echo.Echo
in the outgoing directory. - Serve the directory and begin listening for incoming connections.
- Attach the
handle_echo_request()
function as a request handler for any matchingEcho
requests.
C++
echo-server/main.cc
:
int main(int argc, const char** argv) { async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); async_dispatcher_t* dispatcher = loop.dispatcher(); // Initialize inspect inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{}); inspector.Health().StartingUp(); component::OutgoingDirectory outgoing_directory = component::OutgoingDirectory(dispatcher); zx::result result = outgoing_directory.ServeFromStartupInfo(); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string(); return -1; } result = outgoing_directory.AddUnmanagedProtocol<fidl_examples_routing_echo::Echo>( [dispatcher](fidl::ServerEnd<fidl_examples_routing_echo::Echo> server_end) { fidl::BindServer(dispatcher, std::move(server_end), std::make_unique<EchoImplementation>()); }); if (result.is_error()) { FX_LOGS(ERROR) << "Failed to add Echo protocol: " << result.status_string(); return -1; } // Component is serving and ready to handle incoming requests inspector.Health().Ok(); return loop.Run(); }
This code performs the following steps to serve the Echo
protocol:
- Initialize
ComponentContext
and add an entry under/svc/fidl.examples.routing.echo.Echo
in the outgoing directory. - Serve the directory and begin listening for incoming connections.
- Attach the
EchoImplementation
instance as a request handler for any matchingEcho
requests.
Add the following code to implement the protocol request handler:
Rust
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.
C++
echo-server/main.cc
:
// Handler for incoming service requests class EchoImplementation : public fidl::Server<fidl_examples_routing_echo::Echo> { public: void EchoString(EchoStringRequest& request, EchoStringCompleter::Sync& completer) override { completer.Reply({{.response = request.value()}}); } };
Each Echo
protocol method has a corresponding override function (EchoString()
) and includes a callback 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.
Create a project scaffold for a new component called echo-client
in the //vendor/fuchsia-codelab
directory:
mkdir -p vendor/fuchsia-codelab/echo-client
Create the following file and directory structure in the new project directory:
Rust
//vendor/fuchsia-codelab/echo-client |- BUILD.gn |- meta | |- echo.cml | |- src |- main.rs
C++
//vendor/fuchsia-codelab/echo-client |- BUILD.gn |- meta | |- echo.cml | |- main.cc
Add the following build rules to your BUILD.gn
file to build and package the client component:
Rust
echo-client/BUILD.gn
:
import("//build/components.gni") import("//build/rust/rustc_binary.gni") rustc_binary("bin") { output_name = "echo-client" edition = "2021" deps = [ "//vendor/fuchsia-codelab/echo-fidl:echo_rust", "//src/lib/fuchsia", "//src/lib/fuchsia-component", "//third_party/rust_crates:anyhow", "//third_party/rust_crates:log", ] sources = [ "src/main.rs" ] } # Unpackaged component "#meta/echo_client.cm" fuchsia_component("echo_client_cmp") { component_name = "echo_client" manifest = "meta/echo_client.cml" deps = [ ":bin" ] } fuchsia_package("echo-client") { package_name = "echo-client" deps = [ ":component" ] }
C++
echo-client/BUILD.gn
:
import("//build/components.gni") executable("bin") { output_name = "echo-client" sources = [ "main.cc" ] deps = [ "//vendor/fuchsia-codelab/echo-fidl:echo_cpp", "//sdk/lib/async-loop:async-loop-cpp", "//sdk/lib/async-loop:async-loop-default", "//sdk/lib/component/incoming/cpp", "//sdk/lib/sys/cpp", "//sdk/lib/syslog/cpp", ] } # Unpackaged component "#meta/echo_client.cm" fuchsia_component("echo_client_cmp") { component_name = "echo_client" manifest = "meta/echo_client.cml" deps = [ ":bin" ] } fuchsia_package("echo-client") { package_name = "echo-client" deps = [ ":component" ] }
Configure the client's component manifest to request the fidl.examples.routing.echo.Echo
capability exposed by the server:
Rust
echo-client/meta/echo_client.cml
:
{ include: [ // Enable logging on stdout "syslog/client.shard.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!" ], }, // Capabilities used by this component. use: [ { protocol: "fidl.examples.routing.echo.Echo" }, ], }
C++
echo-client/meta/echo_client.cml
:
{ include: [ // Enable logging. "syslog/client.shard.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!" ], }, // 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
:
Rust
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!" ], },
C++
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 source file and replace the import statements with the following code:
Rust
echo-client/src/main.rs
:
use fidl_fidl_examples_routing_echo::EchoMarker; use fuchsia_component::client::connect_to_protocol;
C++
echo-client/main.cc
:
#include <fidl/fidl.examples.routing.echo/cpp/fidl.h> #include <lib/component/incoming/cpp/protocol.h> #include <lib/fidl/cpp/string.h> #include <lib/sys/cpp/component_context.h> #include <lib/syslog/cpp/log_settings.h> #include <lib/syslog/cpp/macros.h> #include <cstdlib> #include <iostream> #include <string>
Add the following code to main()
to connect to the Echo
protocol and send a request:
Rust
echo-client/src/main.rs
:
#[fuchsia::main] 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"); log::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 returns a handle to the open EchoProxy
interface. This proxy contains the echo_string()
FIDL protocol method.
C++
echo-client/main.cc
:
int main(int argc, const char* argv[], char* envp[]) { // Set tags for logging. fuchsia_logging::LogSettingsBuilder builder; builder.WithTags({"echo_client"}).BuildAndInitialize(); // Connect to FIDL protocol zx::result client_end = component::Connect<fidl_examples_routing_echo::Echo>(); if (client_end.is_error()) { FX_LOGS(ERROR) << "Failed to connect to Echo protocol: " << client_end.status_string(); return EXIT_FAILURE; } fidl::SyncClient client(std::move(client_end.value())); // Send messages over FIDL interface for each argument fidl::StringPtr response = nullptr; for (int i = 1; i < argc; i++) { fidl::Result response = client->EchoString({argv[i]}); if (response.is_error()) { FX_LOGS(ERROR) << "echo_string failed: " << response.error_value(); return EXIT_FAILURE; } if (!response->response().has_value()) { FX_LOGS(ERROR) << "echo_string got empty result"; return EXIT_FAILURE; } const std::string& response_value = response->response().value(); FX_LOG_KV(INFO, "Server response", FX_KV("response", response_value)); } return 0; }
The EchoSyncPtr
provides a wrapper to connect to the exposed capability by name and returns a handle to the open proxy interface. This proxy contains the EchoString()
FIDL protocol method.
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 -p vendor/fuchsia-codelab/echo-realm
Create the following file and directory structure in the new project directory:
//vendor/fuchsia-codelab/echo-realm |- BUILD.gn |- meta | |- echo_realm.cml
Create a new component manifest file meta/echo_realm.cml
with the following contents:
echo-realm/meta/echo_realm.cml
:
{ // Two children: a server and client. children: [ { name: "echo_server", url: "#meta/echo_server.cm", }, { name: "echo_client", url: "#meta/echo_client.cm", }, ], offer: [ // Route Echo protocol from server to client. { protocol: "fidl.examples.routing.echo.Echo", from: "#echo_server", to: "#echo_client", }, // Route diagnostics protocols to both children. { dictionary: "diagnostics", 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_eng.x64 \ --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
Then, resolve the echo-realm
component with ffx component resolve
:
ffx component resolve /core/ffx-laboratory:echo-realm
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 Execution State: Stopped Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
Verify the component interactions
Start the existing client component instance using ffx component start
:
ffx component start /core/ffx-laboratory:echo-realm/echo_client
Open another terminal window and verify the log output from the client component:
ffx log --filter 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: fuchsia.logger.LogSink Exposed Capabilities: diagnostics fidl.examples.routing.echo.Echo Execution State: Running Job ID: 474691 Process ID: 474712 Running for: 2026280474361 ticks Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b Outgoing Capabilities: diagnostics fidl.examples.routing.echo.Echo
Destroy the instance
Clean up the echo-realm
instance using the following command:
ffx component destroy /core/ffx-laboratory:echo-realm