PlatformMessageChannel: code cleanup, bugfixes, and unit tests (#8509)
This commit is contained in:
parent
69b6bb87d1
commit
ddaba8992f
@ -14,16 +14,16 @@ import 'package:typed_data/typed_buffers.dart' show Uint8Buffer;
|
||||
/// The byte order of serialized data is [Endianness.BIG_ENDIAN].
|
||||
/// The byte order of deserialized data is [Endianness.HOST_ENDIAN].
|
||||
class WriteBuffer {
|
||||
Uint8Buffer _buffer;
|
||||
ByteData _eightBytes;
|
||||
Uint8List _eightBytesAsList;
|
||||
|
||||
WriteBuffer() {
|
||||
_buffer = new Uint8Buffer();
|
||||
_eightBytes = new ByteData(8);
|
||||
_eightBytesAsList = _eightBytes.buffer.asUint8List();
|
||||
}
|
||||
|
||||
Uint8Buffer _buffer;
|
||||
ByteData _eightBytes;
|
||||
Uint8List _eightBytesAsList;
|
||||
|
||||
void putUint8(int byte) {
|
||||
_buffer.add(byte);
|
||||
}
|
||||
@ -60,9 +60,8 @@ class WriteBuffer {
|
||||
if (Endianness.HOST_ENDIAN == Endianness.BIG_ENDIAN) {
|
||||
_buffer.addAll(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length));
|
||||
} else {
|
||||
for (final int value in list) {
|
||||
for (final int value in list)
|
||||
putInt32(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,9 +70,8 @@ class WriteBuffer {
|
||||
if (Endianness.HOST_ENDIAN == Endianness.BIG_ENDIAN) {
|
||||
_buffer.addAll(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
|
||||
} else {
|
||||
for (final int value in list) {
|
||||
for (final int value in list)
|
||||
putInt64(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,18 +80,16 @@ class WriteBuffer {
|
||||
if (Endianness.HOST_ENDIAN == Endianness.BIG_ENDIAN) {
|
||||
_buffer.addAll(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
|
||||
} else {
|
||||
for (final double value in list) {
|
||||
for (final double value in list)
|
||||
putFloat64(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _alignTo(int alignment) {
|
||||
final int mod = _buffer.length % alignment;
|
||||
if (mod != 0) {
|
||||
for (int i = 0; i < alignment - mod; i++) {
|
||||
for (int i = 0; i < alignment - mod; i++)
|
||||
_buffer.add(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,9 +148,8 @@ class ReadBuffer {
|
||||
list = data.buffer.asInt32List(data.offsetInBytes + position, length);
|
||||
} else {
|
||||
final ByteData invertedData = new ByteData(4 * length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
for (int i = 0; i < length; i++)
|
||||
invertedData.setInt32(i * 4, data.getInt32(position + i * 4, Endianness.HOST_ENDIAN));
|
||||
}
|
||||
list = new Int32List.view(invertedData.buffer);
|
||||
}
|
||||
position += 4 * length;
|
||||
@ -168,9 +163,8 @@ class ReadBuffer {
|
||||
list = data.buffer.asInt64List(data.offsetInBytes + position, length);
|
||||
} else {
|
||||
final ByteData invertedData = new ByteData(8 * length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
for (int i = 0; i < length; i++)
|
||||
invertedData.setInt64(i * 8, data.getInt64(position + i * 8, Endianness.HOST_ENDIAN));
|
||||
}
|
||||
list = new Int64List.view(invertedData.buffer);
|
||||
}
|
||||
position += 8 * length;
|
||||
@ -184,9 +178,8 @@ class ReadBuffer {
|
||||
list = data.buffer.asFloat64List(data.offsetInBytes + position, length);
|
||||
} else {
|
||||
final ByteData invertedData = new ByteData(8 * length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
for (int i = 0; i < length; i++)
|
||||
invertedData.setFloat64(i * 8, data.getFloat64(position + i * 8, Endianness.HOST_ENDIAN));
|
||||
}
|
||||
list = new Float64List.view(invertedData.buffer);
|
||||
}
|
||||
position += 8 * length;
|
||||
@ -195,9 +188,8 @@ class ReadBuffer {
|
||||
|
||||
void _alignTo(int alignment) {
|
||||
final int mod = position % alignment;
|
||||
if (mod != 0) {
|
||||
if (mod != 0)
|
||||
position += alignment - mod;
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasRemaining => position < data.lengthInBytes;
|
||||
|
@ -138,11 +138,13 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
|
||||
// * The serialization of the value itself follows the type byte.
|
||||
// * Lengths and sizes of serialized parts are encoded using an expanding
|
||||
// format optimized for the common case of small non-negative integers:
|
||||
// * values 0..<254 using one byte with that value;
|
||||
// * values 254..<2^16 using three bytes, the first of which is 254, the
|
||||
// next two the usual big-endian unsigned representation of the value;
|
||||
// * values 2^16..<2^32 using five bytes, the first of which is 255, the
|
||||
// next four the usual big-endian unsigned representation of the value.
|
||||
// * values 0..253 inclusive using one byte with that value;
|
||||
// * values 254..2^16 inclusive using three bytes, the first of which is
|
||||
// 254, the next two the usual big-endian unsigned representation of the
|
||||
// value;
|
||||
// * values 2^16+1..2^32 inclusive using five bytes, the first of which is
|
||||
// 255, the next four the usual big-endian unsigned representation of the
|
||||
// value.
|
||||
// * null, true, and false have empty serialization; they are encoded directly
|
||||
// in the type byte (using _kNull, _kTrue, _kFalse)
|
||||
// * Integers representable in 32 bits are encoded using 4 bytes big-endian,
|
||||
@ -208,19 +210,19 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
|
||||
}
|
||||
|
||||
static void _writeSize(WriteBuffer buffer, int value) {
|
||||
assert(0 <= value && value < 0xffffffff);
|
||||
assert(0 <= value && value <= 0xffffffff);
|
||||
if (value < 254) {
|
||||
buffer.putUint8(value);
|
||||
} else if (value < 0xffff) {
|
||||
} else if (value <= 0xffff) {
|
||||
buffer.putUint8(254);
|
||||
buffer.putUint8(value >> 8);
|
||||
buffer.putUint8(value & 0xff);
|
||||
buffer.putUint8(value);
|
||||
} else {
|
||||
buffer.putUint8(255);
|
||||
buffer.putUint8(value >> 24);
|
||||
buffer.putUint8((value >> 16) & 0xff);
|
||||
buffer.putUint8((value >> 8) & 0xff);
|
||||
buffer.putUint8(value & 0xff);
|
||||
buffer.putUint8(value >> 16);
|
||||
buffer.putUint8(value >> 8);
|
||||
buffer.putUint8(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,15 +232,13 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
|
||||
} else if (value is bool) {
|
||||
buffer.putUint8(value ? _kTrue : _kFalse);
|
||||
} else if (value is int) {
|
||||
if (-0x7fffffff <= value && value < 0x7fffffff) {
|
||||
if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
|
||||
buffer.putUint8(_kInt32);
|
||||
buffer.putInt32(value);
|
||||
}
|
||||
else if (-0x7fffffffffffffff <= value && value < 0x7fffffffffffffff) {
|
||||
} else if (-0x7fffffffffffffff - 1 <= value && value <= 0x7fffffffffffffff) {
|
||||
buffer.putUint8(_kInt64);
|
||||
buffer.putInt64(value);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
buffer.putUint8(_kLargeInt);
|
||||
final List<int> hex = UTF8.encoder.convert(value.toRadixString(16));
|
||||
_writeSize(buffer, hex.length);
|
||||
@ -292,12 +292,12 @@ class StandardMessageCodec implements MessageCodec<dynamic> {
|
||||
return value;
|
||||
} else if (value == 254) {
|
||||
return (buffer.getUint8() << 8)
|
||||
| buffer.getUint8();
|
||||
| buffer.getUint8();
|
||||
} else {
|
||||
return (buffer.getUint8() << 24)
|
||||
| (buffer.getUint8() << 16)
|
||||
| (buffer.getUint8() << 8)
|
||||
| buffer.getUint8();
|
||||
| (buffer.getUint8() << 16)
|
||||
| (buffer.getUint8() << 8)
|
||||
| buffer.getUint8();
|
||||
}
|
||||
}
|
||||
|
||||
|
254
packages/flutter/test/services/message_codecs_test.dart
Normal file
254
packages/flutter/test/services/message_codecs_test.dart
Normal file
@ -0,0 +1,254 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('Binary codec', () {
|
||||
const MessageCodec<ByteData> binary = const BinaryCodec();
|
||||
test('should encode and decode simple messages', () {
|
||||
_checkEncodeDecode<ByteData>(binary, null);
|
||||
_checkEncodeDecode<ByteData>(binary, new ByteData(0));
|
||||
_checkEncodeDecode<ByteData>(binary, new ByteData(4)..setInt32(0, -7));
|
||||
});
|
||||
});
|
||||
group('String codec', () {
|
||||
const MessageCodec<String> string = const StringCodec();
|
||||
test('should encode and decode simple messages', () {
|
||||
_checkEncodeDecode<String>(string, null);
|
||||
_checkEncodeDecode<String>(string, '');
|
||||
_checkEncodeDecode<String>(string, 'hello');
|
||||
_checkEncodeDecode<String>(string, 'special chars >\u263A\u{1F602}<');
|
||||
});
|
||||
});
|
||||
group('JSON message codec', () {
|
||||
const MessageCodec<dynamic> json = const JSONMessageCodec();
|
||||
test('should encode and decode simple messages', () {
|
||||
_checkEncodeDecode<dynamic>(json, null);
|
||||
_checkEncodeDecode<dynamic>(json, true);
|
||||
_checkEncodeDecode<dynamic>(json, false);
|
||||
_checkEncodeDecode<dynamic>(json, 7);
|
||||
_checkEncodeDecode<dynamic>(json, -7);
|
||||
_checkEncodeDecode<dynamic>(json, 98742923489);
|
||||
_checkEncodeDecode<dynamic>(json, -98742923489);
|
||||
_checkEncodeDecode<dynamic>(json, 98740023429234899324932473298438);
|
||||
_checkEncodeDecode<dynamic>(json, -98740023429234899324932473298438);
|
||||
_checkEncodeDecode<dynamic>(json, 3.14);
|
||||
_checkEncodeDecode<dynamic>(json, '');
|
||||
_checkEncodeDecode<dynamic>(json, 'hello');
|
||||
_checkEncodeDecode<dynamic>(json, 'special chars >\u263A\u{1F602}<');
|
||||
});
|
||||
test('should encode and decode composite message', () {
|
||||
final List<dynamic> message = <dynamic>[
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
-707,
|
||||
-7000000007,
|
||||
-70000000000000000000000000000000000000000000000007,
|
||||
-3.14,
|
||||
'',
|
||||
'hello',
|
||||
<dynamic>['nested', <dynamic>[]],
|
||||
<dynamic, dynamic>{ 'a': 'nested', 'b': <dynamic, dynamic>{} },
|
||||
'world',
|
||||
];
|
||||
_checkEncodeDecode<dynamic>(json, message);
|
||||
});
|
||||
});
|
||||
group('Standard message codec', () {
|
||||
const MessageCodec<dynamic> standard = const StandardMessageCodec();
|
||||
test('should encode integers correctly at boundary cases', () {
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
-0x7fffffff - 1,
|
||||
<int>[3, 0x80, 0x00, 0x00, 0x00],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
-0x7fffffff - 2,
|
||||
<int>[4, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
0x7fffffff,
|
||||
<int>[3, 0x7f, 0xff, 0xff, 0xff],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
0x7fffffff + 1,
|
||||
<int>[4, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
-0x7fffffffffffffff - 1,
|
||||
<int>[4, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
-0x7fffffffffffffff - 2,
|
||||
<int>[5, 17]..addAll('-8000000000000001'.codeUnits),
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
0x7fffffffffffffff,
|
||||
<int>[4, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
0x7fffffffffffffff + 1,
|
||||
<int>[5, 16]..addAll('8000000000000000'.codeUnits),
|
||||
);
|
||||
});
|
||||
test('should encode sizes correctly at boundary cases', () {
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
new Uint8List(253),
|
||||
<int>[8, 253]..addAll(new List<int>.filled(253, 0)),
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
new Uint8List(254),
|
||||
<int>[8, 254, 0, 254]..addAll(new List<int>.filled(254, 0)),
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
new Uint8List(0xffff),
|
||||
<int>[8, 254, 0xff, 0xff]..addAll(new List<int>.filled(0xffff, 0)),
|
||||
);
|
||||
_checkEncoding<dynamic>(
|
||||
standard,
|
||||
new Uint8List(0xffff + 1),
|
||||
<int>[8, 255, 0, 1, 0, 0]..addAll(new List<int>.filled(0xffff + 1, 0)),
|
||||
);
|
||||
});
|
||||
test('should encode and decode simple messages', () {
|
||||
_checkEncodeDecode<dynamic>(standard, null);
|
||||
_checkEncodeDecode<dynamic>(standard, true);
|
||||
_checkEncodeDecode<dynamic>(standard, false);
|
||||
_checkEncodeDecode<dynamic>(standard, 7);
|
||||
_checkEncodeDecode<dynamic>(standard, -7);
|
||||
_checkEncodeDecode<dynamic>(standard, 98742923489);
|
||||
_checkEncodeDecode<dynamic>(standard, -98742923489);
|
||||
_checkEncodeDecode<dynamic>(standard, 98740023429234899324932473298438);
|
||||
_checkEncodeDecode<dynamic>(standard, -98740023429234899324932473298438);
|
||||
_checkEncodeDecode<dynamic>(standard, 3.14);
|
||||
_checkEncodeDecode<dynamic>(standard, double.INFINITY);
|
||||
_checkEncodeDecode<dynamic>(standard, double.NAN);
|
||||
_checkEncodeDecode<dynamic>(standard, '');
|
||||
_checkEncodeDecode<dynamic>(standard, 'hello');
|
||||
_checkEncodeDecode<dynamic>(standard, 'special chars >\u263A\u{1F602}<');
|
||||
});
|
||||
test('should encode and decode composite message', () {
|
||||
final List<dynamic> message = <dynamic>[
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
-707,
|
||||
-7000000007,
|
||||
-70000000000000000000000000000000000000000000000007,
|
||||
-3.14,
|
||||
'',
|
||||
'hello',
|
||||
new Uint8List.fromList(<int>[0xBA, 0x5E, 0xBA, 0x11]),
|
||||
new Int32List.fromList(<int>[-0x7fffffff - 1, 0, 0x7fffffff]),
|
||||
null, // ensures the offset of the following list is unaligned.
|
||||
new Int64List.fromList(
|
||||
<int>[-0x7fffffffffffffff - 1, 0, 0x7fffffffffffffff]),
|
||||
null, // ensures the offset of the following list is unaligned.
|
||||
new Float64List.fromList(<double>[
|
||||
double.NEGATIVE_INFINITY,
|
||||
-double.MAX_FINITE,
|
||||
-double.MIN_POSITIVE,
|
||||
-0.0,
|
||||
0.0,
|
||||
double.MIN_POSITIVE,
|
||||
double.MAX_FINITE,
|
||||
double.INFINITY,
|
||||
double.NAN
|
||||
]),
|
||||
<dynamic>['nested', <dynamic>[]],
|
||||
<dynamic, dynamic>{ 'a': 'nested', null: <dynamic, dynamic>{} },
|
||||
'world',
|
||||
];
|
||||
_checkEncodeDecode<dynamic>(standard, message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _checkEncoding<T>(MessageCodec<T> codec, T message, List<int> expectedBytes) {
|
||||
final ByteData encoded = codec.encodeMessage(message);
|
||||
expect(
|
||||
encoded.buffer.asUint8List(0, encoded.lengthInBytes),
|
||||
orderedEquals(expectedBytes),
|
||||
);
|
||||
}
|
||||
|
||||
void _checkEncodeDecode<T>(MessageCodec<T> codec, T message) {
|
||||
final ByteData encoded = codec.encodeMessage(message);
|
||||
final T decoded = codec.decodeMessage(encoded);
|
||||
if (message == null) {
|
||||
expect(encoded, isNull);
|
||||
expect(decoded, isNull);
|
||||
} else {
|
||||
expect(_deepEquals(message, decoded), isTrue);
|
||||
final ByteData encodedAgain = codec.encodeMessage(decoded);
|
||||
expect(
|
||||
encodedAgain.buffer.asUint8List(),
|
||||
orderedEquals(encoded.buffer.asUint8List()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _deepEquals(dynamic valueA, dynamic valueB) {
|
||||
if (valueA is TypedData)
|
||||
return valueB is TypedData && _deepEqualsTypedData(valueA, valueB);
|
||||
if (valueA is List)
|
||||
return valueB is List && _deepEqualsList(valueA, valueB);
|
||||
if (valueA is Map)
|
||||
return valueB is Map && _deepEqualsMap(valueA, valueB);
|
||||
if (valueA is double && valueA.isNaN)
|
||||
return valueB is double && valueB.isNaN;
|
||||
return valueA == valueB;
|
||||
}
|
||||
|
||||
bool _deepEqualsTypedData(TypedData valueA, TypedData valueB) {
|
||||
if (valueA is ByteData) {
|
||||
return valueB is ByteData
|
||||
&& _deepEqualsList(
|
||||
valueA.buffer.asUint8List(), valueB.buffer.asUint8List());
|
||||
}
|
||||
if (valueA is Uint8List)
|
||||
return valueB is Uint8List && _deepEqualsList(valueA, valueB);
|
||||
if (valueA is Int32List)
|
||||
return valueB is Int32List && _deepEqualsList(valueA, valueB);
|
||||
if (valueA is Int64List)
|
||||
return valueB is Int64List && _deepEqualsList(valueA, valueB);
|
||||
if (valueA is Float64List)
|
||||
return valueB is Float64List && _deepEqualsList(valueA, valueB);
|
||||
throw 'Unexpected typed data: $valueA';
|
||||
}
|
||||
|
||||
bool _deepEqualsList(List<dynamic> valueA, List<dynamic> valueB) {
|
||||
if (valueA.length != valueB.length)
|
||||
return false;
|
||||
for (int i = 0; i < valueA.length; i++) {
|
||||
if (!_deepEquals(valueA[i], valueB[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _deepEqualsMap(Map<dynamic, dynamic> valueA, Map<dynamic, dynamic> valueB) {
|
||||
if (valueA.length != valueB.length)
|
||||
return false;
|
||||
for (final dynamic key in valueA.keys) {
|
||||
if (!valueB.containsKey(key) || !_deepEquals(valueA[key], valueB[key]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user