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.
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.
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).
FDomain defines a small set of core types that are used throughout the protocol.
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).
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.
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.
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.
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.
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.
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 ObjType
s 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.
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 Hid
s and closes the associated handles. The Hid
s 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.
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.
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 NewHid
s and associates them to a newly-created pair of event pair handles.
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 NewHid
s. 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.