FDomain Protocol

An FDomain is a collection of handles that can be manipulated remotely. Consequently the FDomain protocol is a protocol that allows a client to request certain actions be performed on certain handles within an FDomain.

Protocol Design

The FDomain protocol is a regular FIDL protocol, except that it never includes handles, making it possible to transmit it over a non-channel medium, or off the device entirely. It otherwise relies on normal FIDL mechanisms, including standard FIDL transaction headers and the use of an open protocol and flexible methods for API evolution and compatibility.

Two-way methods only

The FDomain protocol only uses two-way methods which have the option to return an error. This allows unknown method errors to always be returned to the client in the event of an API mismatch. (FIDL allows one way methods to fail silently, and can‘t report a proper unknown error code if the original method doesn’t normally report errors).

Protocol Primitives

FDomain defines a small set of core types that are used throughout the protocol.

Handle IDs

Handles within FDomain are referred to by an ID. IDs are 32-bit integers, not unlike the handles themselves, however handle IDs do not correlate to the actual integer value of handles.

When we pass around handle IDs in FDomain we use the newtype wrapper Hid to make the semantics clear.

FDomain supports operations that create handles, and we would like to support pipelining for those operations. For example, we'd like the client to be able to create a socket and then immediately try to send data through that socket without waiting for a response to the creation request. For this to work, the client has to know the ID of the new socket before the creation request returns. We solve this by allowing the client to specify the IDs of the new handles created by a request. When the client is specifying handle IDs to be populated by a request we use the NewHid newtype wrapper.

There are operations which may create new handles in the FDomain where we cannot give the client the opportunity to specify what the new IDs should be. Most notably, reading from a channel may produce a message with an unknown number of handles. In these cases the handle ID is assigned by the FDomain itself. To prevent collisions between handle IDs assigned by the FDomain and handle IDs provided by the client, the most significant bit of the handle ID sent in a NewHid must always be zero (and the most significant bit of a handle ID created by the FDomain will never be zero).

Allocation

Handle IDs, whether allocated by the client or FDomain, should be chosen randomly within the space of allowable values.

Handle IDs should not be reused. If a NewHid is sent as part of a request which fails, the ID in that NewHid should not be reused. The FDomain may return the bad_hid error variant to the client if it detects a handle ID being reused by the client.

Handle Info and Disposition

FDomain provides its own versions of zx_handle_info_t and zx_handle_disposition_t which contain handle IDs instead of handles. These are named, as one might expect, HandleInfo and HandleDisposition. These structs use the Rights and ObjectType types for metadata, whereas for the handle operation in HandleDisposition is stored using an FDomain specific type HandleOp. These are used where we'd expect their Zircon counterparts to be used when operating on local handles directly. Consult the Zircon documentation for their semantics.

Signals

FDomain provides a Signals type which is a bits and is analogous to zx_signal_t. It is currently bit-compatible with zx_signals_t as well but is defined separately so it can be versioned independently.

To prevent incompatibility, the implementation should not rely on the bit compatibility here and should always manually translate to and from zx_status_t, validating along the way. This will create code that will be disrupted by a syscall API change and signal that additional compatibility work is needed.

Rights

FDomain provides a Rights type which is a bits and is analogous to zx_rights_t. It is currently bit-compatible with zx_rights_t as well but is defined separately so it can be versioned independently.

To prevent incompatibility, the implementation should not rely on the bit compatibility here and should always manually translate to and from zx_rights_t, validating along the way. This will create code that will be disrupted by a syscall API change and signal that additional compatibility work is needed.

ObjType

FDomain provides an ObjType type which is an enum and is analogous to zx_obj_type_t. It is currently bit-compatible with zx_obj_type_t as well but is defined separately so it can be versioned independently.

To prevent incompatibility, the implementation should not rely on the bit compatibility here and should always manually translate to and from zx_obj_type_t, validating along the way. This will create code that will be disrupted by a syscall API change and signal that additional compatibility work is needed.

Errors

The FDomain protocol has a global Error union which provides an error type for returning from methods within the protocol. Most of the variants we will explain as they come up, but here are a few globally relevant ones:

  • target_error - Contains an int32 presumed to be a zx_status_t, and indicates that an operation on a handle failed for reasons relating to the handle itself rather than the FDomain or the protocol state. In short this indicates that the actual system call implied by the operation requested returned an error.
  • bad_hid - indicates that the client used a Handle ID which did not refer to a handle in the FDomain. Contains the unwrapped uint32 handle ID that was given.
  • wrong_handle_type - indicates we expected a handle of some type (e.g. a socket) and the client gave an ID referring to a handle of a different type (e.g. a channel). Contains ObjTypes for both the expected and actual type.
  • bad_new_hid - Indicates we gave a NewHid that either referred to an existing handle, or had the most significant bit set.

Note that in the case of target_error, a major change to the schema of values used in zx_status_t could cause a breakage in error reporting. Such changes are incredibly rare, and are usually additive, which shouldn‘t disrupt compatibility at all. We’ve decided the risk is low enough that we will risk a degraded error reporting experience for legacy tool users to keep the API simple here.

Core Methods

The FDomain protocol has a series of core methods that are generally useful for operating on any type of handle or otherwise manipulating the FDomain, and then composes several sub-protocols with methods specific to dealing with certain types of handle. These are the core methods:

Namespace

The Namespace method takes a NewHid and creates a channel at that ID which points to a fuchsia.io.Directory. What that directory contains is up to the service exposing the FDomain. This the mechanism by which we can “bootstrap” an FDomain; it allows us to get an initial set of handles which connect to other services we might want to communicate with.

Close

Close takes a list of Hids and closes the associated handles. The Hids are no longer valid after this operation.

Duplicate

Duplicate takes a Hid, a NewHid, and a Rights and duplicates the handle at Hid, placing the new handle at NewHid. The Rights indicates the rights for the new handle. The specific semantics of the duplication are identical to zx_handle_duplicate.

Replace

Replace takes a Hid, a NewHid, and a Rights and destroys the handle associated with Hid, placing a new handle to the same object at NewHid, which now has the rights given. The semantics are identical to the syscall zx_handle_replace.

Signal

Signal takes a Hid and two Signals values, a “set” list and a “clear” list, and asserts and de-asserts those signals respectively on the handle associated with the Hid. Semantics are identical to zx_object_signal.

SignalPeer

SignalPeer takes a Hid and two Signals values, a “set” list and a “clear” list, and asserts and de-asserts those signals respectively on the handle peered with the handle associated with the Hid. Semantics are identical to zx_object_signal_peer.

WaitForSignals

WaitForSignals takes a Hid and a Signals value and hangs returning until one of the signals given is asserted on the handle associated with the Hid. It returns a Signals value indicating which signal or signals were asserted.

AcknowledgeWriteError

FDomain wants to enable pipelining for transactions. That means if you submit a request to write from a handle, you can submit a second request to write more data before the first request returns.

This creates a potential to produce inconsistent writes. Consider a client that submits three write requests, A, B, and C. Suppose request B experiences a transient failure, but A, and C succeed. This means the receiver will see message A followed by message C. Message B could then be re-submitted, but the messages will arrive in the order A, C, B, which may be undesirable if message ordering is important.

To fix this, Playground has the notion of “write-locked” handles. A particular request may specify in its documentation that it write-locks the handle on error. This makes further write operations on that handle fail. So in our example, if A succeeded, and B failed transiently, C would also fail, returning the error_pending variant of our Error union.

AcknowledgeWriteError takes a Hid and removes the write-locked state from the associated handle, returning it to normal operation. If the handle is not write-locked, AcknowledgeWriteError will return the no_error_pending error variant.

Events

The FDomain protocol composes an Event sub-protocol for dealing with Event handles. This protocol exposes one method, CreateEvent, which takes a NewHid and associates it to a newly-created event handle.

Event Pairs

The FDomain protocol composes an EventPair sub-protocol for dealing with Event Pair handles. This protocol exposes one method, CreateEvent, which takes an array of two NewHids and associates them to a newly-created pair of event pair handles.

Sockets

The FDomain protocol composes a Socket sub-protocol for dealing with Socket handles.

The SocketProtocol has the following methods:

CreateSocket

CreateSocket takes an options parameter and an array of two NewHids. It associates the two new ids with a pair of sockets.

The options parameter has the type SocketType which is defined alongside the Socket protocol and has two values: STREAM and DATAGRAM. This can be used to select stream or datagram semantics for the new socket.

SetSocketDisposition

SetSocketDisposition takes an Hid and two SocketDisposition arguments.

SocketDisposition is an enum defined alongside the Socket protocol and has three values: NO_CHANGE, WRITE_ENABLED, and WRITE_DISABLED.

SetSocketDisposition changes the disposition of the socket associated with the Hid, and the disposition of that socket's peer.