Unverified Commit 340c6803 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Make standard codecs extensible (#15414)

parent 2d5ebd2a
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
package com.yourcompany.channels; package com.yourcompany.channels;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Date;
import android.os.Bundle; import android.os.Bundle;
...@@ -20,9 +22,9 @@ public class MainActivity extends FlutterActivity { ...@@ -20,9 +22,9 @@ public class MainActivity extends FlutterActivity {
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", StandardMessageCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", ExtendedStandardMessageCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE)); setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", StandardMethodCodec.INSTANCE)); setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", new StandardMethodCodec(ExtendedStandardMessageCodec.INSTANCE)));
} }
private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) { private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) {
...@@ -135,3 +137,49 @@ public class MainActivity extends FlutterActivity { ...@@ -135,3 +137,49 @@ public class MainActivity extends FlutterActivity {
}); });
} }
} }
final class ExtendedStandardMessageCodec extends StandardMessageCodec {
public static final ExtendedStandardMessageCodec INSTANCE = new ExtendedStandardMessageCodec();
private static final byte DATE = (byte) 128;
private static final byte PAIR = (byte) 129;
@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
if (value instanceof Date) {
stream.write(DATE);
writeLong(stream, ((Date) value).getTime());
} else if (value instanceof Pair) {
stream.write(PAIR);
writeValue(stream, ((Pair) value).left);
writeValue(stream, ((Pair) value).right);
} else {
super.writeValue(stream, value);
}
}
@Override
protected Object readValueOfType(byte type, ByteBuffer buffer) {
switch (type) {
case DATE:
return new Date(buffer.getLong());
case PAIR:
return new Pair(readValue(buffer), readValue(buffer));
default: return super.readValueOfType(type, buffer);
}
}
}
final class Pair {
public final Object left;
public final Object right;
public Pair(Object left, Object right) {
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Pair[" + left + ", " + right + "]";
}
}
...@@ -5,6 +5,82 @@ ...@@ -5,6 +5,82 @@
#include "AppDelegate.h" #include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h" #include "GeneratedPluginRegistrant.h"
@interface Pair : NSObject
@property(atomic, readonly, strong, nullable) NSObject* left;
@property(atomic, readonly, strong, nullable) NSObject* right;
- (instancetype)initWithLeft:(NSObject*)first right:(NSObject*)right;
@end
@implementation Pair
- (instancetype)initWithLeft:(NSObject*)left right:(NSObject*)right {
self = [super init];
_left = left;
_right = right;
return self;
}
@end
const UInt8 DATE = 128;
const UInt8 PAIR = 129;
@interface ExtendedWriter : FlutterStandardWriter
- (void)writeValue:(id)value;
@end
@implementation ExtendedWriter
- (void)writeValue:(id)value {
if ([value isKindOfClass:[NSDate class]]) {
[self writeByte:DATE];
NSDate* date = value;
NSTimeInterval time = date.timeIntervalSince1970;
SInt64 ms = (SInt64) (time * 1000.0);
[self writeBytes:&ms length:8];
} else if ([value isKindOfClass:[Pair class]]) {
Pair* pair = value;
[self writeByte:PAIR];
[self writeValue:pair.left];
[self writeValue:pair.right];
} else {
[super writeValue:value];
}
}
@end
@interface ExtendedReader : FlutterStandardReader
- (id)readValueOfType:(UInt8)type;
@end
@implementation ExtendedReader
- (id)readValueOfType:(UInt8)type {
switch (type) {
case DATE: {
SInt64 value;
[self readBytes:&value length:8];
NSTimeInterval time = [NSNumber numberWithLong:value].doubleValue / 1000.0;
return [NSDate dateWithTimeIntervalSince1970:time];
}
case PAIR: {
return [[Pair alloc] initWithLeft:[self readValue] right:[self readValue]];
}
default: return [super readValueOfType:type];
}
}
@end
@interface ExtendedReaderWriter : FlutterStandardReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data;
- (FlutterStandardReader*)readerWithData:(NSData*)data;
@end
@implementation ExtendedReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data {
return [[ExtendedWriter alloc] initWithData:data];
}
- (FlutterStandardReader*)readerWithData:(NSData*)data {
return [[ExtendedReader alloc] initWithData:data];
}
@end
@implementation AppDelegate @implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...@@ -13,6 +89,7 @@ ...@@ -13,6 +89,7 @@
FlutterViewController *flutterController = FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController; (FlutterViewController *)self.window.rootViewController;
ExtendedReaderWriter* extendedReaderWriter = [ExtendedReaderWriter new];
[self setupMessagingHandshakeOnChannel: [self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg" [FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController binaryMessenger:flutterController
...@@ -28,7 +105,7 @@ ...@@ -28,7 +105,7 @@
[self setupMessagingHandshakeOnChannel: [self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg" [FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController binaryMessenger:flutterController
codec:[FlutterStandardMessageCodec sharedInstance]]]; codec:[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]]];
[self setupMethodCallSuccessHandshakeOnChannel: [self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method" [FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController binaryMessenger:flutterController
...@@ -36,7 +113,7 @@ ...@@ -36,7 +113,7 @@
[self setupMethodCallSuccessHandshakeOnChannel: [self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method" [FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec sharedInstance]]]; codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
return [super application:application didFinishLaunchingWithOptions:launchOptions]; return [super application:application didFinishLaunchingWithOptions:launchOptions];
} }
......
...@@ -10,6 +10,7 @@ import 'package:flutter_driver/driver_extension.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_driver/driver_extension.dart';
import 'src/basic_messaging.dart'; import 'src/basic_messaging.dart';
import 'src/method_calls.dart'; import 'src/method_calls.dart';
import 'src/pair.dart';
import 'src/test_step.dart'; import 'src/test_step.dart';
void main() { void main() {
...@@ -23,6 +24,7 @@ class TestApp extends StatefulWidget { ...@@ -23,6 +24,7 @@ class TestApp extends StatefulWidget {
} }
class _TestAppState extends State<TestApp> { class _TestAppState extends State<TestApp> {
static final dynamic anUnknownValue = new DateTime.fromMillisecondsSinceEpoch(1520777802314);
static final List<dynamic> aList = <dynamic>[ static final List<dynamic> aList = <dynamic>[
false, false,
0, 0,
...@@ -39,7 +41,7 @@ class _TestAppState extends State<TestApp> { ...@@ -39,7 +41,7 @@ class _TestAppState extends State<TestApp> {
'd': 'hello', 'd': 'hello',
'e': <dynamic>[ 'e': <dynamic>[
<String, dynamic>{'key': 42} <String, dynamic>{'key': 42}
] ],
}; };
static final Uint8List someUint8s = new Uint8List.fromList(<int>[ static final Uint8List someUint8s = new Uint8List.fromList(<int>[
0xBA, 0xBA,
...@@ -69,6 +71,10 @@ class _TestAppState extends State<TestApp> { ...@@ -69,6 +71,10 @@ class _TestAppState extends State<TestApp> {
double.maxFinite, double.maxFinite,
double.infinity, double.infinity,
]); ]);
static final dynamic aCompoundUnknownValue = <dynamic>[
anUnknownValue,
new Pair(anUnknownValue, aList),
];
static final List<TestStep> steps = <TestStep>[ static final List<TestStep> steps = <TestStep>[
() => methodCallJsonSuccessHandshake(null), () => methodCallJsonSuccessHandshake(null),
() => methodCallJsonSuccessHandshake(true), () => methodCallJsonSuccessHandshake(true),
...@@ -83,6 +89,8 @@ class _TestAppState extends State<TestApp> { ...@@ -83,6 +89,8 @@ class _TestAppState extends State<TestApp> {
() => methodCallStandardSuccessHandshake('world'), () => methodCallStandardSuccessHandshake('world'),
() => methodCallStandardSuccessHandshake(aList), () => methodCallStandardSuccessHandshake(aList),
() => methodCallStandardSuccessHandshake(aMap), () => methodCallStandardSuccessHandshake(aMap),
() => methodCallStandardSuccessHandshake(anUnknownValue),
() => methodCallStandardSuccessHandshake(aCompoundUnknownValue),
() => methodCallJsonErrorHandshake(null), () => methodCallJsonErrorHandshake(null),
() => methodCallJsonErrorHandshake('world'), () => methodCallJsonErrorHandshake('world'),
() => methodCallStandardErrorHandshake(null), () => methodCallStandardErrorHandshake(null),
...@@ -138,6 +146,8 @@ class _TestAppState extends State<TestApp> { ...@@ -138,6 +146,8 @@ class _TestAppState extends State<TestApp> {
() => basicStandardHandshake(<String, dynamic>{}), () => basicStandardHandshake(<String, dynamic>{}),
() => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}), () => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}),
() => basicStandardHandshake(aMap), () => basicStandardHandshake(aMap),
() => basicStandardHandshake(anUnknownValue),
() => basicStandardHandshake(aCompoundUnknownValue),
() => basicBinaryMessageToUnknownChannel(), () => basicBinaryMessageToUnknownChannel(),
() => basicStringMessageToUnknownChannel(), () => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(), () => basicJsonMessageToUnknownChannel(),
......
...@@ -5,8 +5,42 @@ ...@@ -5,8 +5,42 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'pair.dart';
import 'test_step.dart'; import 'test_step.dart';
class ExtendedStandardMessageCodec extends StandardMessageCodec {
const ExtendedStandardMessageCodec();
static const int _kDateTime = 128;
static const int _kPair = 129;
@override
void writeValue(WriteBuffer buffer, dynamic value) {
if (value is DateTime) {
buffer.putUint8(_kDateTime);
buffer.putInt64(value.millisecondsSinceEpoch);
} else if (value is Pair) {
buffer.putUint8(_kPair);
writeValue(buffer, value.left);
writeValue(buffer, value.right);
} else {
super.writeValue(buffer, value);
}
}
@override
dynamic readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case _kDateTime:
return new DateTime.fromMillisecondsSinceEpoch(buffer.getInt64());
case _kPair:
return new Pair(readValue(buffer), readValue(buffer));
default: return super.readValueOfType(type, buffer);
}
}
}
Future<TestStepResult> basicBinaryHandshake(ByteData message) async { Future<TestStepResult> basicBinaryHandshake(ByteData message) async {
const BasicMessageChannel<ByteData> channel = const BasicMessageChannel<ByteData> channel =
const BasicMessageChannel<ByteData>( const BasicMessageChannel<ByteData>(
...@@ -38,7 +72,7 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async { ...@@ -38,7 +72,7 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async {
const BasicMessageChannel<dynamic> channel = const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>( const BasicMessageChannel<dynamic>(
'std-msg', 'std-msg',
const StandardMessageCodec(), const ExtendedStandardMessageCodec(),
); );
return _basicMessageHandshake<dynamic>( return _basicMessageHandshake<dynamic>(
'Standard >${toString(message)}<', channel, message); 'Standard >${toString(message)}<', channel, message);
...@@ -74,7 +108,7 @@ Future<TestStepResult> basicStandardMessageToUnknownChannel() async { ...@@ -74,7 +108,7 @@ Future<TestStepResult> basicStandardMessageToUnknownChannel() async {
const BasicMessageChannel<dynamic> channel = const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>( const BasicMessageChannel<dynamic>(
'std-unknown', 'std-unknown',
const StandardMessageCodec(), const ExtendedStandardMessageCodec(),
); );
return _basicMessageToUnknownChannel<dynamic>('Standard', channel); return _basicMessageToUnknownChannel<dynamic>('Standard', channel);
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic_messaging.dart';
import 'test_step.dart'; import 'test_step.dart';
Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async { Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async {
...@@ -27,22 +28,28 @@ Future<TestStepResult> methodCallJsonNotImplementedHandshake() async { ...@@ -27,22 +28,28 @@ Future<TestStepResult> methodCallJsonNotImplementedHandshake() async {
Future<TestStepResult> methodCallStandardSuccessHandshake( Future<TestStepResult> methodCallStandardSuccessHandshake(
dynamic payload) async { dynamic payload) async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallSuccessHandshake( return _methodCallSuccessHandshake(
'Standard success($payload)', channel, payload); 'Standard success($payload)', channel, payload);
} }
Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async { Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallErrorHandshake( return _methodCallErrorHandshake(
'Standard error($payload)', channel, payload); 'Standard error($payload)', channel, payload);
} }
Future<TestStepResult> methodCallStandardNotImplementedHandshake() async { Future<TestStepResult> methodCallStandardNotImplementedHandshake() async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallNotImplementedHandshake( return _methodCallNotImplementedHandshake(
'Standard notImplemented()', channel); 'Standard notImplemented()', channel);
} }
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// A pair of values. Used for testing custom codecs.
class Pair {
final dynamic left;
final dynamic right;
Pair(this.left, this.right);
@override
String toString() => 'Pair[$left, $right]';
}
...@@ -7,6 +7,8 @@ import 'dart:typed_data'; ...@@ -7,6 +7,8 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'pair.dart';
enum TestStatus { ok, pending, failed, complete } enum TestStatus { ok, pending, failed, complete }
typedef Future<TestStepResult> TestStep(); typedef Future<TestStepResult> TestStep();
...@@ -147,6 +149,8 @@ bool _deepEquals(dynamic a, dynamic b) { ...@@ -147,6 +149,8 @@ bool _deepEquals(dynamic a, dynamic b) {
return b is List && _deepEqualsList(a, b); return b is List && _deepEqualsList(a, b);
if (a is Map) if (a is Map)
return b is Map && _deepEqualsMap(a, b); return b is Map && _deepEqualsMap(a, b);
if (a is Pair)
return b is Pair && _deepEqualsPair(a, b);
return false; return false;
} }
...@@ -176,3 +180,7 @@ bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) { ...@@ -176,3 +180,7 @@ bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
} }
return true; return true;
} }
bool _deepEqualsPair(Pair a, Pair b) {
return _deepEquals(a.left, b.left) && _deepEquals(a.right, b.right);
}
...@@ -201,6 +201,9 @@ class JSONMethodCodec implements MethodCodec { ...@@ -201,6 +201,9 @@ class JSONMethodCodec implements MethodCodec {
/// `FlutterStandardTypedData` /// `FlutterStandardTypedData`
/// * [List]\: `NSArray` /// * [List]\: `NSArray`
/// * [Map]\: `NSDictionary` /// * [Map]\: `NSDictionary`
///
/// The codec is extensible by subclasses overriding [writeValue] and
/// [readValueOfType].
class StandardMessageCodec implements MessageCodec<dynamic> { class StandardMessageCodec implements MessageCodec<dynamic> {
// The codec serializes messages as outlined below. This format must // The codec serializes messages as outlined below. This format must
// match the Android and iOS counterparts. // match the Android and iOS counterparts.
...@@ -262,7 +265,7 @@ class StandardMessageCodec implements MessageCodec<dynamic> { ...@@ -262,7 +265,7 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
if (message == null) if (message == null)
return null; return null;
final WriteBuffer buffer = new WriteBuffer(); final WriteBuffer buffer = new WriteBuffer();
_writeValue(buffer, message); writeValue(buffer, message);
return buffer.done(); return buffer.done();
} }
...@@ -271,26 +274,25 @@ class StandardMessageCodec implements MessageCodec<dynamic> { ...@@ -271,26 +274,25 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
if (message == null) if (message == null)
return null; return null;
final ReadBuffer buffer = new ReadBuffer(message); final ReadBuffer buffer = new ReadBuffer(message);
final dynamic result = _readValue(buffer); final dynamic result = readValue(buffer);
if (buffer.hasRemaining) if (buffer.hasRemaining)
throw const FormatException('Message corrupted'); throw const FormatException('Message corrupted');
return result; return result;
} }
static void _writeSize(WriteBuffer buffer, int value) { /// Writes [value] to [buffer] by first writing a type discriminator
assert(0 <= value && value <= 0xffffffff); /// byte, then the value itself.
if (value < 254) { ///
buffer.putUint8(value); /// This method may be called recursively to serialize container values.
} else if (value <= 0xffff) { ///
buffer.putUint8(254); /// Type discriminators 0 through 127 inclusive are reserved for use by the
buffer.putUint16(value); /// base class.
} else { ///
buffer.putUint8(255); /// The codec can be extended by overriding this method, calling super
buffer.putUint32(value); /// for values that the extension does not handle. Type discriminators
} /// used by extensions must be greater than or equal to 128 in order to avoid
} /// clashes with any later extensions to the base class.
void writeValue(WriteBuffer buffer, dynamic value) {
static void _writeValue(WriteBuffer buffer, dynamic value) {
if (value == null) { if (value == null) {
buffer.putUint8(_kNull); buffer.putUint8(_kNull);
} else if (value is bool) { } else if (value is bool) {
...@@ -309,57 +311,60 @@ class StandardMessageCodec implements MessageCodec<dynamic> { ...@@ -309,57 +311,60 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
} else if (value is String) { } else if (value is String) {
buffer.putUint8(_kString); buffer.putUint8(_kString);
final List<int> bytes = utf8.encoder.convert(value); final List<int> bytes = utf8.encoder.convert(value);
_writeSize(buffer, bytes.length); writeSize(buffer, bytes.length);
buffer.putUint8List(bytes); buffer.putUint8List(bytes);
} else if (value is Uint8List) { } else if (value is Uint8List) {
buffer.putUint8(_kUint8List); buffer.putUint8(_kUint8List);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
buffer.putUint8List(value); buffer.putUint8List(value);
} else if (value is Int32List) { } else if (value is Int32List) {
buffer.putUint8(_kInt32List); buffer.putUint8(_kInt32List);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
buffer.putInt32List(value); buffer.putInt32List(value);
} else if (value is Int64List) { } else if (value is Int64List) {
buffer.putUint8(_kInt64List); buffer.putUint8(_kInt64List);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
buffer.putInt64List(value); buffer.putInt64List(value);
} else if (value is Float64List) { } else if (value is Float64List) {
buffer.putUint8(_kFloat64List); buffer.putUint8(_kFloat64List);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
buffer.putFloat64List(value); buffer.putFloat64List(value);
} else if (value is List) { } else if (value is List) {
buffer.putUint8(_kList); buffer.putUint8(_kList);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
for (final dynamic item in value) { for (final dynamic item in value) {
_writeValue(buffer, item); writeValue(buffer, item);
} }
} else if (value is Map) { } else if (value is Map) {
buffer.putUint8(_kMap); buffer.putUint8(_kMap);
_writeSize(buffer, value.length); writeSize(buffer, value.length);
value.forEach((dynamic key, dynamic value) { value.forEach((dynamic key, dynamic value) {
_writeValue(buffer, key); writeValue(buffer, key);
_writeValue(buffer, value); writeValue(buffer, value);
}); });
} else { } else {
throw new ArgumentError.value(value); throw new ArgumentError.value(value);
} }
} }
static int _readSize(ReadBuffer buffer) { /// Reads a value from [buffer] as written by [writeValue].
final int value = buffer.getUint8(); ///
if (value < 254) /// This method is intended for use by subclasses overriding
return value; /// [readValueOfType].
else if (value == 254) dynamic readValue(ReadBuffer buffer) {
return buffer.getUint16();
else
return buffer.getUint32();
}
static dynamic _readValue(ReadBuffer buffer) {
if (!buffer.hasRemaining) if (!buffer.hasRemaining)
throw const FormatException('Message corrupted'); throw const FormatException('Message corrupted');
final int type = buffer.getUint8();
return readValueOfType(type, buffer);
}
/// Reads a value of the indicated [type] from [buffer].
///
/// The codec can be extended by overriding this method, calling super
/// for types that the extension does not handle.
dynamic readValueOfType(int type, ReadBuffer buffer) {
dynamic result; dynamic result;
switch (buffer.getUint8()) { switch (type) {
case _kNull: case _kNull:
result = null; result = null;
break; break;
...@@ -379,7 +384,7 @@ class StandardMessageCodec implements MessageCodec<dynamic> { ...@@ -379,7 +384,7 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
// Flutter Engine APIs to use large ints have been deprecated on // Flutter Engine APIs to use large ints have been deprecated on
// 2018-01-09 and will be made unavailable. // 2018-01-09 and will be made unavailable.
// TODO(mravn): remove this case once the APIs are unavailable. // TODO(mravn): remove this case once the APIs are unavailable.
final int length = _readSize(buffer); final int length = readSize(buffer);
final String hex = utf8.decoder.convert(buffer.getUint8List(length)); final String hex = utf8.decoder.convert(buffer.getUint8List(length));
result = int.parse(hex, radix: 16); result = int.parse(hex, radix: 16);
break; break;
...@@ -387,43 +392,77 @@ class StandardMessageCodec implements MessageCodec<dynamic> { ...@@ -387,43 +392,77 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
result = buffer.getFloat64(); result = buffer.getFloat64();
break; break;
case _kString: case _kString:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = utf8.decoder.convert(buffer.getUint8List(length)); result = utf8.decoder.convert(buffer.getUint8List(length));
break; break;
case _kUint8List: case _kUint8List:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = buffer.getUint8List(length); result = buffer.getUint8List(length);
break; break;
case _kInt32List: case _kInt32List:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = buffer.getInt32List(length); result = buffer.getInt32List(length);
break; break;
case _kInt64List: case _kInt64List:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = buffer.getInt64List(length); result = buffer.getInt64List(length);
break; break;
case _kFloat64List: case _kFloat64List:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = buffer.getFloat64List(length); result = buffer.getFloat64List(length);
break; break;
case _kList: case _kList:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = new List<dynamic>(length); result = new List<dynamic>(length);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
result[i] = _readValue(buffer); result[i] = readValue(buffer);
} }
break; break;
case _kMap: case _kMap:
final int length = _readSize(buffer); final int length = readSize(buffer);
result = <dynamic, dynamic>{}; result = <dynamic, dynamic>{};
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
result[_readValue(buffer)] = _readValue(buffer); result[readValue(buffer)] = readValue(buffer);
} }
break; break;
default: throw const FormatException('Message corrupted'); default: throw const FormatException('Message corrupted');
} }
return result; return result;
} }
/// Writes a non-negative 32-bit integer [value] to [buffer]
/// using an expanding 1-5 byte encoding that optimizes for small values.
///
/// This method is intended for use by subclasses overriding
/// [writeValue].
void writeSize(WriteBuffer buffer, int value) {
assert(0 <= value && value <= 0xffffffff);
if (value < 254) {
buffer.putUint8(value);
} else if (value <= 0xffff) {
buffer.putUint8(254);
buffer.putUint16(value);
} else {
buffer.putUint8(255);
buffer.putUint32(value);
}
}
/// Reads a non-negative int from [buffer] as written by [writeSize].
///
/// This method is intended for use by subclasses overriding
/// [readValueOfType].
int readSize(ReadBuffer buffer) {
final int value = buffer.getUint8();
switch (value) {
case 254:
return buffer.getUint16();
case 255:
return buffer.getUint32();
default:
return value;
}
}
} }
/// [MethodCodec] using the Flutter standard binary encoding. /// [MethodCodec] using the Flutter standard binary encoding.
...@@ -448,21 +487,24 @@ class StandardMethodCodec implements MethodCodec { ...@@ -448,21 +487,24 @@ class StandardMethodCodec implements MethodCodec {
// string, the error message string, and the error details value. // string, the error message string, and the error details value.
/// Creates a [MethodCodec] using the Flutter standard binary encoding. /// Creates a [MethodCodec] using the Flutter standard binary encoding.
const StandardMethodCodec(); const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);
/// The message codec that this method codec uses for encoding values.
final StandardMessageCodec messageCodec;
@override @override
ByteData encodeMethodCall(MethodCall call) { ByteData encodeMethodCall(MethodCall call) {
final WriteBuffer buffer = new WriteBuffer(); final WriteBuffer buffer = new WriteBuffer();
StandardMessageCodec._writeValue(buffer, call.method); messageCodec.writeValue(buffer, call.method);
StandardMessageCodec._writeValue(buffer, call.arguments); messageCodec.writeValue(buffer, call.arguments);
return buffer.done(); return buffer.done();
} }
@override @override
MethodCall decodeMethodCall(ByteData methodCall) { MethodCall decodeMethodCall(ByteData methodCall) {
final ReadBuffer buffer = new ReadBuffer(methodCall); final ReadBuffer buffer = new ReadBuffer(methodCall);
final dynamic method = StandardMessageCodec._readValue(buffer); final dynamic method = messageCodec.readValue(buffer);
final dynamic arguments = StandardMessageCodec._readValue(buffer); final dynamic arguments = messageCodec.readValue(buffer);
if (method is String && !buffer.hasRemaining) if (method is String && !buffer.hasRemaining)
return new MethodCall(method, arguments); return new MethodCall(method, arguments);
else else
...@@ -473,7 +515,7 @@ class StandardMethodCodec implements MethodCodec { ...@@ -473,7 +515,7 @@ class StandardMethodCodec implements MethodCodec {
ByteData encodeSuccessEnvelope(dynamic result) { ByteData encodeSuccessEnvelope(dynamic result) {
final WriteBuffer buffer = new WriteBuffer(); final WriteBuffer buffer = new WriteBuffer();
buffer.putUint8(0); buffer.putUint8(0);
StandardMessageCodec._writeValue(buffer, result); messageCodec.writeValue(buffer, result);
return buffer.done(); return buffer.done();
} }
...@@ -481,9 +523,9 @@ class StandardMethodCodec implements MethodCodec { ...@@ -481,9 +523,9 @@ class StandardMethodCodec implements MethodCodec {
ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) { ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) {
final WriteBuffer buffer = new WriteBuffer(); final WriteBuffer buffer = new WriteBuffer();
buffer.putUint8(1); buffer.putUint8(1);
StandardMessageCodec._writeValue(buffer, code); messageCodec.writeValue(buffer, code);
StandardMessageCodec._writeValue(buffer, message); messageCodec.writeValue(buffer, message);
StandardMessageCodec._writeValue(buffer, details); messageCodec.writeValue(buffer, details);
return buffer.done(); return buffer.done();
} }
...@@ -494,10 +536,10 @@ class StandardMethodCodec implements MethodCodec { ...@@ -494,10 +536,10 @@ class StandardMethodCodec implements MethodCodec {
throw const FormatException('Expected envelope, got nothing'); throw const FormatException('Expected envelope, got nothing');
final ReadBuffer buffer = new ReadBuffer(envelope); final ReadBuffer buffer = new ReadBuffer(envelope);
if (buffer.getUint8() == 0) if (buffer.getUint8() == 0)
return StandardMessageCodec._readValue(buffer); return messageCodec.readValue(buffer);
final dynamic errorCode = StandardMessageCodec._readValue(buffer); final dynamic errorCode = messageCodec.readValue(buffer);
final dynamic errorMessage = StandardMessageCodec._readValue(buffer); final dynamic errorMessage = messageCodec.readValue(buffer);
final dynamic errorDetails = StandardMessageCodec._readValue(buffer); final dynamic errorDetails = messageCodec.readValue(buffer);
if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining) if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining)
throw new PlatformException(code: errorCode, message: errorMessage, details: errorDetails); throw new PlatformException(code: errorCode, message: errorMessage, details: errorDetails);
else else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment