## Goal \& motivation

FIDL [protocols and protocol requests](https://fuchsia.dev/docs/reference/fidl/language/language#protocols) are backed by Zircon
channels under the hood. Given the following FIDL definition:

    library foo;

    protocol Calculator {};

    resource struct Record {
        // Client endpoint of a channel speaking the Calculator protocol
        Calculator c;
        // Server endpoint of a channel speaking the Calculator protocol
        request<Calculator> s;
    };

We used to generate a struct with two Zircon channels in LLCPP:

    struct Record {
        zx::channel c;
        zx::channel s;
    };

Any FIDL protocol became just a channel, opening the door to accidentally mixing
up protocol types or directions (here are some instances that were
[identified](https://fuchsia-review.googlesource.com/c/fuchsia/+/463034) [and](https://fuchsia-review.googlesource.com/c/fuchsia/+/466169) [fixed](https://fuchsia-review.googlesource.com/c/fuchsia/+/478491/28/src/firmware/paver/partition-client.h#b46)). To
increase type safety and self-documentation, we have changed the generated code
to the following:

    struct Record {
        // Now it's clear that |c| is a client channel endpoint speaking the |Calculator| protocol.
        fidl::ClientEnd<foo::Calculator> c;
        // Similarly, |s| is a server channel endpoint for that protocol.
        fidl::ServerEnd<foo::Calculator> s;
    };

Similarly, all functions in the LLCPP runtime that previously dealt with
`zx::channel` were updated to speak a more precise type that encodes the
direction and kind of the protocol (for example:
[`fidl::BindServer`](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/server.h?q=fidl::BindServer&ss=fuchsia/fuchsia)).

However, the majority of user code still uses `zx::channel`. They continue to
compile because we have added temporary implicit conversions support to
`fidl::ClientEnd` / `fidl::ServerEnd`, at the cost of type safety. To reap the
benefits of this change across the code base, user code should propagate the
`fidl::ClientEnd` / `fidl::ServerEnd` type through their public interface, as
opposed to locally casting from a raw channel.

## Technical background

[LLCPP typed channel reference](https://fuchsia.dev/docs/reference/fidl/bindings/cpp-bindings#typed-channels)

## How to help

### Picking a task

Search for a BUILD.gn file that contains the string `TODO(https://fxbug.dev/42148734)`. It
would look similar to this:

    # TODO(https://fxbug.dev/42148734): This target uses raw zx::channel with LLCPP which is deprecated.
    # Please migrate to typed channel APIs (fidl::ClientEnd<T>, fidl::ServerEnd<T>).
    # See linked bug for details.
    configs += [ "//build/cpp:fidl-llcpp-deprecated-raw-channels" ]

Remove these lines and `fx build`. If the build succeeds without any warning or
error, skip to the [last step](https://fuchsia.dev/fuchsia-src/contribute/open_projects/graduated/typed_channels#finalize). Otherwise, the warning and errors
point to the deprecated usages. From there, three typical scenarios follow:

### Scenario 1: implementing a server

Migrating servers is quite straightforward - look for places where the server
implementation is inheriting from a class named
[`RawChannelInterface`](https://cs.opensource.google/search?q=RawChannelInterface&sq&ss=fuchsia/fuchsia). That class is a shim that
translates server methods taking `fidl::ClientEnd<P>` / `fidl::ServerEnd<P>`
arguments into ones taking `zx::channel`. Change that to the usual `Interface`
and update method arguments to match:

#### FIDL

    protocol Foo {
        TakeBar(Bar bar);
        HandleBar(request<Bar> bar);
    };

### Before

    class MyServer : public fidl::WireRawChannelInterface<Foo> {
      void TakeBar(zx::channel bar, TakeBarCompleter::Sync& completer) override;
      void HandleBar(zx::channel bar, HandleBarCompleter::Sync& completer) override;
    };

### After

    class MyServer : public Foo::Interface {
      void TakeBar(fidl::ClientEnd<Bar> bar, TakeBarCompleter::Sync& completer) override;
      void HandleBar(fidl::ServerEnd<Bar> bar, HandleBarCompleter::Sync& completer) override;
    };

### Scenario 2: protocol request pipelining

It's common to create a pair of channel endpoints, and pass the server-end to
the protocol implementation. We can avoid creating raw Zircon channels with the
[`fidl::CreateEndpoints<Protocol>`](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/connect_service.h;l=36?q=fidl::CreateEndpoints&ss=fuchsia/fuchsia) method:

### Before

    zx::channel client_end, server_end;
    zx_status_t status = zx::channel::create(0, &client_end, &server_end);
    if (status != ZX_OK)
      return status;
    foo.HandleBar(std::move(server_end));
    fidl::WireClient<Bar> bar(std::move(client_end), &dispatcher);

### After

    auto bar_ends = fidl::CreateEndpoints<Bar>();
    if (!bar_ends.is_ok())
      return bar_ends.status_value();
    foo.HandleBar(std::move(bar_ends->server));
    fidl::WireClient bar(std::move(bar_ends->client), &dispatcher);

    // Alternatively, |CreateEndpoints| supports returning the client-end by address,
    // which would be useful when the client-end is an instance variable, for example
    // in a test fixture.
    fidl::ClientEnd<Foo> bar_client_end;
    auto bar_server_end = fidl::CreateEndpoints(&bar_client_end);
    if (!bar_server_end.is_ok())
      return bar_server_end.status_value();
    foo.HandleBar(std::move(*bar_server_end));

Note that the protocol template parameter to `fidl::WireClient` may be omitted
when typed channels are used, leading to more succinct code.

#### Sync clients

You may use `fidl::WireSyncClient` to convert a `fidl::ClientEnd` into the
corresponding synchronous client for the protocol. This has the advantage of
avoiding having to spell out the protocol type twice (one in `ClientEnd` and
then in the synchronous client class).

    fidl::WireSyncClient bar{std::move(bar_ends->client)};

### Scenario 3: connecting to a protocol

[`fdio_service_connect`](https://cs.opensource.google/search?q=fdio_service_connect&ss=fuchsia/fuchsia&start=11) is commonly used to connect to
FIDL services in a component's namespace. Because its signature is C, it becomes
quite verbose to use, especially in the presence of typed channels. We have
created ergonomic wrappers: [`component::Connect<Protocol>`](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/component/incoming/cpp/protocol.h;l=41),
[`component::ConnectAt<Protocol>`](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/component/incoming/cpp/protocol.h;l=69), and
[`component::OpenServiceRoot`](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/component/incoming/cpp/protocol.h;l=28). They are located in the
[sdk/lib/sys/component/cpp](https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/sys/component/cpp/) library.

#### Connecting to an individual protocol

### Before

    zx::channel client_end, server_end;
    zx_status_t status = zx::channel::create(0, &client_end, &server_end);
    if (status != ZX_OK)
      return status;
    status = fdio_service_connect("/svc/fuchsia.Foo", server_end.release());
    if (status != ZX_OK)
      return status;
    fidl::WireClient<Foo> foo(std::move(client_end), &dispatcher);

### After

    // The channel creation and service connection is done in one function.
    // By default it opens the protocol name.
    // Returns |zx::result<fidl::ClientEnd<Foo>>|.
    auto client_end = component::Connect<Foo>();
    if (!client_end.is_ok())
      return client_end.status_value();
    // Note: can omit template argument
    fidl::WireClient foo(std::move(*client_end), &dispatcher);

#### Opening service directory

### Before

    zx::channel client_end, server_end;
    zx_status_t status = zx::channel::create(0, &client_end, &server_end);
    if (status != ZX_OK)
      return status;
    status = fdio_service_connect("/svc", server_end.release());
    if (status != ZX_OK)
      return status;
    fidl::WireClient<::fuchsia_io::Directory> dir(std::move(client_end));

### After

    // The channel creation and service connection is done in one function.
    // Opens "/svc" and returns the client endpoint, as a
    // |zx::result<fidl::ClientEnd<::fuchsia_io::Directory>>|.
    auto client_end = component::OpenServiceRoot();
    if (!client_end.is_ok())
      return client_end.status_value();
    // Note: can omit template argument
    fidl::WireClient dir(std::move(*client_end), &dispatcher);

### Note: propagating protocol types

Whenever feasible, prefer to propagate the protocol types across related
functions and variables. **Any time you find yourself creating a `ClientEnd` /
`ServerEnd` / `UnownedClientEnd` from a channel, consider if the source channel
could also be changed to a typed channel** . They serve as self-checking
documentation and could reveal incorrect assumptions about the kind of protocols
flowing through a channel. Different from LLCPP generated structures, using
typed channels on the public API does not unfavorably predispose the interface
towards a particular ownership model or set of types, because typed channels are
simply lightweight wrappers around Zircon channels. Here we show an example
migrating a `zx::unowned_channel`:

### Before

    // |client| should speak the |fuchsia.foobar/Baz| protocol.
    zx_status_t DoThing(zx::unowned_channel client, int64_t args) {
      return fidl::WireCall<fuchsia_foobar::Baz>(std::move(client))->Method(args).status();
    }

### After

    // The intended protocol is encoded in the type system. No need for comment.
    zx_status_t DoThing(fidl::UnownedClientEnd<fuchsia_foobar::Baz> client, int64_t args) {
      return fidl::WireCall(client)->Method(args).status();
    }

### Note: resolving type mismatch due to protocol composition

There is [no "is-a" (inheritance, subsumption) relationship](https://fuchsia.dev/docs/contribute/governance/rfcs/0023_compositional_model_protocols#is_a_relationship_considered_harmful) between
FIDL protocols when one composes another. This implies that when protocol `More`
composes protocol `Less`, one may want to call a function `void
foo(fidl::ClientEnd<Less>)` with a `fidl::ClientEnd<More>`, but we would not
provide implicit conversions between those types.

Upon determining that the usage is safe, one could manually convert one
client-end into another via
`fidl::ClientEnd<Less>(more_client_end.TakeChannel())`. Prefer commenting on the
conversion as to why it would be safe (e.g. `More` will not add new events on
top of `Less`).

## Last step: making the CL

Before uploading the changes, make sure to double-check these three places:

- The `"//build/cpp:fidl-llcpp-deprecated-raw-channels"` config was removed from your target-specific `BUILD.gn` file.
- In `//build/cpp/BUILD.gn`, delete the lines in the [visibility
  section](https://cs.opensource.google/fuchsia/fuchsia/+/main:build/cpp/BUILD.gn?q=%22could+be+migrated+to+use+typed+channels%22&ss=fuchsia/fuchsia:build/cpp/) corresponding to your GN target, such that it won't regress back into raw channels. It'll also easily visualize the migration progress.
- If you're sure that the target being migrated is the last user of the `RawChannelInterface` of a particular FIDL protocol, you may delete that [protocol from the `fidlgen_cpp` compiler](https://cs.opensource.google/fuchsia/fuchsia/+/main:tools/fidl/lib/fidlgen_cpp/typed_channel_migration.go?q=%22rawChannelInterfaceAllowed+%3D+map%5Bstring%5Dbool%22&ss=fuchsia/fuchsia). Don't worry, the code won't compile if you made a premature removal.

Then you can upload the CL and tag it with `Bug: 69585` 🎉

You may add one of ianloic@, yifeit@ if need specific review from the FIDL team.

## Example CLs

- [Example 1: network-device](https://fxrev.dev/I195ebf4d38c4e949de2b913b8c96788275889b5a)
- [Example 2: ddk-lifecycle](https://fxrev.dev/I36f77e139eae177890d8037ec4f0e9396496d70b)
- [Example 3: ldsvc](https://fxrev.dev/I89ec43ffb61eb3b5a5b21f2da4835862895ee842)

## Known pain-points identified during migration:

- When converting `fdio_open3(path, flags, server.release())`, there is no type-safe alternative of `fdio_open3`.
- Converting between HLCPP and LLCPP endpoint types is tricky. We would like `fidl::ClientEnd<::my_thing::Protocol>` and `fidl::InterfaceHandle<my::thing::Protocol>` to easily convert into one another, and same for servers.
- HLCPP and legacy component framework APIs (`sys::ServiceDirectory`, `sys::OutgoingDirectory`) use HLCPP `InterfaceHandle` and `InterfaceRequest` types, hence need additional conversion into LLCPP typed channels.

## Sponsors

Reach out for questions or for status updates:

- yifeit@google.com
- ianloic@google.com

[](https://issues.fuchsia.dev/issues/new?component=1439230&template=1881640&description=Issue+on:+https://fuchsia.dev/docs/contribute/open_projects/graduated/typed_channels.md "Report a bug or suggest improvements on this page") [](https://cs.opensource.google/fuchsia/fuchsia/+/main:docs/contribute/open_projects/graduated/typed_channels.md "View the source code of this page") [](https://ci.android.com/edit?repo=fuchsia/fuchsia/main&file=docs/contribute/open_projects/graduated/typed_channels.md&cloneRepo=fuchsia/fuchsia:refs/heads/main&fullPath=fuchsia/docs/contribute/open_projects/graduated/typed_channels.md "Edit the source code of this page")