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.
// 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.
// 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
examples.routing.echo
to implement client and server components.
Start the emulator
If you do not already have an instance running, start FEMU with networking support:
ffx emu start workstation_eng.x64 --headless
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 new project directory in your Bazel workspace for a new component
called echo_server
:
mkdir -p fuchsia-codelab/echo-server
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/echo-server
|- BUILD.bazel
|- meta
| |- echo_server.cml
|
|- main.cc
Create the echo-server/meta/echo_server.cml
component manifest, 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
:
{
include: [
"syslog/client.shard.cml",
"inspect/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_cpp",
},
// Capabilities provided by this component.
capabilities: [
{ protocol: "examples.routing.echo.Echo" },
],
expose: [
{
protocol: "examples.routing.echo.Echo",
from: "self",
},
],
}
Add the following BUILD.bazel
rules to build and package the server component:
echo-server/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
)
fuchsia_cc_binary(
name = "echo_server_cpp",
srcs = [
"main.cc",
],
deps = [
"//fuchsia-codelab/echo-fidl:examples.routing.echo.fidl_cc",
"@fuchsia_sdk//pkg/async-default",
"@fuchsia_sdk//pkg/async-loop",
"@fuchsia_sdk//pkg/async-loop-cpp",
"@fuchsia_sdk//pkg/async-loop-default",
"@fuchsia_sdk//pkg/fdio",
"@fuchsia_sdk//pkg/inspect",
"@fuchsia_sdk//pkg/inspect_component_cpp",
"@fuchsia_sdk//pkg/component_outgoing_cpp",
"@fuchsia_sdk//pkg/syslog",
],
)
fuchsia_component_manifest(
name = "manifest",
src = "meta/echo_server.cml",
component_name = "echo_server_component",
includes = [
"@fuchsia_sdk//pkg/inspect:client",
"@fuchsia_sdk//pkg/syslog:client",
],
)
fuchsia_component(
name = "echo_server_component",
component_name = "echo_server_component",
manifest = ":manifest",
visibility = ["//visibility:public"],
deps = [
":echo_server_cpp",
],
)
Implement the server
Open the main source file and replace the import statements with the following code:
echo-server/main.cc
:
#include <fidl/examples.routing.echo/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/inspect/component/cpp/component.h>
#include <lib/syslog/global.h>
Add the following code to implement the protocol request handler:
echo-server/main.cc
:
// Handler for incoming FIDL protocol requests
class EchoImplementation : public fidl::Server<examples_routing_echo::Echo> {
public:
// The handler for `examples.routing.echo/Echo.EchoString` requests.
//
// Replies back to the caller with the original request value.
void EchoString(EchoStringRequest &request,
EchoStringCompleter::Sync &completer) override {
completer.Reply({{request.value()}});
}
// Called when the FIDL connection is torn down.
void OnUnbound(fidl::UnbindInfo info,
fidl::ServerEnd<examples_routing_echo::Echo> server_end) {
if (info.is_user_initiated()) {
return;
}
if (info.is_peer_closed()) {
// The peer (the client) closed their endpoint.
FX_LOG(DEBUG, "echo_server", "Client disconnected.");
} else {
// Treat other unbind causes as errors.
FX_LOGF(ERROR, "echo_server", "Server error: %s", info.status_string());
}
}
};
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.
Add the following code to main()
to serve the Echo
protocol:
echo-server/main.cc
:
int main(int argc, const char **argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
component::OutgoingDirectory outgoing =
component::OutgoingDirectory(loop.dispatcher());
// Initialize inspect
inspect::ComponentInspector inspector(loop.dispatcher(), {});
inspector.Health().StartingUp();
// Serve the Echo protocol
std::unique_ptr<EchoImplementation> echo_instance =
std::make_unique<EchoImplementation>();
zx::result result = outgoing.AddProtocol<examples_routing_echo::Echo>(
std::move(echo_instance));
if (result.is_error()) {
FX_LOGF(ERROR, "echo_server", "Failed to add Echo protocol: %s",
result.status_string());
return -1;
}
result = outgoing.ServeFromStartupInfo();
if (result.is_error()) {
FX_LOGF(ERROR, "echo_server", "Failed to serve outgoing directory: %s",
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/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.
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 new project directory in your Bazel workspace for a new component
called echo_client
:
mkdir -p fuchsia-codelab/echo-client
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/echo-client
|- BUILD.bazel
|- meta
| |- echo_client.cml
|
|- main.cc
Create the echo-client/meta/echo_client.cml
component manifest and configure
the client component to request the examples.routing.echo.Echo
capability
exposed by the server:
echo-client/meta/echo_client.cml
:
{
include: [
"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_cpp",
// Program arguments
args: [ "Hello Fuchsia!" ],
},
// Capabilities used by this component.
use: [
{ protocol: "examples.routing.echo.Echo" },
],
}
Add the following BUILD.bazel
rules to build and package the client component:
echo-client/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
)
fuchsia_cc_binary(
name = "echo_client_cpp",
srcs = [
"main.cc",
],
deps = [
"//fuchsia-codelab/echo-fidl:examples.routing.echo.fidl_cc",
"@fuchsia_sdk//pkg/async-default",
"@fuchsia_sdk//pkg/async-loop",
"@fuchsia_sdk//pkg/async-loop-cpp",
"@fuchsia_sdk//pkg/async-loop-default",
"@fuchsia_sdk//pkg/fdio",
"@fuchsia_sdk//pkg/component_incoming_cpp",
"@fuchsia_sdk//pkg/syslog",
],
)
fuchsia_component_manifest(
name = "manifest",
src = "meta/echo_client.cml",
component_name = "echo_client_component",
includes = ["@fuchsia_sdk//pkg/syslog:client"],
)
fuchsia_component(
name = "echo_client_component",
component_name = "echo_client_component",
deps = [
":echo_client_cpp",
],
manifest = ":manifest",
visibility = ["//visibility:public"],
)
Implement the client
Similar to echo
, the client passes the program arguments as a message
to the server. Recall that the arguments are described in the program
block of
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_cpp",
// Program arguments
args: [ "Hello Fuchsia!" ],
},
Open the main source file and replace the import statements with the following code:
echo-client/main.cc
:
#include <fidl/examples.routing.echo/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/global.h>
#include <cstdlib>
#include <iostream>
#include <string>
Add the following code to main()
to connect to the Echo
protocol and send
a request:
echo-client/main.cc
:
int main(int argc, const char *argv[], char *envp[]) {
// Connect to FIDL protocol
zx::result client_end = component::Connect<examples_routing_echo::Echo>();
if (!client_end.is_ok()) {
FX_LOGF(ERROR, "echo_client", "Error connecting to Echo protocol: %s",
client_end.status_string());
return -1;
}
fidl::SyncClient client{std::move(*client_end)};
// Send messages over FIDL interface for each argument
for (int i = 1; i < argc; i++) {
fidl::Result result = client->EchoString({argv[i]});
ZX_ASSERT(result.is_ok());
auto response = result->response();
if (!response.has_value()) {
FX_LOG(INFO, "echo_client", "echo_string got empty result");
} else {
FX_LOGF(INFO, "echo_client", "Server response: %s", response->c_str());
}
}
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 component definition:
mkdir -p fuchsia-codelab/echo-realm
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/echo-realm
|- BUILD.bazel
|- meta
|- echo_realm.cml
Create a new component manifest file echo-realm/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",
},
],
// Route Echo service from server to client.
offer: [
{
protocol: "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 examples.routing.echo.Echo
protocol capability to the
client.
Add a BUILD.bazel
file to create a build target for the realm component and a
Fuchsia package containing the server and client:
echo-realm/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_component",
"fuchsia_component_manifest",
"fuchsia_package",
)
fuchsia_component_manifest(
name = "manifest",
src = "meta/echo_realm.cml",
component_name = "echo_realm",
)
fuchsia_component(
name = "echo_realm",
component_name = "echo_realm",
manifest = ":manifest",
)
fuchsia_package(
name = "pkg",
package_name = "echo-realm",
visibility = ["//visibility:public"],
components = [
":echo_realm",
"//fuchsia-codelab/echo-client:echo_client_component",
"//fuchsia-codelab/echo-server:echo_server_component",
],
)
Build and publish the package to the fuchsiasamples.com
repository:
bazel run //fuchsia-codelab/echo-realm:pkg.publish -- \
--repo_name fuchsiasamples.com
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://fuchsiasamples.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://fuchsiasamples.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
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
examples.routing.echo.Echo
Execution State: Running
Job ID: 474691
Process ID: 474712
Running for: 2026280474361 ticks
Merkle root: 666c40477785f89b0ace22b30d65f1338f1d308ecceacb0f65f5140baa889e1b
Outgoing Capabilities: diagnostics
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