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);
+    });
+  });
+}