| // Copyright 2018 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 'dart:typed_data'; |
| |
| import 'package:zircon/zircon.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import 'error.dart'; |
| import 'interface.dart'; |
| import 'message.dart'; |
| |
| /// The different states that an [AsyncBinding] or [AsyncProxy] can be in. |
| enum InterfaceState { |
| /// The binding or proxy has not yet been bound. |
| unbound, |
| |
| /// The binding or proxy has been bound to a channel. |
| bound, |
| |
| /// The binding or proxy has been closed. |
| closed |
| } |
| |
| /// An exception that's thrown if an [AsyncBinding] or [AsyncProxy] isn't in the required |
| /// state for the requested operation. |
| class FidlStateException extends FidlError { |
| /// Create a new [FidlStateException]. |
| FidlStateException(String message) : super(message); |
| } |
| |
| /// A class that holds and mutates the state of an [AsyncBinding] or [AsyncProxy], |
| /// represented as an [InterfaceState] value. |
| abstract class _Stateful { |
| InterfaceState _currentState = InterfaceState.unbound; |
| |
| /// The controller for the stream of state changes. |
| final StreamController<InterfaceState> _streamController = |
| StreamController.broadcast(); |
| |
| /// Gets the current state. |
| InterfaceState get state => _currentState; |
| |
| /// Change the state. |
| @protected |
| set state(InterfaceState newState) { |
| if (_currentState == newState) { |
| // No change. |
| return; |
| } |
| if (newState.index < _currentState.index) { |
| throw FidlStateException( |
| "Can't change InterfaceState from $_currentState to $newState."); |
| } |
| _currentState = newState; |
| _streamController.add(newState); |
| } |
| |
| /// A stream of state changes. |
| Stream<InterfaceState> get stateChanges => _streamController.stream; |
| |
| /// Is this interface unbound? |
| bool get isUnbound => _currentState == InterfaceState.unbound; |
| |
| /// Is this interface bound? |
| bool get isBound => _currentState == InterfaceState.bound; |
| |
| /// A future that completes when the interface becomes bound. |
| Future<void> get whenBound { |
| if (_currentState == InterfaceState.unbound) { |
| return stateChanges.firstWhere((s) => s == InterfaceState.bound); |
| } |
| if (_currentState == InterfaceState.bound) { |
| return Future.value(); |
| } |
| return Future.error( |
| FidlStateException('Interface will never become bound')); |
| } |
| |
| /// Is this interface closed? |
| bool get isClosed => _currentState == InterfaceState.closed; |
| |
| /// A future that completes when the interface is closed. |
| Future<void> get whenClosed { |
| if (_currentState == InterfaceState.closed) { |
| return Future.value(); |
| } |
| return stateChanges.firstWhere((s) => s == InterfaceState.closed); |
| } |
| } |
| |
| /// Listens for messages and dispatches them to an implementation of [T]. |
| abstract class AsyncBinding<T> extends _Stateful { |
| /// Creates a binding object in an unbound state. |
| /// |
| /// Rather than creating a [AsyncBinding<T>] object directly, you typically create |
| /// a `TBinding` object, which are subclasses of [AsyncBinding<T>] created by the |
| /// FIDL compiler for a specific interface. |
| AsyncBinding(this.$interfaceName) { |
| _reader |
| ..onReadable = _handleReadable |
| ..onError = _handleError; |
| } |
| |
| /// The name of the interface [T] as a string. |
| /// |
| /// This is used to generate meaningful error messages at runtime. |
| final String $interfaceName; |
| |
| /// 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) { |
| if (!isUnbound) { |
| throw FidlStateException("AsyncBinding<${$interfaceName}> isn't unbound"); |
| } |
| ChannelPair pair = ChannelPair(); |
| if (pair.status != ZX.OK) { |
| throw Exception( |
| "AsyncBinding<${$interfaceName}> couldn't create channel: ${getStringForStatus(pair.status)}"); |
| } |
| _impl = impl; |
| _reader.bind(pair.first!); |
| |
| state = InterfaceState.bound; |
| |
| 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`. |
| /// |
| /// Implementation note: in a generic context, the inferred type when creating |
| /// the `interfaceRequest` may be a super-class of `T`, possibly `Service`. |
| /// Since concrete classes of `AsyncBinding` are code generated, the type |
| /// parameter `T` will always be precise, even when used in a generic context. |
| /// As a result, we cannot type-bound `interfaceRequest` statically, and |
| /// should instead rely on dynamic behavior. |
| void bind(T impl, InterfaceRequest<dynamic> interfaceRequest) { |
| if (!isUnbound) { |
| throw FidlStateException("AsyncBinding<${$interfaceName}> isn't unbound"); |
| } |
| if (impl == null) { |
| throw FidlError( |
| "AsyncBinding<${$interfaceName}> can't bind to a null impl"); |
| } |
| |
| Channel? channel = interfaceRequest.passChannel(); |
| if (channel == null) { |
| throw FidlError( |
| "AsyncBinding<${$interfaceName}> can't bind to a null InterfaceRequest channel"); |
| } |
| |
| _impl = impl; |
| _reader.bind(channel); |
| |
| state = InterfaceState.bound; |
| } |
| |
| /// 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() { |
| if (!isBound) { |
| throw FidlStateException("AsyncBinding<${$interfaceName}> isn't bound"); |
| } |
| final InterfaceRequest<T> result = InterfaceRequest<T>(_reader.unbind()); |
| _impl = null; |
| |
| state = InterfaceState.closed; |
| |
| return result; |
| } |
| |
| void _writeEpitaph(int statusCode) { |
| var bytes = ByteData(24) |
| ..setUint32(0, 0) // txid (0 because not tracking this on server side) |
| ..setUint8(4, 0) // flags |
| ..setUint8(5, 0) // flags |
| ..setUint8(6, 0) // flags |
| ..setUint8(7, 0) // magic byte |
| ..setUint64(8, epitaphOrdinal) // ordinal |
| ..setInt32(16, statusCode); // body (epitaph struct) |
| _reader.channel?.write(bytes, []); |
| } |
| |
| /// Close the bound channel. |
| /// |
| /// This function does nothing if the object is not bound. |
| void close([int? statusCode]) { |
| if (isBound) { |
| if (statusCode != null) { |
| _writeEpitaph(statusCode); |
| } |
| _reader.close(); |
| _impl = null; |
| |
| state = InterfaceState.closed; |
| } |
| } |
| |
| /// The implementation of [T] bound using this object. |
| /// |
| /// If this object is not bound, this property is null. |
| T? get impl => _impl; |
| T? _impl; |
| |
| /// 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(IncomingMessage message, OutgoingMessageSink respond); |
| |
| void _handleReadable() { |
| final ReadResult result = _reader.channel!.queryAndRead(); |
| if (result.bytes.lengthInBytes == 0) { |
| throw FidlError( |
| 'AsyncBinding<${$interfaceName}> Unexpected empty message or error: $result'); |
| } |
| |
| final IncomingMessage message = IncomingMessage.fromReadResult(result); |
| if (!message.isCompatible()) { |
| close(); |
| throw FidlError( |
| 'Incompatible wire format', FidlErrorCode.fidlUnknownMagic); |
| } |
| handleMessage(message, sendMessage); |
| } |
| |
| /// Always called when the channel underneath closes. |
| void _handleError(ChannelReaderError error) { |
| /// TODO(ianloic): do something with [error]. |
| close(); |
| } |
| |
| /// 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(OutgoingMessage response) { |
| if (!isBound) { |
| response.closeHandles(); |
| return; |
| } |
| _reader.channel?.write(response.data, response.handles); |
| } |
| |
| final ChannelReader _reader = ChannelReader(); |
| } |
| |
| /// Representation of a service that all [T] implementations should extend from. |
| abstract class Service { |
| /// Getter for the [ServiceData] |
| ServiceData? get $serviceData; |
| } |
| |
| /// Exposes the ability to get a hold of the service runtime name and bindings. |
| abstract class ServiceData<T> { |
| /// Returns the generated runtime service name. |
| String getName(); |
| |
| /// Returns the generated runtime service bindings. |
| AsyncBinding getBinding(); |
| } |
| |
| /// Sends messages to a remote implementation of [T] |
| class AsyncProxy<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. |
| AsyncProxy(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 AsyncProxyController<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. |
| } |
| |
| /// A controller for Future based proxies. |
| class AsyncProxyController<T> extends _Stateful { |
| final ChannelReader _reader = ChannelReader(); |
| |
| final HashMap<int, Completer<dynamic>> _completerMap = HashMap(); |
| int _nextTxid = 1; |
| |
| /// Creates proxy controller. |
| /// |
| /// Proxy controllers are not typically created directly. Instead, you |
| /// typically obtain an [AsyncProxyController<T>] object as the [AsyncProxy<T>.ctrl] |
| /// property of a `TProxy` object. |
| AsyncProxyController({this.$serviceName, this.$interfaceName}) { |
| _reader |
| ..onReadable = _handleReadable |
| ..onError = _handleError; |
| whenClosed.then((_) { |
| for (final Completer completer in _completerMap.values) { |
| if (!completer.isCompleted) { |
| completer.completeError(FidlError( |
| 'AsyncProxyController<${$interfaceName}> connection closed')); |
| } |
| } |
| _completerMap.clear(); |
| }, onError: (_) { |
| // Ignore errors. |
| }); |
| } |
| |
| /// 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() { |
| if (!isUnbound) { |
| throw FidlStateException( |
| "AsyncProxyController<${$interfaceName}> isn't unbound"); |
| } |
| |
| ChannelPair pair = ChannelPair(); |
| if (pair.status != ZX.OK) { |
| throw FidlError( |
| "AsyncProxyController<${$interfaceName}> couldn't create channel: ${getStringForStatus(pair.status)}"); |
| } |
| _reader.bind(pair.first!); |
| state = InterfaceState.bound; |
| |
| 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) { |
| if (!isUnbound) { |
| throw FidlStateException( |
| "AsyncProxyController<${$interfaceName}> isn't unbound"); |
| } |
| |
| final channel = interfaceHandle.passChannel(); |
| if (channel == null) { |
| throw FidlError( |
| "AsyncProxyController<${$interfaceName}> can't bind to null InterfaceHandle channel"); |
| } |
| |
| _reader.bind(channel); |
| state = InterfaceState.bound; |
| } |
| |
| /// 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() { |
| if (!isBound) { |
| throw FidlStateException( |
| "AsyncProxyController<${$interfaceName}> isn't bound"); |
| } |
| if (!_reader.isBound) { |
| throw FidlError( |
| "AsyncProxyController<${$interfaceName}> reader isn't bound"); |
| } |
| |
| state = InterfaceState.closed; |
| |
| return InterfaceHandle<T>(_reader.unbind()); |
| } |
| |
| /// Close the channel bound to the proxy. |
| /// |
| /// The proxy must have previously been bound (e.g., using [bind]). |
| void close() { |
| _close(null); |
| } |
| |
| void _close(FidlError? error) { |
| if (isBound) { |
| _reader.close(); |
| state = InterfaceState.closed; |
| _completerMap.forEach((_, Completer<dynamic> completer) => |
| completer.completeError(FidlStateException(error != null |
| ? 'AsyncProxyController<${$interfaceName}> is closed with error: ${error.message}' |
| : 'AsyncProxyController<${$interfaceName}> is closed.'))); |
| } |
| } |
| |
| /// close the channel and forwards error to any open completers. |
| void proxyError(FidlError error) { |
| _close(error); |
| } |
| |
| /// Called when an epitaph is received (from channel closure). |
| EpitaphHandler? onEpitaphReceived; |
| |
| /// Called whenever this object receives a response on a bound channel. |
| /// |
| /// Used by subclasses of [Proxy<T>] to receive responses to messages. |
| IncomingMessageSink? onResponse; |
| |
| void _handleReadable() { |
| final ReadResult result = _reader.channel!.queryAndRead(); |
| if (result.bytes.lengthInBytes == 0) { |
| proxyError(FidlError( |
| 'AsyncProxyController<${$interfaceName}>: Read from channel failed')); |
| return; |
| } |
| try { |
| IncomingMessage message = IncomingMessage.fromReadResult(result); |
| final epitaphCallback = onEpitaphReceived; |
| final responseCallback = onResponse; |
| if (message.ordinal == epitaphOrdinal) { |
| int statusCode = message.data.getInt32(16); |
| if (epitaphCallback != null) { |
| epitaphCallback(statusCode); |
| } |
| } else if (responseCallback != null) { |
| responseCallback(message); |
| } |
| } on FidlError catch (e) { |
| for (Handle handle in result.handles) { |
| handle.close(); |
| } |
| proxyError(e); |
| } |
| } |
| |
| /// Always called when the channel underneath closes. |
| void _handleError(ChannelReaderError error) { |
| proxyError(FidlError(error.toString())); |
| } |
| |
| /// Sends the given messages over the bound channel. |
| /// |
| /// Used by subclasses of [Proxy<T>] to send encoded messages. |
| void sendMessage(OutgoingMessage message) { |
| if (!_reader.isBound) { |
| proxyError(FidlStateException( |
| 'AsyncProxyController<${$interfaceName}> is closed.')); |
| return; |
| } |
| final int status = _reader.channel!.write(message.data, message.handles); |
| if (status != ZX.OK) { |
| proxyError(FidlError( |
| 'AsyncProxyController<${$interfaceName}> failed to write to channel: ${_reader.channel} (status: $status)')); |
| } |
| } |
| |
| /// Sends the given messages over the bound channel and registers a Completer |
| /// to handle the response. |
| /// |
| /// Used by subclasses of [AsyncProxy<T>] to send encoded messages. |
| void sendMessageWithResponse( |
| OutgoingMessage message, Completer<dynamic> completer) { |
| if (!_reader.isBound) { |
| proxyError(FidlStateException( |
| 'AsyncProxyController<${$interfaceName}> is closed.')); |
| return; |
| } |
| |
| const int _userspaceTxidMask = 0x7FFFFFFF; |
| |
| int txid = _nextTxid++ & _userspaceTxidMask; |
| while (txid == 0 || _completerMap.containsKey(txid)) |
| txid = _nextTxid++ & _userspaceTxidMask; |
| message.txid = txid; |
| _completerMap[message.txid] = completer; |
| final int status = _reader.channel!.write(message.data, message.handles); |
| |
| if (status != ZX.OK) { |
| proxyError(FidlError( |
| 'AsyncProxyController<${$interfaceName}> failed to write to channel: ${_reader.channel} (status: $status)')); |
| return; |
| } |
| } |
| |
| /// Returns the completer associated with the given response message. |
| /// |
| /// Used by subclasses of [AsyncProxy<T>] to retrieve registered completers when |
| /// handling response messages. |
| Completer? getCompleter(int txid) { |
| final Completer? result = _completerMap.remove(txid); |
| if (result == null) { |
| proxyError(FidlError('Message had unknown request id: $txid')); |
| } |
| return result; |
| } |
| } |