blob: f580a64cd67a9eb181210f7794e080a6c3940c61 [file] [log] [blame]
// 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:convert';
import 'dart:typed_data';
import 'package:fidl/fidl.dart';
import 'package:fidl_fuchsia_mem/fidl_async.dart' as fuchsia_mem;
import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl;
import 'package:meta/meta.dart';
import 'package:zircon/zircon.dart';
import '../../internal/_component_context.dart';
import '../../module/internal/_module_context.dart';
import '../entity.dart';
import '../entity_codec.dart';
import '../entity_exceptions.dart';
/// An [Entity] implementation which supports
/// data that lives inside links.
///
/// Note: This is a temporary solution that will go away when links are removed.
class LinkEntity<T> implements Entity<T> {
/// The name of the link to pull data from
final String linkName;
/// The codec used to decode/encode data
final EntityCodec codec;
fidl.Link _link;
/// Constructor
LinkEntity({
@required this.linkName,
@required this.codec,
}) : assert(linkName != null),
assert(codec != null);
@override
String get type => codec.type;
@override
Future<T> getData() async => _getSnapshot();
@override
Future<String> getEntityReference() => null;
@override
Stream<T> watch() {
final link = _getLink();
final controller = StreamController<fuchsia_mem.Buffer>();
final watcher = _LinkWatcher(onNotify: (buffer) async {
controller.add(buffer);
});
link.watch(watcher.getInterfaceHandle());
// if the user closed the stream we close the binding to the watcher
controller.onCancel = watcher.binding.close;
// if the connection closes then we close the stream
watcher.binding.whenClosed.then((_) {
if (!controller.isClosed) {
controller.close();
}
});
// Use _getSnapshot here instead of using the buffer directly because
// most uses of links are using the entityRef value on the link instead
// of raw json.
return controller.stream.asyncMap((_) => _getSnapshot());
}
@override
Future<void> write(T value) async {
final link = _getLink();
// Convert the object to raw bytes
final bytes = codec.encode(value);
// need to base64 encode so we can encode it as json.
final b64ByteString = base64.encode(bytes);
final jsonString = json.encode(b64ByteString);
// convert the json encoded string into bytes so we can put it in a vmo
final jsonData = Uint8List.fromList(utf8.encode(jsonString));
final vmo = SizedVmo.fromUint8List(jsonData);
final buffer = fuchsia_mem.Buffer(vmo: vmo, size: jsonData.length);
await link.set(null, buffer);
}
Future<T> _getEntityData<T>(String entityReference) async {
final resolver = fidl.EntityResolverProxy();
await getComponentContext().getEntityResolver(resolver.ctrl.request());
final entity = fidl.EntityProxy();
await resolver.resolveEntity(entityReference, entity.ctrl.request());
final types = await entity.getTypes();
if (!types.contains(codec.type)) {
throw EntityTypeException(codec.type);
}
final buffer = await entity.getData(codec.type);
final dataVmo = SizedVmo(buffer.vmo.handle, buffer.size);
final result = dataVmo.read(buffer.size);
if (result.status != 0) {
throw new Exception('Failed to read VMO');
}
dataVmo.close();
return codec.decode(result.bytesAsUint8List());
}
T _getJsonData<T>(fuchsia_mem.Buffer jsonBuffer) {
final vmo = SizedVmo(jsonBuffer.vmo.handle, jsonBuffer.size);
final result = vmo.read(jsonBuffer.size);
if (result.status != 0) {
throw Exception('Failed to read VMO');
}
vmo.close();
final resultBytes = result.bytesAsUint8List();
Uint8List bytesToDecode;
try {
// Try to decode the values in the format that this entity encoded them
final utf8String = utf8.decode(resultBytes);
final jsonDecoded = json.decode(utf8String);
bytesToDecode = base64.decode(jsonDecoded);
} on Exception catch (_) {
bytesToDecode = resultBytes;
}
return codec.decode(bytesToDecode);
}
fidl.Link _getLink() {
if (_link != null) {
return _link;
}
// Store the Link instead of the LinkProxy to avoid closing.
fidl.LinkProxy linkProxy = fidl.LinkProxy();
_link = linkProxy;
getModuleContext().getLink(linkName, linkProxy.ctrl.request());
return _link;
}
Future<T> _getSnapshot() async {
final link = _getLink();
final buffer = await link.get(null);
if (buffer == null) {
final entityReference = await link.getEntity();
if (entityReference == null) {
return null;
}
return _getEntityData(entityReference);
}
return _getJsonData(buffer);
}
}
class _LinkWatcher extends fidl.LinkWatcher {
final fidl.LinkWatcherBinding binding = fidl.LinkWatcherBinding();
final Future<void> Function(fuchsia_mem.Buffer) onNotify;
_LinkWatcher({@required this.onNotify}) : assert(onNotify != null);
InterfaceHandle<fidl.LinkWatcher> getInterfaceHandle() {
if (binding.isBound) {
throw Exception(
'Attempting to call _LinkWatcher.getInterfaceHandle on already bound binding');
}
return binding.wrap(this);
}
@override
Future<void> notify(fuchsia_mem.Buffer data) => onNotify(data);
}