blob: b701f5e4cf390f5c92b69b8a23abdb1ef3b6bd9a [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:async/async.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:stream_channel/stream_channel.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'stream_manager.dart';
/// Adds support for binary events send from the VM service, which are not part
/// of the official JSON RPC 2.0 specification.
///
/// A binary event from the VM service has the form:
/// ```
/// type BinaryEvent {
/// dataOffset : uint32,
/// metadata : uint8[dataOffset-4],
/// data : uint8[],
/// }
/// ```
/// where `metadata` is the JSON body of the event.
///
/// [BinaryCompatiblePeer] assumes that only stream events can contain a
/// binary payload (e.g., clients cannot send a `BinaryEvent` to the VM service).
class BinaryCompatiblePeer extends json_rpc.Peer {
BinaryCompatiblePeer(WebSocketChannel ws, StreamManager streamManager)
: super(
ws.transform<String>(
StreamChannelTransformer(
StreamTransformer<dynamic, String>.fromHandlers(
handleData: (data, EventSink<String> sink) =>
_transformStream(streamManager, data, sink)),
StreamSinkTransformer<String, dynamic>.fromHandlers(
handleData: (String data, EventSink<dynamic> sink) {
sink.add(data);
},
),
),
),
// Allow for requests without the jsonrpc parameter.
strictProtocolChecks: false,
);
static void _transformStream(
StreamManager streamManager, dynamic data, EventSink<String> sink) {
if (data is String) {
// Non-binary messages come in as Strings. Simply forward to the sink.
sink.add(data);
} else if (data is Uint8List) {
// Only binary events will result in `data` being of type Uint8List. We
// need to manually forward them here.
final bytesView =
ByteData.view(data.buffer, data.offsetInBytes, data.lengthInBytes);
const metadataOffset = 4;
final dataOffset = bytesView.getUint32(0, Endian.little);
final metadataLength = dataOffset - metadataOffset;
final metadata = Utf8Decoder().convert(Uint8List.view(bytesView.buffer,
bytesView.offsetInBytes + metadataOffset, metadataLength));
final decodedMetadata = json.decode(metadata);
streamManager.streamNotify(decodedMetadata['params']['streamId'], data);
}
}
}