blob: 5763994b5d9520a91722d90fad2934b2819694f4 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'package:meta/meta.dart';
import 'package:zircon/zircon.dart';
import 'error.dart';
import 'message.dart';
// ignore_for_file: public_member_api_docs
typedef _VoidCallback = void Function();
/// A channel over which messages from interface T can be sent.
///
/// An interface handle holds a [channel] whose peer expects to receive messages
/// from the FIDL interface T. The channel held by an interface handle is not
/// currently bound, which means messages cannot yet be exchanged with the
/// channel's peer.
///
/// To send messages over the channel, bind the interface handle to a `TProxy`
/// object using use [ProxyController<T>.bind] method on the proxy's
/// [Proxy<T>.ctrl] property.
///
/// Example:
///
/// ```dart
/// InterfaceHandle<T> fooHandle = [...]
/// FooProxy foo = new FooProxy();
/// foo.ctrl.bind(fooHandle);
/// foo.bar();
/// ```
///
/// To obtain an interface handle to send over a channel, used the
/// [Binding<T>.wrap] method on an object of type `TBinding`.
///
/// Example:
///
/// ```dart
/// class FooImpl extends Foo {
/// final FooBinding _binding = new FooBinding();
///
/// InterfaceHandle<T> getInterfaceHandle() => _binding.wrap(this);
///
/// @override
/// void bar() {
/// print('Received bar message.');
/// }
/// }
/// ```
class InterfaceHandle<T> {
/// Creates an interface handle that wraps the given channel.
InterfaceHandle(this._channel);
/// The underlying channel messages will be sent over when the interface
/// handle is bound to a [Proxy].
///
/// To take the channel from this object, use [passChannel].
Channel get channel => _channel;
Channel _channel;
/// Returns [channel] and sets [channel] to `null`.
///
/// Useful for taking ownership of the underlying channel.
Channel passChannel() {
final Channel result = _channel;
_channel = null;
return result;
}
/// Closes the underlying channel.
void close() {
_channel?.close();
_channel = null;
}
}
/// A channel over which messages from interface T can be received.
///
/// An interface request holds a [channel] whose peer expects to be able to send
/// messages from the FIDL interface T. A channel held by an interface request
/// is not currently bound, which means messages cannot yet be exchanged with
/// the channel's peer.
///
/// To receive messages sent over the channel, bind the interface handle using
/// [Binding<T>.bind] on a `TBinding` object, which you typically hold as a
/// private member variable in a class that implements [T].
///
/// Example:
///
/// ```dart
/// class FooImpl extends Foo {
/// final FooBinding _binding = new FooBinding();
///
/// void bind(InterfaceRequest<T> request) {
/// _binding.bind(request);
/// }
///
/// @override
/// void bar() {
/// print('Received bar message.');
/// }
/// }
/// ```
///
/// To obtain an interface request to send over a channel, used the
/// [ProxyController<T>.request] method on the [Proxy<T>.ctrl] property of an
/// object of type `TProxy`.
///
/// Example:
///
/// ```dart
/// FooProxy foo = new FooProxy();
/// InterfaceRequest<T> request = foo.ctrl.request();
/// ```
class InterfaceRequest<T> {
/// Creates an interface request that wraps the given channel.
InterfaceRequest(this._channel);
/// The underlying channel messages will be received over when the interface
/// handle is bound to [Binding].
///
/// To take the channel from this object, use [passChannel].
Channel get channel => _channel;
Channel _channel;
/// Returns [channel] and sets [channel] to `null`.
///
/// Useful for taking ownership of the underlying channel.
Channel passChannel() {
final Channel result = _channel;
_channel = null;
return result;
}
/// Closes the underlying channel.
void close() {
_channel?.close();
_channel = null;
}
}
class InterfacePair<T> {
InterfacePair() {
ChannelPair pair = ChannelPair();
request = InterfaceRequest<T>(pair.first);
handle = InterfaceHandle<T>(pair.second);
}
InterfaceRequest<T> request;
InterfaceHandle<T> handle;
InterfaceRequest<T> passRequest() {
final InterfaceRequest<T> result = request;
request = null;
return result;
}
InterfaceHandle<T> passHandle() {
final InterfaceHandle<T> result = handle;
handle = null;
return result;
}
}
/// Listens for messages and dispatches them to an implementation of T.
abstract class Binding<T> {
/// Creates a binding object in an unbound state.
///
/// Rather than creating a [Binding<T>] object directly, you typically create
/// a `TBinding` object, which are subclasses of [Binding<T>] created by the
/// FIDL compiler for a specific interface.
Binding() {
_reader
..onReadable = _handleReadable
..onError = _handleError;
}
/// Event for binding.
_VoidCallback onBind;
/// Event for unbinding.
_VoidCallback onUnbind;
/// Event for when the binding is closed.
_VoidCallback onClose;
/// Returns an interface handle whose peer is bound to the given object.
///
/// Creates a channel pair, binds one of the channels to this object, and
/// returns the other channel. Messages sent over the returned channel will be
/// decoded and dispatched to `impl`.
///
/// The `impl` parameter must not be null.
InterfaceHandle<T> wrap(T impl) {
assert(!isBound);
ChannelPair pair = ChannelPair();
if (pair.status != ZX.OK) {
return null;
}
_impl = impl;
_reader.bind(pair.first);
if (onBind != null) {
onBind();
}
return InterfaceHandle<T>(pair.second);
}
/// Binds the given implementation to the given interface request.
///
/// Listens for messages on channel underlying the given interface request,
/// decodes them, and dispatches the decoded messages to `impl`.
///
/// This object must not already be bound.
///
/// The `impl` and `interfaceRequest` parameters must not be `null`. The
/// `channel` property of the given `interfaceRequest` must not be `null`.
void bind(T impl, InterfaceRequest<T> interfaceRequest) {
assert(!isBound);
assert(impl != null);
assert(interfaceRequest != null);
Channel channel = interfaceRequest.passChannel();
assert(channel != null);
_impl = impl;
_reader.bind(channel);
if (onBind != null) {
onBind();
}
}
/// Unbinds [impl] and returns the unbound channel as an interface request.
///
/// Stops listening for messages on the bound channel, wraps the channel in an
/// interface request of the appropriate type, and returns that interface
/// request.
///
/// The object must have previously been bound (e.g., using [bind]).
InterfaceRequest<T> unbind() {
assert(isBound);
final InterfaceRequest<T> result =
InterfaceRequest<T>(_reader.unbind());
_impl = null;
if (onUnbind != null) {
onUnbind();
}
return result;
}
/// Close the bound channel.
///
/// This function does nothing if the object is not bound.
void close() {
if (isBound) {
_reader.close();
_impl = null;
if (onClose != null) {
onClose();
}
}
}
/// Called when the channel underneath closes.
_VoidCallback onConnectionError;
/// The implementation of [T] bound using this object.
///
/// If this object is not bound, this property is null.
T get impl => _impl;
T _impl;
/// Whether this object is bound to a channel.
///
/// See [bind] and [unbind] for more information.
bool get isBound => _impl != null;
/// Decodes the given message and dispatches the decoded message to [impl].
///
/// This function is called by this object whenever a message arrives over a
/// bound channel.
@protected
void handleMessage(Message message, MessageSink respond);
void _handleReadable() {
final ReadResult result = _reader.channel.queryAndRead();
if ((result.bytes == null) || (result.bytes.lengthInBytes == 0))
throw FidlError('Unexpected empty message or error: $result '
'from channel ${_reader.channel}');
final Message message = Message.fromReadResult(result);
handleMessage(message, sendMessage);
}
/// Always called when the channel underneath closes. If [onConnectionError]
/// is set, it is called.
void _handleError(ChannelReaderError error) {
if (onConnectionError != null) {
onConnectionError();
}
}
/// Sends the given message over the bound channel.
///
/// If the channel is not bound, the handles inside the message are closed and
/// the message itself is discarded.
void sendMessage(Message response) {
if (!_reader.isBound) {
response.closeHandles();
return;
}
_reader.channel.write(response.data, response.handles);
}
final ChannelReader _reader = ChannelReader();
}
/// The object that [ProxyController<T>.error] completes with when there is
/// an error.
class ProxyError {
/// Creates a proxy error with the given message.
///
/// The `message` argument must not be null.
ProxyError(this.message);
/// What went wrong.
final String message;
@override
String toString() => 'ProxyError: $message';
}
/// The control plane for an interface proxy.
///
/// A proxy controller lets you operate on the local [Proxy<T>] object itself
/// rather than send messages to the remote implementation of the proxy. For
/// example, you can [unbind] or [close] the proxy.
///
/// You typically obtain a [ProxyController<T>] object as the [Proxy<T>.ctrl]
/// property of a `TProxy` object.
///
/// Example:
///
/// ```dart
/// FooProxy foo = new FooProxy();
/// fooProvider.getFoo(foo.ctrl.request());
/// ```
class ProxyController<T> {
/// Creates proxy controller.
///
/// Proxy controllers are not typically created directly. Instead, you
/// typically obtain a [ProxyController<T>] object as the [Proxy<T>.ctrl]
/// property of a `TProxy` object.
ProxyController({this.$serviceName, this.$interfaceName}) {
_reader
..onReadable = _handleReadable
..onError = _handleError;
}
/// Event for binding.
_VoidCallback onBind;
/// Event for unbinding.
_VoidCallback onUnbind;
/// Event for when the binding is closed.
_VoidCallback onClose;
/// The service name associated with [T], if any.
///
/// Will be set if the `[Discoverable]` attribute is on the FIDL interface
/// definition. If set it will be the fully-qualified name of the interface.
///
/// This string is typically used with the `ServiceProvider` interface to
/// request an implementation of [T].
final String $serviceName;
/// The name of the interface of [T].
///
/// Unlike [$serviceName] should always be set and won't be fully qualified.
/// This should only be used for debugging and logging purposes.
final String $interfaceName;
/// Creates an interface request whose peer is bound to this interface proxy.
///
/// Creates a channel pair, binds one of the channels to this object, and
/// returns the other channel. Calls to the proxy will be encoded as messages
/// and sent to the returned channel.
///
/// The proxy must not already have been bound.
InterfaceRequest<T> request() {
assert(!isBound);
ChannelPair pair = ChannelPair();
assert(pair.status == ZX.OK);
_reader.bind(pair.first);
_boundCompleter.complete();
if (onBind != null) {
onBind();
}
return InterfaceRequest<T>(pair.second);
}
/// Binds the proxy to the given interface handle.
///
/// Calls to the proxy will be encoded as messages and sent over the channel
/// underlying the given interface handle.
///
/// This object must not already be bound.
///
/// The `interfaceHandle` parameter must not be null. The `channel` property
/// of the given `interfaceHandle` must not be null.
void bind(InterfaceHandle<T> interfaceHandle) {
assert(!isBound);
assert(interfaceHandle != null);
assert(interfaceHandle.channel != null);
_reader.bind(interfaceHandle.passChannel());
_boundCompleter.complete();
if (onBind != null) {
onBind();
}
}
/// Unbinds the proxy and returns the unbound channel as an interface handle.
///
/// Calls on the proxy will no longer be encoded as messages on the bound
/// channel.
///
/// The proxy must have previously been bound (e.g., using [bind]).
InterfaceHandle<T> unbind() {
assert(isBound);
if (!_reader.isBound) {
return null;
}
if (onUnbind != null) {
onUnbind();
}
// TODO(rosswang): Do we need to _reset() here?
return InterfaceHandle<T>(_reader.unbind());
}
/// Whether this object is bound to a channel.
///
/// See [bind] and [unbind] for more information.
bool get isBound => _reader.isBound;
/// Close the channel bound to the proxy.
///
/// The proxy must have previously been bound (e.g., using [bind]).
void close() {
if (isBound) {
if (_pendingResponsesCount > 0) {
proxyError('The proxy is closed.');
}
_reset();
_reader.close();
if (onClose != null) {
onClose();
}
}
}
/// Called when the channel underneath closes.
_VoidCallback onConnectionError;
/// Called whenever this object receives a response on a bound channel.
///
/// Used by subclasses of [Proxy<T>] to receive responses to messages.
MessageSink onResponse;
final ChannelReader _reader = ChannelReader();
final HashMap<int, Function> _callbackMap = HashMap<int, Function>();
/// A future that completes when an error is generated by the proxy.
Future<ProxyError> get error => _errorCompleter.future;
Completer<ProxyError> _errorCompleter = Completer<ProxyError>();
/// A future that completes when the proxy is bound.
Future<Null> get bound => _boundCompleter.future;
Completer<Null> _boundCompleter = Completer<Null>();
int _nextTxid = 1;
int _pendingResponsesCount = 0;
void _reset() {
_callbackMap.clear();
_errorCompleter = Completer<ProxyError>();
if (!_boundCompleter.isCompleted) {
_boundCompleter.completeError('Proxy<${$interfaceName}> closed.');
}
_boundCompleter = Completer<Null>();
_nextTxid = 1;
_pendingResponsesCount = 0;
}
void _handleReadable() {
final ReadResult result = _reader.channel.queryAndRead();
if ((result.bytes == null) || (result.bytes.lengthInBytes == 0)) {
proxyError('Read from channel ${_reader.channel} failed');
return;
}
try {
_pendingResponsesCount--;
if (onResponse != null) {
onResponse(Message.fromReadResult(result));
}
} on FidlError catch (e) {
if (result.handles != null) {
for (Handle handle in result.handles) {
handle.close();
}
}
proxyError(e.toString());
close();
}
}
/// Always called when the channel underneath closes. If [onConnectionError]
/// is set, it is called.
void _handleError(ChannelReaderError error) {
proxyError(error.toString());
_reset();
if (onConnectionError != null) {
onConnectionError();
}
}
/// Sends the given messages over the bound channel.
///
/// Used by subclasses of [Proxy<T>] to send encoded messages.
void sendMessage(Message message) {
if (!_reader.isBound) {
proxyError('The proxy is closed.');
return;
}
final int status = _reader.channel.write(message.data, message.handles);
if (status != ZX.OK)
proxyError(
'Failed to write to channel: ${_reader.channel} (status: $status)');
}
/// Sends the given messages over the bound channel and registers a callback
/// to handle the response.
///
/// Used by subclasses of [Proxy<T>] to send encoded messages.
void sendMessageWithResponse(Message message, Function callback) {
if (!_reader.isBound) {
proxyError('The sender is closed.');
return;
}
const int _kUserspaceTxidMask = 0x7FFFFFFF;
int txid = _nextTxid++ & _kUserspaceTxidMask;
while (txid == 0 || _callbackMap.containsKey(txid))
txid = _nextTxid++ & _kUserspaceTxidMask;
message.txid = txid;
final int status = _reader.channel.write(message.data, message.handles);
if (status != ZX.OK) {
proxyError(
'Failed to write to channel: ${_reader.channel} (status: $status)');
return;
}
_callbackMap[message.txid] = callback;
_pendingResponsesCount++;
}
/// Returns the callback associated with the given response message.
///
/// Used by subclasses of [Proxy<T>] to retrieve registered callbacks when
/// handling response messages.
Function getCallback(int txid) {
final Function result = _callbackMap.remove(txid);
if (result == null) {
proxyError('Message had unknown request id: $txid');
return null;
}
return result;
}
/// Complete the [error] future with the given message.
void proxyError(String message) {
final fullMessage =
'Error in proxy with interface name [${$interfaceName}] and '
'service name [${$serviceName}]: $message';
print(fullMessage);
if (!_errorCompleter.isCompleted) {
error.whenComplete(() {
_errorCompleter = Completer<ProxyError>();
});
_errorCompleter.complete(ProxyError(fullMessage));
}
}
}
/// Sends messages to a remote implementation of [T]
class Proxy<T> {
/// Creates a proxy object with the given [ctrl].
///
/// Rather than creating [Proxy<T>] object directly, you typically create
/// `TProxy` objects, which are subclasses of [Proxy<T>] created by the FIDL
/// compiler for a specific interface.
Proxy(this.ctrl);
/// The control plane for this proxy.
///
/// Methods that manipulate the local proxy (as opposed to sending messages
/// to the remote implementation of [T]) are exposed on this [ctrl] object to
/// avoid naming conflicts with the methods of [T].
final ProxyController<T> ctrl;
// In general it's probably better to avoid adding fields and methods to this
// class. Names added to this class have to be mangled by bindings generation
// to avoid name conflicts.
}