FIDL tutorial

This guide explains how to export a FIDL service from a parent driver and utilize it in a child driver. This guide assumes familiarity with the following concepts:

For a complete working example in the Fuchsia source tree, see the Zircon transport example.

FIDL Protocol Definition

The following snippets utilize this FIDL definition:

library fidl.examples.echo;

const MAX_STRING_LENGTH uint64 = 32;

@discoverable
closed protocol Echo {
    /// Returns the input.
    strict EchoString(struct {
        value string:<MAX_STRING_LENGTH, optional>;
    }) -> (struct {
        response string:<MAX_STRING_LENGTH, optional>;
    });
};

service EchoService {
    echo client_end:Echo;
};

Parent Driver (The Server)

The parent driver implements the fidl.examples.echo/Echo protocol, serves fidl.examples.echo.EchoService in its outgoing directory, and offers that service when adding a child node.

Driver Manifest

In the parent driver's manifest (.dml), declare and expose the FIDL service capability:

capabilities: [
    { service: "fidl.examples.echo.EchoService" },
],
expose: [
    {
        service: "fidl.examples.echo.EchoService",
        from: "self",
    },
],

Server Code

#include <fidl/fidl.examples.echo/cpp/wire.h>
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <lib/driver/logging/cpp/logger.h>

// This class implements the fidl.examples.echo/Echo FIDL protocol using the
// C++ Wire bindings.
class ParentDriver : public fdf::DriverBase2,
                     public fidl::WireServer<fidl_examples_echo::Echo> {
 public:
  ParentDriver() : fdf::DriverBase2("parent") {}

  zx::result<> Start(fdf::DriverContext context) override {
    // Add the FIDL service we wish to export to our child to our outgoing
    // directory. When a connection is attempted we bind the server end of
    // the channel pair to our server implementation using the driver's default
    // dispatcher.
    fidl_examples_echo::EchoService::InstanceHandler handler({
        .echo = bindings_.CreateHandler(this, dispatcher(),
                                        fidl::kIgnoreBindingClosure),
    });

    zx::result result =
        outgoing()->AddService<fidl_examples_echo::EchoService>(std::move(handler));
    if (result.is_error()) {
      fdf::error("Failed to add EchoService to outgoing directory: {}", result);
      return result.take_error();
    }

    // Declare our outgoing service offer and add the child node. The driver
    // framework uses this offer to route the service to the child and
    // automatically populate node properties for binding.
    std::vector<fuchsia_driver_framework::NodeProperty2> properties = {};
    zx::result child_result = AddChild(
        "echo_child", properties,
        std::array{fdf::MakeOffer2<fidl_examples_echo::EchoService>()});
    if (child_result.is_error()) {
      fdf::error("Failed to add child node: {}", child_result);
      return child_result.take_error();
    }
    controller_.Bind(std::move(child_result.value()), dispatcher());

    return zx::ok();
  }

 private:
  // Implementation of the method required by our FIDL protocol.
  void EchoString(EchoStringRequestView request,
                  EchoStringCompleter::Sync& completer) override {
    completer.Reply(request->value);
  }

  fidl::WireClient<fuchsia_driver_framework::NodeController> controller_;
  // Ensures that the FIDL connections do not outlive the driver instance.
  fidl::ServerBindingGroup<fidl_examples_echo::Echo> bindings_;
};

FUCHSIA_DRIVER_EXPORT2(ParentDriver);

Child Driver (The Client)

Driver Manifest

In the child driver's manifest (.dml), declare that the driver uses the service:

use: [
    { service: "fidl.examples.echo.EchoService" },
],

Note that the child driver's bind rules are automatically generated from its DML.

Client Code

The following code snippet shows how a child driver that has bound to the parent node connects to and calls the Echo protocol:

#include <fidl/fidl.examples.echo/cpp/wire.h>
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/driver/logging/cpp/logger.h>

class ChildDriver : public fdf::DriverBase2 {
 public:
  ChildDriver() : fdf::DriverBase2("child") {}

  zx::result<> Start(fdf::DriverContext context) override {
    // Connect to the protocol offered by our parent driver through our incoming
    // namespace. We do not need to specify the service or protocol name string
    // because Connect is templated on the generated service member type.
    zx::result client_end =
        context.incoming().Connect<fidl_examples_echo::EchoService::Echo>();
    if (client_end.is_error()) {
      fdf::error("Failed to connect to Echo protocol: {}", client_end);
      return client_end.take_error();
    }

    // Turn the client side of the endpoint pair into a synchronous client.
    fidl::WireSyncClient client{std::move(client_end.value())};

    // Utilize our client to make calls.
    constexpr std::string_view kInput = "Test String";

    fidl::WireResult result =
        client->EchoString(fidl::StringView::FromExternal(kInput));
    if (!result.ok()) {
      fdf::error("Failed to call EchoString: {}", result.error());
      return zx::error(result.status());
    }
    if (result->response.get() != kInput) {
      fdf::error("Unexpected response: Actual: \"{}\", Expected: \"{}\"",
                 result->response.get(), kInput);
      return zx::error(ZX_ERR_INTERNAL);
    }

    return zx::ok();
  }
};

FUCHSIA_DRIVER_EXPORT2(ChildDriver);