add StandardMessageCodec extracted from Flutter SDK (#2434)
diff --git a/packages/standard_message_codec/.gitignore b/packages/standard_message_codec/.gitignore
new file mode 100644
index 0000000..9331cd4
--- /dev/null
+++ b/packages/standard_message_codec/.gitignore
@@ -0,0 +1,6 @@
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
diff --git a/packages/standard_message_codec/AUTHORS b/packages/standard_message_codec/AUTHORS
new file mode 100644
index 0000000..557dff9
--- /dev/null
+++ b/packages/standard_message_codec/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/packages/standard_message_codec/CHANGELOG.md b/packages/standard_message_codec/CHANGELOG.md
new file mode 100644
index 0000000..a9888a2
--- /dev/null
+++ b/packages/standard_message_codec/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* Initial release of standard message codec extracted from the Flutter SDK.
diff --git a/packages/standard_message_codec/LICENSE b/packages/standard_message_codec/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/standard_message_codec/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/standard_message_codec/README.md b/packages/standard_message_codec/README.md
new file mode 100644
index 0000000..b67b977
--- /dev/null
+++ b/packages/standard_message_codec/README.md
@@ -0,0 +1,52 @@
+An efficient and schemaless binary format used by the Flutter SDK.
+
+## Features
+
+### Efficiency
+
+The standard message codec is a binary format, as opposed to text based formats
+like JSON. Consider the following snippet of JSON:
+
+```json
+{
+ "data": [1, 2, 3, 4],
+}
+```
+
+In order for this message to be decoded into a Dart map, a utf8 binary file must
+first be parsed and validated into a Dart string. Then a second pass is performed
+which looks for specific characters that indicate JSON structures - for example
+"{" and "}". No sizes or lengths are known ahead of time while, parsing, so the
+resulting Dart list created for the "data" key is append to as decoding happens.
+
+In contrast, decoding the standard message codec version of this message avoids
+utf8 decoding, instead operating on the bytes themselves. The only string constructed
+will be for the "data" key. The length of the list in the data field is encoded in
+the structure, meaning the correct length object can be allocated and filled in
+as decoding happens.
+
+### Schemaless
+
+Using standard message codec does not require a schema (like protobuf) or any
+generated code. This makes it easy to use for dynamic messages and simplifies
+the integration into existing codebases.
+
+The tradeoff for this ease of use is that it becomes the application's
+responsibility to verify the structure of messages sent/received. There is also
+no automatic backwards compatibility like protobuf.
+
+## Getting started
+
+standard_message_codec can be used to encode and decode messages in either Flutter
+or pure Dart applications.
+
+<?code-excerpt "readme_excerpts.dart (Encoding)"?>
+```dart
+void main() {
+ final ByteData? data = const StandardMessageCodec().encodeMessage(<Object, Object>{
+ 'foo': true,
+ 3: 'fizz',
+ });
+ print('The encoded message is $data');
+}
+```
diff --git a/packages/standard_message_codec/example/README.md b/packages/standard_message_codec/example/README.md
new file mode 100644
index 0000000..39712c7
--- /dev/null
+++ b/packages/standard_message_codec/example/README.md
@@ -0,0 +1,3 @@
+# example_standard_message_codec
+
+A sample app for demonstrating the StandardMessageCodec
diff --git a/packages/standard_message_codec/example/lib/readme_excerpts.dart b/packages/standard_message_codec/example/lib/readme_excerpts.dart
new file mode 100644
index 0000000..f2d4412
--- /dev/null
+++ b/packages/standard_message_codec/example/lib/readme_excerpts.dart
@@ -0,0 +1,20 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file exists solely to host compiled excerpts for README.md, and is not
+// intended for use as an actual example application.
+
+// #docregion Encoding
+import 'dart:typed_data';
+import 'package:standard_message_codec/standard_message_codec.dart';
+// #enddocregion Encoding
+
+void main() {
+ final ByteData? data =
+ const StandardMessageCodec().encodeMessage(<Object, Object>{
+ 'foo': true,
+ 3: 'fizz',
+ });
+ print('The encoded message is $data');
+}
diff --git a/packages/standard_message_codec/example/pubspec.yaml b/packages/standard_message_codec/example/pubspec.yaml
new file mode 100644
index 0000000..4f81a03
--- /dev/null
+++ b/packages/standard_message_codec/example/pubspec.yaml
@@ -0,0 +1,11 @@
+name: standard_message_codec_examples
+description: Example code for standard message codec usage
+version: 0.0.1
+publish_to: none
+
+environment:
+ sdk: ">=2.17.0 <3.0.0"
+
+dependencies:
+ standard_message_codec:
+ path: ../
diff --git a/packages/standard_message_codec/lib/src/serialization.dart b/packages/standard_message_codec/lib/src/serialization.dart
new file mode 100644
index 0000000..cdcf9b3
--- /dev/null
+++ b/packages/standard_message_codec/lib/src/serialization.dart
@@ -0,0 +1,285 @@
+// Copyright 2013 The Flutter 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:math' as math;
+import 'dart:typed_data';
+
+export 'dart:typed_data'
+ show
+ ByteData,
+ Endian,
+ Float32List,
+ Float64List,
+ Int32List,
+ Int64List,
+ Uint8List;
+
+/// Write-only buffer for incrementally building a [ByteData] instance.
+///
+/// A WriteBuffer instance can be used only once. Attempts to reuse will result
+/// in [StateError]s being thrown.
+///
+/// The byte order used is [Endian.host] throughout.
+class WriteBuffer {
+ /// Creates an interface for incrementally building a [ByteData] instance.
+ /// [startCapacity] determines the start size of the [WriteBuffer] in bytes.
+ /// The closer that value is to the real size used, the better the
+ /// performance.
+ factory WriteBuffer({int startCapacity = 8}) {
+ assert(startCapacity > 0);
+ final ByteData eightBytes = ByteData(8);
+ final Uint8List eightBytesAsList = eightBytes.buffer.asUint8List();
+ return WriteBuffer._(
+ Uint8List(startCapacity), eightBytes, eightBytesAsList);
+ }
+
+ WriteBuffer._(this._buffer, this._eightBytes, this._eightBytesAsList);
+
+ Uint8List _buffer;
+ int _currentSize = 0;
+ bool _isDone = false;
+ final ByteData _eightBytes;
+ final Uint8List _eightBytesAsList;
+ static final Uint8List _zeroBuffer = Uint8List(8);
+
+ void _add(int byte) {
+ if (_currentSize == _buffer.length) {
+ _resize();
+ }
+ _buffer[_currentSize] = byte;
+ _currentSize += 1;
+ }
+
+ void _append(Uint8List other) {
+ final int newSize = _currentSize + other.length;
+ if (newSize >= _buffer.length) {
+ _resize(newSize);
+ }
+ _buffer.setRange(_currentSize, newSize, other);
+ _currentSize += other.length;
+ }
+
+ void _addAll(Uint8List data, [int start = 0, int? end]) {
+ final int newEnd = end ?? _eightBytesAsList.length;
+ final int newSize = _currentSize + (newEnd - start);
+ if (newSize >= _buffer.length) {
+ _resize(newSize);
+ }
+ _buffer.setRange(_currentSize, newSize, data);
+ _currentSize = newSize;
+ }
+
+ void _resize([int? requiredLength]) {
+ final int doubleLength = _buffer.length * 2;
+ final int newLength = math.max(requiredLength ?? 0, doubleLength);
+ final Uint8List newBuffer = Uint8List(newLength);
+ newBuffer.setRange(0, _buffer.length, _buffer);
+ _buffer = newBuffer;
+ }
+
+ /// Write a Uint8 into the buffer.
+ void putUint8(int byte) {
+ assert(!_isDone);
+ _add(byte);
+ }
+
+ /// Write a Uint16 into the buffer.
+ void putUint16(int value, {Endian? endian}) {
+ assert(!_isDone);
+ _eightBytes.setUint16(0, value, endian ?? Endian.host);
+ _addAll(_eightBytesAsList, 0, 2);
+ }
+
+ /// Write a Uint32 into the buffer.
+ void putUint32(int value, {Endian? endian}) {
+ assert(!_isDone);
+ _eightBytes.setUint32(0, value, endian ?? Endian.host);
+ _addAll(_eightBytesAsList, 0, 4);
+ }
+
+ /// Write an Int32 into the buffer.
+ void putInt32(int value, {Endian? endian}) {
+ assert(!_isDone);
+ _eightBytes.setInt32(0, value, endian ?? Endian.host);
+ _addAll(_eightBytesAsList, 0, 4);
+ }
+
+ /// Write an Int64 into the buffer.
+ void putInt64(int value, {Endian? endian}) {
+ assert(!_isDone);
+ _eightBytes.setInt64(0, value, endian ?? Endian.host);
+ _addAll(_eightBytesAsList, 0, 8);
+ }
+
+ /// Write an Float64 into the buffer.
+ void putFloat64(double value, {Endian? endian}) {
+ assert(!_isDone);
+ _alignTo(8);
+ _eightBytes.setFloat64(0, value, endian ?? Endian.host);
+ _addAll(_eightBytesAsList);
+ }
+
+ /// Write all the values from a [Uint8List] into the buffer.
+ void putUint8List(Uint8List list) {
+ assert(!_isDone);
+ _append(list);
+ }
+
+ /// Write all the values from an [Int32List] into the buffer.
+ void putInt32List(Int32List list) {
+ assert(!_isDone);
+ _alignTo(4);
+ _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length));
+ }
+
+ /// Write all the values from an [Int64List] into the buffer.
+ void putInt64List(Int64List list) {
+ assert(!_isDone);
+ _alignTo(8);
+ _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
+ }
+
+ /// Write all the values from a [Float32List] into the buffer.
+ void putFloat32List(Float32List list) {
+ assert(!_isDone);
+ _alignTo(4);
+ _append(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length));
+ }
+
+ /// Write all the values from a [Float64List] into the buffer.
+ void putFloat64List(Float64List list) {
+ assert(!_isDone);
+ _alignTo(8);
+ _append(list.buffer.asUint8List(list.offsetInBytes, 8 * list.length));
+ }
+
+ void _alignTo(int alignment) {
+ assert(!_isDone);
+ final int mod = _currentSize % alignment;
+ if (mod != 0) {
+ _addAll(_zeroBuffer, 0, alignment - mod);
+ }
+ }
+
+ /// Finalize and return the written [ByteData].
+ ByteData done() {
+ if (_isDone) {
+ throw StateError(
+ 'done() must not be called more than once on the same $runtimeType.');
+ }
+ final ByteData result = _buffer.buffer.asByteData(0, _currentSize);
+ _buffer = Uint8List(0);
+ _isDone = true;
+ return result;
+ }
+}
+
+/// Read-only buffer for reading sequentially from a [ByteData] instance.
+///
+/// The byte order used is [Endian.host] throughout.
+class ReadBuffer {
+ /// Creates a [ReadBuffer] for reading from the specified [data].
+ ReadBuffer(this.data);
+
+ /// The underlying data being read.
+ final ByteData data;
+
+ /// The position to read next.
+ int _position = 0;
+
+ /// Whether the buffer has data remaining to read.
+ bool get hasRemaining => _position < data.lengthInBytes;
+
+ /// Reads a Uint8 from the buffer.
+ int getUint8() {
+ return data.getUint8(_position++);
+ }
+
+ /// Reads a Uint16 from the buffer.
+ int getUint16({Endian? endian}) {
+ final int value = data.getUint16(_position, endian ?? Endian.host);
+ _position += 2;
+ return value;
+ }
+
+ /// Reads a Uint32 from the buffer.
+ int getUint32({Endian? endian}) {
+ final int value = data.getUint32(_position, endian ?? Endian.host);
+ _position += 4;
+ return value;
+ }
+
+ /// Reads an Int32 from the buffer.
+ int getInt32({Endian? endian}) {
+ final int value = data.getInt32(_position, endian ?? Endian.host);
+ _position += 4;
+ return value;
+ }
+
+ /// Reads an Int64 from the buffer.
+ int getInt64({Endian? endian}) {
+ final int value = data.getInt64(_position, endian ?? Endian.host);
+ _position += 8;
+ return value;
+ }
+
+ /// Reads a Float64 from the buffer.
+ double getFloat64({Endian? endian}) {
+ _alignTo(8);
+ final double value = data.getFloat64(_position, endian ?? Endian.host);
+ _position += 8;
+ return value;
+ }
+
+ /// Reads the given number of Uint8s from the buffer.
+ Uint8List getUint8List(int length) {
+ final Uint8List list =
+ data.buffer.asUint8List(data.offsetInBytes + _position, length);
+ _position += length;
+ return list;
+ }
+
+ /// Reads the given number of Int32s from the buffer.
+ Int32List getInt32List(int length) {
+ _alignTo(4);
+ final Int32List list =
+ data.buffer.asInt32List(data.offsetInBytes + _position, length);
+ _position += 4 * length;
+ return list;
+ }
+
+ /// Reads the given number of Int64s from the buffer.
+ Int64List getInt64List(int length) {
+ _alignTo(8);
+ final Int64List list =
+ data.buffer.asInt64List(data.offsetInBytes + _position, length);
+ _position += 8 * length;
+ return list;
+ }
+
+ /// Reads the given number of Float32s from the buffer
+ Float32List getFloat32List(int length) {
+ _alignTo(4);
+ final Float32List list =
+ data.buffer.asFloat32List(data.offsetInBytes + _position, length);
+ _position += 4 * length;
+ return list;
+ }
+
+ /// Reads the given number of Float64s from the buffer.
+ Float64List getFloat64List(int length) {
+ _alignTo(8);
+ final Float64List list =
+ data.buffer.asFloat64List(data.offsetInBytes + _position, length);
+ _position += 8 * length;
+ return list;
+ }
+
+ void _alignTo(int alignment) {
+ final int mod = _position % alignment;
+ if (mod != 0) {
+ _position += alignment - mod;
+ }
+ }
+}
diff --git a/packages/standard_message_codec/lib/standard_message_codec.dart b/packages/standard_message_codec/lib/standard_message_codec.dart
new file mode 100644
index 0000000..3ecddf4
--- /dev/null
+++ b/packages/standard_message_codec/lib/standard_message_codec.dart
@@ -0,0 +1,406 @@
+// Copyright 2013 The Flutter 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:convert';
+
+import 'src/serialization.dart';
+
+export 'src/serialization.dart' show ReadBuffer, WriteBuffer;
+
+const int _writeBufferStartCapacity = 64;
+
+/// A message encoding/decoding mechanism.
+///
+/// Both operations throw an exception, if conversion fails. Such situations
+/// should be treated as programming errors.
+///
+/// See also:
+///
+/// * [BasicMessageChannel], which use [MessageCodec]s for communication
+/// between Flutter and platform plugins.
+abstract class MessageCodec<T> {
+ /// Encodes the specified [message] in binary.
+ ///
+ /// Returns null if the message is null.
+ ByteData? encodeMessage(T message);
+
+ /// Decodes the specified [message] from binary.
+ ///
+ /// Returns null if the message is null.
+ T? decodeMessage(ByteData? message);
+}
+
+/// [MessageCodec] using the Flutter standard binary encoding.
+///
+/// Supported messages are acyclic values of these forms:
+///
+/// * null
+/// * [bool]s
+/// * [num]s
+/// * [String]s
+/// * [Uint8List]s, [Int32List]s, [Int64List]s, [Float64List]s
+/// * [List]s of supported values
+/// * [Map]s from supported values to supported values
+///
+/// Decoded values will use `List<Object?>` and `Map<Object?, Object?>`
+/// irrespective of content.
+///
+/// The type returned from [decodeMessage] is `dynamic` (not `Object?`), which
+/// means *no type checking is performed on its return value*. It is strongly
+/// recommended that the return value be immediately cast to a known type to
+/// prevent runtime errors due to typos that the type checker could otherwise
+/// catch.
+///
+/// The codec is extensible by subclasses overriding [writeValue] and
+/// [readValueOfType].
+///
+/// ## Android specifics
+///
+/// On Android, messages are represented as follows:
+///
+/// * null: null
+/// * [bool]\: `java.lang.Boolean`
+/// * [int]\: `java.lang.Integer` for values that are representable using 32-bit
+/// two's complement; `java.lang.Long` otherwise
+/// * [double]\: `java.lang.Double`
+/// * [String]\: `java.lang.String`
+/// * [Uint8List]\: `byte[]`
+/// * [Int32List]\: `int[]`
+/// * [Int64List]\: `long[]`
+/// * [Float64List]\: `double[]`
+/// * [List]\: `java.util.ArrayList`
+/// * [Map]\: `java.util.HashMap`
+///
+/// When sending a `java.math.BigInteger` from Java, it is converted into a
+/// [String] with the hexadecimal representation of the integer. (The value is
+/// tagged as being a big integer; subclasses of this class could be made to
+/// support it natively; see the discussion at [writeValue].) This codec does
+/// not support sending big integers from Dart.
+///
+/// ## iOS specifics
+///
+/// On iOS, messages are represented as follows:
+///
+/// * null: nil
+/// * [bool]\: `NSNumber numberWithBool:`
+/// * [int]\: `NSNumber numberWithInt:` for values that are representable using
+/// 32-bit two's complement; `NSNumber numberWithLong:` otherwise
+/// * [double]\: `NSNumber numberWithDouble:`
+/// * [String]\: `NSString`
+/// * [Uint8List], [Int32List], [Int64List], [Float64List]\:
+/// `FlutterStandardTypedData`
+/// * [List]\: `NSArray`
+/// * [Map]\: `NSDictionary`
+class StandardMessageCodec implements MessageCodec<Object?> {
+ /// Creates a [MessageCodec] using the Flutter standard binary encoding.
+ const StandardMessageCodec();
+
+ // The codec serializes messages as outlined below. This format must match the
+ // Android and iOS counterparts and cannot change (as it's possible for
+ // someone to end up using this for persistent storage).
+ //
+ // * A single byte with one of the constant values below determines the
+ // type of the value.
+ // * The serialization of the value itself follows the type byte.
+ // * Numbers are represented using the host endianness throughout.
+ // * Lengths and sizes of serialized parts are encoded using an expanding
+ // format optimized for the common case of small non-negative integers:
+ // * 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 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 unsigned representation of the value.
+ // * null, true, and false have empty serialization; they are encoded directly
+ // in the type byte (using _valueNull, _valueTrue, _valueFalse)
+ // * Integers representable in 32 bits are encoded using 4 bytes two's
+ // complement representation.
+ // * Larger integers are encoded using 8 bytes two's complement
+ // representation.
+ // * doubles are encoded using the IEEE 754 64-bit double-precision binary
+ // format. Zero bytes are added before the encoded double value to align it
+ // to a 64 bit boundary in the full message.
+ // * Strings are encoded using their UTF-8 representation. First the length
+ // of that in bytes is encoded using the expanding format, then follows the
+ // UTF-8 encoding itself.
+ // * Uint8Lists, Int32Lists, Int64Lists, Float32Lists, and Float64Lists are
+ // encoded by first encoding the list's element count in the expanding
+ // format, then the smallest number of zero bytes needed to align the
+ // position in the full message with a multiple of the number of bytes per
+ // element, then the encoding of the list elements themselves, end-to-end
+ // with no additional type information, using two's complement or IEEE 754
+ // as applicable.
+ // * Lists are encoded by first encoding their length in the expanding format,
+ // then follows the recursive encoding of each element value, including the
+ // type byte (Lists are assumed to be heterogeneous).
+ // * Maps are encoded by first encoding their length in the expanding format,
+ // then follows the recursive encoding of each key/value pair, including the
+ // type byte for both (Maps are assumed to be heterogeneous).
+ //
+ // The type labels below must not change, since it's possible for this interface
+ // to be used for persistent storage.
+ static const int _valueNull = 0;
+ static const int _valueTrue = 1;
+ static const int _valueFalse = 2;
+ static const int _valueInt32 = 3;
+ static const int _valueInt64 = 4;
+ static const int _valueLargeInt = 5;
+ static const int _valueFloat64 = 6;
+ static const int _valueString = 7;
+ static const int _valueUint8List = 8;
+ static const int _valueInt32List = 9;
+ static const int _valueInt64List = 10;
+ static const int _valueFloat64List = 11;
+ static const int _valueList = 12;
+ static const int _valueMap = 13;
+ static const int _valueFloat32List = 14;
+
+ @override
+ ByteData? encodeMessage(Object? message) {
+ if (message == null) {
+ return null;
+ }
+ final WriteBuffer buffer =
+ WriteBuffer(startCapacity: _writeBufferStartCapacity);
+ writeValue(buffer, message);
+ return buffer.done();
+ }
+
+ @override
+ dynamic decodeMessage(ByteData? message) {
+ if (message == null) {
+ return null;
+ }
+ final ReadBuffer buffer = ReadBuffer(message);
+ final Object? result = readValue(buffer);
+ if (buffer.hasRemaining) {
+ throw const FormatException('Message corrupted');
+ }
+ return result;
+ }
+
+ /// Writes [value] to [buffer] by first writing a type discriminator
+ /// byte, then the value itself.
+ ///
+ /// This method may be called recursively to serialize container values.
+ ///
+ /// Type discriminators 0 through 127 inclusive are reserved for use by the
+ /// base class, as follows:
+ ///
+ /// * null = 0
+ /// * true = 1
+ /// * false = 2
+ /// * 32 bit integer = 3
+ /// * 64 bit integer = 4
+ /// * larger integers = 5 (see below)
+ /// * 64 bit floating-point number = 6
+ /// * String = 7
+ /// * Uint8List = 8
+ /// * Int32List = 9
+ /// * Int64List = 10
+ /// * Float64List = 11
+ /// * List = 12
+ /// * Map = 13
+ /// * Float32List = 14
+ /// * Reserved for future expansion: 15..127
+ ///
+ /// The codec can be extended by overriding this method, calling super
+ /// 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.
+ ///
+ /// The "larger integers" type, 5, is never used by [writeValue]. A subclass
+ /// could represent big integers from another package using that type. The
+ /// format is first the type byte (0x05), then the actual number as an ASCII
+ /// string giving the hexadecimal representation of the integer, with the
+ /// string's length as encoded by [writeSize] followed by the string bytes. On
+ /// Android, that would get converted to a `java.math.BigInteger` object. On
+ /// iOS, the string representation is returned.
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value == null) {
+ buffer.putUint8(_valueNull);
+ } else if (value is bool) {
+ buffer.putUint8(value ? _valueTrue : _valueFalse);
+ } else if (value is double) {
+ // Double precedes int because in JS everything is a double.
+ // Therefore in JS, both `is int` and `is double` always
+ // return `true`. If we check int first, we'll end up treating
+ // all numbers as ints and attempt the int32/int64 conversion,
+ // which is wrong. This precedence rule is irrelevant when
+ // decoding because we use tags to detect the type of value.
+ buffer.putUint8(_valueFloat64);
+ buffer.putFloat64(value);
+ } else if (value is int) {
+ // ignore: avoid_double_and_int_checks, JS code always goes through the `double` path above
+ if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
+ buffer.putUint8(_valueInt32);
+ buffer.putInt32(value);
+ } else {
+ buffer.putUint8(_valueInt64);
+ buffer.putInt64(value);
+ }
+ } else if (value is String) {
+ buffer.putUint8(_valueString);
+ final Uint8List asciiBytes = Uint8List(value.length);
+ Uint8List? utf8Bytes;
+ int utf8Offset = 0;
+ // Only do utf8 encoding if we encounter non-ascii characters.
+ for (int i = 0; i < value.length; i += 1) {
+ final int char = value.codeUnitAt(i);
+ if (char <= 0x7f) {
+ asciiBytes[i] = char;
+ } else {
+ utf8Bytes = utf8.encoder.convert(value.substring(i));
+ utf8Offset = i;
+ break;
+ }
+ }
+ if (utf8Bytes != null) {
+ writeSize(buffer, utf8Offset + utf8Bytes.length);
+ buffer.putUint8List(Uint8List.sublistView(asciiBytes, 0, utf8Offset));
+ buffer.putUint8List(utf8Bytes);
+ } else {
+ writeSize(buffer, asciiBytes.length);
+ buffer.putUint8List(asciiBytes);
+ }
+ } else if (value is Uint8List) {
+ buffer.putUint8(_valueUint8List);
+ writeSize(buffer, value.length);
+ buffer.putUint8List(value);
+ } else if (value is Int32List) {
+ buffer.putUint8(_valueInt32List);
+ writeSize(buffer, value.length);
+ buffer.putInt32List(value);
+ } else if (value is Int64List) {
+ buffer.putUint8(_valueInt64List);
+ writeSize(buffer, value.length);
+ buffer.putInt64List(value);
+ } else if (value is Float32List) {
+ buffer.putUint8(_valueFloat32List);
+ writeSize(buffer, value.length);
+ buffer.putFloat32List(value);
+ } else if (value is Float64List) {
+ buffer.putUint8(_valueFloat64List);
+ writeSize(buffer, value.length);
+ buffer.putFloat64List(value);
+ } else if (value is List) {
+ buffer.putUint8(_valueList);
+ writeSize(buffer, value.length);
+ for (final Object? item in value) {
+ writeValue(buffer, item);
+ }
+ } else if (value is Map) {
+ buffer.putUint8(_valueMap);
+ writeSize(buffer, value.length);
+ value.forEach((Object? key, Object? value) {
+ writeValue(buffer, key);
+ writeValue(buffer, value);
+ });
+ } else {
+ throw ArgumentError.value(value);
+ }
+ }
+
+ /// Reads a value from [buffer] as written by [writeValue].
+ ///
+ /// This method is intended for use by subclasses overriding
+ /// [readValueOfType].
+ Object? readValue(ReadBuffer buffer) {
+ if (!buffer.hasRemaining) {
+ 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. See the discussion at
+ /// [writeValue].
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case _valueNull:
+ return null;
+ case _valueTrue:
+ return true;
+ case _valueFalse:
+ return false;
+ case _valueInt32:
+ return buffer.getInt32();
+ case _valueInt64:
+ return buffer.getInt64();
+ case _valueFloat64:
+ return buffer.getFloat64();
+ case _valueLargeInt:
+ case _valueString:
+ final int length = readSize(buffer);
+ return utf8.decoder.convert(buffer.getUint8List(length));
+ case _valueUint8List:
+ final int length = readSize(buffer);
+ return buffer.getUint8List(length);
+ case _valueInt32List:
+ final int length = readSize(buffer);
+ return buffer.getInt32List(length);
+ case _valueInt64List:
+ final int length = readSize(buffer);
+ return buffer.getInt64List(length);
+ case _valueFloat32List:
+ final int length = readSize(buffer);
+ return buffer.getFloat32List(length);
+ case _valueFloat64List:
+ final int length = readSize(buffer);
+ return buffer.getFloat64List(length);
+ case _valueList:
+ final int length = readSize(buffer);
+ final List<Object?> result = List<Object?>.filled(length, null);
+ for (int i = 0; i < length; i++) {
+ result[i] = readValue(buffer);
+ }
+ return result;
+ case _valueMap:
+ final int length = readSize(buffer);
+ final Map<Object?, Object?> result = <Object?, Object?>{};
+ for (int i = 0; i < length; i++) {
+ result[readValue(buffer)] = readValue(buffer);
+ }
+ return result;
+ default:
+ throw const FormatException('Message corrupted');
+ }
+ }
+
+ /// 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;
+ }
+ }
+}
diff --git a/packages/standard_message_codec/pubspec.yaml b/packages/standard_message_codec/pubspec.yaml
new file mode 100644
index 0000000..bf86f46
--- /dev/null
+++ b/packages/standard_message_codec/pubspec.yaml
@@ -0,0 +1,11 @@
+name: standard_message_codec
+description: An efficient and schemaless binary encoding format for Flutter and Dart.
+version: 0.0.1
+repository: https://github.com/flutter/packages/tree/main/packages/standard_message_codec
+issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Astandard_message_codec
+
+environment:
+ sdk: '>=2.17.0 <3.0.0'
+
+dev_dependencies:
+ test: ^1.11.1
diff --git a/packages/standard_message_codec/test/standard_message_codec_test.dart b/packages/standard_message_codec/test/standard_message_codec_test.dart
new file mode 100644
index 0000000..6feb5fc
--- /dev/null
+++ b/packages/standard_message_codec/test/standard_message_codec_test.dart
@@ -0,0 +1,165 @@
+// Copyright 2013 The Flutter 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:standard_message_codec/standard_message_codec.dart';
+import 'package:test/test.dart';
+
+const StandardMessageCodec messageCodec = StandardMessageCodec();
+
+void main() {
+ group('Standard method codec', () {
+ test('Should encode and decode objects produced from codec', () {
+ final ByteData? data = messageCodec.encodeMessage(<Object, Object>{
+ 'foo': true,
+ 3: 'fizz',
+ });
+
+ expect(messageCodec.decodeMessage(data), <Object?, Object?>{
+ 'foo': true,
+ 3: 'fizz',
+ });
+ });
+ });
+
+ group('Write and read buffer round-trip', () {
+ test('of empty buffer', () {
+ final WriteBuffer write = WriteBuffer();
+ final ByteData written = write.done();
+
+ expect(written.lengthInBytes, 0);
+ });
+
+ test('of single byte', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putUint8(201);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(1));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getUint8(), equals(201));
+ });
+
+ test('of 32-bit integer', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putInt32(-9);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(4));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getInt32(), equals(-9));
+ });
+
+ test('of 32-bit integer in big endian', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putInt32(-9, endian: Endian.big);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(4));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getInt32(endian: Endian.big), equals(-9));
+ });
+
+ test('of 64-bit integer', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putInt64(-9000000000000);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(8));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getInt64(), equals(-9000000000000));
+ });
+
+ test('of 64-bit integer in big endian', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putInt64(-9000000000000, endian: Endian.big);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(8));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getInt64(endian: Endian.big), equals(-9000000000000));
+ });
+
+ test('of double', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putFloat64(3.14);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(8));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getFloat64(), equals(3.14));
+ });
+ test('of double in big endian', () {
+ final WriteBuffer write = WriteBuffer();
+ write.putFloat64(3.14, endian: Endian.big);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(8));
+ final ReadBuffer read = ReadBuffer(written);
+ expect(read.getFloat64(endian: Endian.big), equals(3.14));
+ });
+ test('of 32-bit int list when unaligned', () {
+ final Int32List integers = Int32List.fromList(<int>[-99, 2, 99]);
+ final WriteBuffer write = WriteBuffer();
+ write.putUint8(9);
+ write.putInt32List(integers);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(16));
+ final ReadBuffer read = ReadBuffer(written);
+ read.getUint8();
+ expect(read.getInt32List(3), equals(integers));
+ });
+
+ test('of 64-bit int list when unaligned', () {
+ final Int64List integers = Int64List.fromList(<int>[-99, 2, 99]);
+ final WriteBuffer write = WriteBuffer();
+ write.putUint8(9);
+ write.putInt64List(integers);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(32));
+ final ReadBuffer read = ReadBuffer(written);
+ read.getUint8();
+ expect(read.getInt64List(3), equals(integers));
+ });
+
+ test('of float list when unaligned', () {
+ final Float32List floats =
+ Float32List.fromList(<double>[3.14, double.nan]);
+ final WriteBuffer write = WriteBuffer();
+ write.putUint8(9);
+ write.putFloat32List(floats);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(12));
+ final ReadBuffer read = ReadBuffer(written);
+ read.getUint8();
+ final Float32List readFloats = read.getFloat32List(2);
+ expect(readFloats[0], closeTo(3.14, 0.0001));
+ expect(readFloats[1], isNaN);
+ });
+
+ test('of double list when unaligned', () {
+ final Float64List doubles =
+ Float64List.fromList(<double>[3.14, double.nan]);
+ final WriteBuffer write = WriteBuffer();
+ write.putUint8(9);
+ write.putFloat64List(doubles);
+ final ByteData written = write.done();
+ expect(written.lengthInBytes, equals(24));
+ final ReadBuffer read = ReadBuffer(written);
+ read.getUint8();
+ final Float64List readDoubles = read.getFloat64List(2);
+ expect(readDoubles[0], equals(3.14));
+ expect(readDoubles[1], isNaN);
+ });
+
+ test('done twice', () {
+ final WriteBuffer write = WriteBuffer();
+ write.done();
+ expect(() => write.done(), throwsStateError);
+ });
+
+ test('empty WriteBuffer', () {
+ expect(
+ () => WriteBuffer(startCapacity: 0), throwsA(isA<AssertionError>()));
+ });
+
+ test('size 1', () {
+ expect(() => WriteBuffer(startCapacity: 1), returnsNormally);
+ });
+ });
+}