blob: cc2011760072b1d74fa6d9fe6c72cbf85ddd198a [file] [log] [blame] [view]
# 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](https://fuchsia.dev/reference/syscalls) 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 `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.
## 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 `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.
## 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 `NewHid`s 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 `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.