blob: 8adf1a1d0380d73e87c7c5cc8f29bf57f04ff474 [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:typed_data';
import 'remote_instance.dart';
/// All serialization must be done in a serialization Zone, which tells it
/// whether we are the client or server.
///
/// In [SerializationMode.server], sets up a remote instance cache to use when
/// deserializing remote instances back to their original instance.
T withSerializationMode<T>(
SerializationMode mode,
T Function() fn, {
Serializer Function()? serializerFactory,
Deserializer Function(Object? data)? deserializerFactory,
}) =>
runZoned(fn, zoneValues: {
#serializationMode: mode,
if (!mode.isClient) remoteInstanceZoneKey: <int, RemoteInstance>{}
});
/// Serializable interface
abstract class Serializable {
/// Serializes this object using [serializer].
void serialize(Serializer serializer);
}
/// A push based object serialization interface.
abstract class Serializer {
/// Serializes a [String].
void addString(String value);
/// Serializes a nullable [String].
void addNullableString(String? value) =>
value == null ? addNull() : addString(value);
/// Serializes a [double].
void addDouble(double value);
/// Serializes a nullable [double].
void addNullableDouble(double? value) =>
value == null ? addNull() : addDouble(value);
/// Serializes an [int].
void addInt(int value);
/// Serializes a nullable [int].
void addNullableInt(int? value) => value == null ? addNull() : addInt(value);
/// Serializes a [bool].
void addBool(bool value);
/// Serializes a nullable [bool].
void addNullableBool(bool? value) =>
value == null ? addNull() : addBool(value);
/// Serializes a `null` literal.
void addNull();
/// Used to signal the start of an arbitrary length list of items.
void startList();
/// Used to signal the end of an arbitrary length list of items.
void endList();
/// Returns the resulting serialized object.
Object? get result;
}
/// A pull based object deserialization interface.
///
/// You must call [moveNext] before reading any items, and in order to advance
/// to the next item.
abstract class Deserializer {
/// Checks if the current value is a null, returns `true` if so and `false`
/// otherwise.
bool checkNull();
/// Reads the current value as a non-nullable [String].
bool expectBool();
/// Reads the current value as a nullable [bool].
bool? expectNullableBool() => checkNull() ? null : expectBool();
/// Reads the current value as a non-nullable [double].
double expectDouble();
/// Reads the current value as a nullable [double].
double? expectNullableDouble() => checkNull() ? null : expectDouble();
/// Reads the current value as a non-nullable [int].
int expectInt();
/// Reads the current value as a nullable [int].
int? expectNullableInt() => checkNull() ? null : expectInt();
/// Reads the current value as a non-nullable [String].
String expectString();
/// Reads the current value as a nullable [String].
String? expectNullableString() => checkNull() ? null : expectString();
/// Asserts that the current item is the start of a list.
///
/// An example for how to read from a list is as follows:
///
/// var json = JsonReader.fromString(source);
/// I know it's a list of strings.
///
/// ```
/// var result = <String>[];
/// deserializer.moveNext();
/// deserializer.expectList();
/// while (json.moveNext()) {
/// result.add(json.expectString());
/// }
/// // Can now read later items, but need to call `moveNext` again to move
/// // past the list.
/// deserializer.moveNext();
/// deserializer.expectBool();
/// ```
void expectList();
/// Moves to the next item, returns `false` if there are no more items to
/// read.
///
/// If inside of a list, this returns `false` when the end of the list is
/// reached, and moves back to the parent, but does not advance it, so another
/// call to `moveNext` is needed. See example in the [expectList] docs.
bool moveNext();
}
class JsonSerializer implements Serializer {
/// The full result.
final _result = <Object?>[];
/// A path to the current list we are modifying.
late List<List<Object?>> _path = [_result];
/// Returns the result as an unmodifiable [Iterable].
///
/// Asserts that all [List] entries have not been closed with [endList].
@override
Iterable<Object?> get result {
assert(_path.length == 1);
return _result;
}
@override
void addBool(bool value) => _path.last.add(value);
@override
void addNullableBool(bool? value) => _path.last.add(value);
@override
void addDouble(double value) => _path.last.add(value);
@override
void addNullableDouble(double? value) => _path.last.add(value);
@override
void addInt(int value) => _path.last.add(value);
@override
void addNullableInt(int? value) => _path.last.add(value);
@override
void addString(String value) => _path.last.add(value);
@override
void addNullableString(String? value) => _path.last.add(value);
@override
void addNull() => _path.last.add(null);
@override
void startList() {
List<Object?> sublist = [];
_path.last.add(sublist);
_path.add(sublist);
}
@override
void endList() {
_path.removeLast();
}
}
class JsonDeserializer implements Deserializer {
/// The root source list to read from.
final Iterable<Object?> _source;
/// The path to the current iterator we are reading from.
late List<Iterator<Object?>> _path = [];
/// Whether we have received our first [moveNext] call.
bool _initialized = false;
/// Initialize this deserializer from `_source`.
JsonDeserializer(this._source);
@override
bool checkNull() => _expectValue<Object?>() == null;
@override
void expectList() => _path.add(_expectValue<Iterable<Object?>>().iterator);
@override
bool expectBool() => _expectValue();
@override
bool? expectNullableBool() => _expectValue();
@override
double expectDouble() => _expectValue();
@override
double? expectNullableDouble() => _expectValue();
@override
int expectInt() => _expectValue();
@override
int? expectNullableInt() => _expectValue();
@override
String expectString() => _expectValue();
@override
String? expectNullableString() => _expectValue();
/// Reads the current value and casts it to [T].
T _expectValue<T>() {
if (!_initialized) {
throw new StateError(
'You must call `moveNext()` before reading any values.');
}
return _path.last.current as T;
}
@override
bool moveNext() {
if (!_initialized) {
_path.add(_source.iterator);
_initialized = true;
}
// Move the current iterable, if its at the end of its items remove it from
// the current path and return false.
if (!_path.last.moveNext()) {
_path.removeLast();
return false;
}
return true;
}
}
class ByteDataSerializer extends Serializer {
final BytesBuilder _builder = new BytesBuilder();
// Re-usable 8 byte list and view for encoding doubles.
final Uint8List _eightByteList = new Uint8List(8);
late final ByteData _eightByteListData =
new ByteData.sublistView(_eightByteList);
@override
void addBool(bool value) => _builder
.addByte(value ? DataKind.boolTrue.index : DataKind.boolFalse.index);
@override
void addDouble(double value) {
_eightByteListData.setFloat64(0, value);
_builder
..addByte(DataKind.float64.index)
..add(_eightByteList);
}
@override
void addNull() => _builder.addByte(DataKind.nil.index);
@override
void addInt(int value) {
if (value >= 0x0) {
if (value + DataKind.values.length <= 0xff) {
_builder..addByte(value + DataKind.values.length);
} else if (value <= 0xff) {
_builder
..addByte(DataKind.uint8.index)
..addByte(value);
} else if (value <= 0xffff) {
_builder
..addByte(DataKind.uint16.index)
..addByte(value >> 8)
..addByte(value);
} else if (value <= 0xffffffff) {
_builder
..addByte(DataKind.uint32.index)
..addByte(value >> 24)
..addByte(value >> 16)
..addByte(value >> 8)
..addByte(value);
} else {
_builder
..addByte(DataKind.uint64.index)
..addByte(value >> 56)
..addByte(value >> 48)
..addByte(value >> 40)
..addByte(value >> 32)
..addByte(value >> 24)
..addByte(value >> 16)
..addByte(value >> 8)
..addByte(value);
}
} else {
if (value >= -0x80) {
_builder
..addByte(DataKind.int8.index)
..addByte(value);
} else if (value >= -0x8000) {
_builder
..addByte(DataKind.int16.index)
..addByte(value >> 8)
..addByte(value);
} else if (value >= -0x8000000) {
_builder
..addByte(DataKind.int32.index)
..addByte(value >> 24)
..addByte(value >> 16)
..addByte(value >> 8)
..addByte(value);
} else {
_builder
..addByte(DataKind.int64.index)
..addByte(value >> 56)
..addByte(value >> 48)
..addByte(value >> 40)
..addByte(value >> 32)
..addByte(value >> 24)
..addByte(value >> 16)
..addByte(value >> 8)
..addByte(value);
}
}
}
@override
void addString(String value) {
for (int i = 0; i < value.length; i++) {
if (value.codeUnitAt(i) > 0xff) {
_addTwoByteString(value);
return;
}
}
_addOneByteString(value);
}
void _addOneByteString(String value) {
_builder.addByte(DataKind.oneByteString.index);
addInt(value.length);
for (int i = 0; i < value.length; i++) {
_builder.addByte(value.codeUnitAt(i));
}
}
void _addTwoByteString(String value) {
_builder.addByte(DataKind.twoByteString.index);
addInt(value.length);
for (int i = 0; i < value.length; i++) {
int codeUnit = value.codeUnitAt(i);
switch (Endian.host) {
case Endian.little:
_builder
..addByte(codeUnit)
..addByte(codeUnit >> 8);
break;
case Endian.big:
_builder
..addByte(codeUnit >> 8)
..addByte(codeUnit);
break;
}
}
}
@override
void endList() => _builder.addByte(DataKind.endList.index);
@override
void startList() => _builder.addByte(DataKind.startList.index);
@override
Uint8List get result => _builder.takeBytes();
}
class ByteDataDeserializer extends Deserializer {
final ByteData _bytes;
int _byteOffset = 0;
int? _byteOffsetIncrement = 0;
ByteDataDeserializer(this._bytes);
/// Reads the next [DataKind] and advances [_byteOffset].
DataKind _readKind([int offset = 0]) {
int value = _bytes.getUint8(_byteOffset + offset);
if (value < DataKind.values.length) {
return DataKind.values[value];
} else {
return DataKind.directEncodedUint8;
}
}
@override
bool checkNull() {
_byteOffsetIncrement = 1;
return _readKind() == DataKind.nil;
}
@override
bool expectBool() {
DataKind kind = _readKind();
_byteOffsetIncrement = 1;
if (kind == DataKind.boolTrue) {
return true;
} else if (kind == DataKind.boolFalse) {
return false;
} else {
throw new StateError('Expected a bool but found a $kind');
}
}
@override
double expectDouble() {
DataKind kind = _readKind();
if (kind != DataKind.float64) {
throw new StateError('Expected a double but found a $kind');
}
_byteOffsetIncrement = 9;
return _bytes.getFloat64(_byteOffset + 1);
}
@override
int expectInt() => _expectInt(0);
int _expectInt(int offset) {
DataKind kind = _readKind(offset);
if (kind == DataKind.directEncodedUint8) {
_byteOffsetIncrement = offset + 1;
return _bytes.getUint8(_byteOffset + offset) - DataKind.values.length;
}
offset += 1;
int result;
switch (kind) {
case DataKind.int8:
result = _bytes.getInt8(_byteOffset + offset);
_byteOffsetIncrement = 1 + offset;
break;
case DataKind.int16:
result = _bytes.getInt16(_byteOffset + offset);
_byteOffsetIncrement = 2 + offset;
break;
case DataKind.int32:
result = _bytes.getInt32(_byteOffset + offset);
_byteOffsetIncrement = 4 + offset;
break;
case DataKind.int64:
result = _bytes.getInt64(_byteOffset + offset);
_byteOffsetIncrement = 8 + offset;
break;
case DataKind.uint8:
result = _bytes.getUint8(_byteOffset + offset);
_byteOffsetIncrement = 1 + offset;
break;
case DataKind.uint16:
result = _bytes.getUint16(_byteOffset + offset);
_byteOffsetIncrement = 2 + offset;
break;
case DataKind.uint32:
result = _bytes.getUint32(_byteOffset + offset);
_byteOffsetIncrement = 4 + offset;
break;
case DataKind.uint64:
result = _bytes.getUint64(_byteOffset + offset);
_byteOffsetIncrement = 8 + offset;
break;
default:
throw new StateError('Expected an int but found a $kind');
}
return result;
}
@override
void expectList() {
DataKind kind = _readKind();
if (kind != DataKind.startList) {
throw new StateError('Expected the start to a list but found a $kind');
}
_byteOffsetIncrement = 1;
}
@override
String expectString() {
DataKind kind = _readKind();
int length = _expectInt(1);
int offset = _byteOffsetIncrement! + _byteOffset;
if (kind == DataKind.oneByteString) {
_byteOffsetIncrement = _byteOffsetIncrement! + length;
return new String.fromCharCodes(
_bytes.buffer.asUint8List(offset, length));
} else if (kind == DataKind.twoByteString) {
length = length * 2;
_byteOffsetIncrement = _byteOffsetIncrement! + length;
Uint8List bytes =
new Uint8List.fromList(_bytes.buffer.asUint8List(offset, length));
return new String.fromCharCodes(bytes.buffer.asUint16List());
} else {
throw new StateError('Expected a string but found a $kind');
}
}
@override
bool moveNext() {
int? increment = _byteOffsetIncrement;
_byteOffsetIncrement = null;
if (increment == null) {
throw new StateError("Can't move until consuming the current element");
}
_byteOffset += increment;
if (_byteOffset >= _bytes.lengthInBytes) {
return false;
} else if (_readKind() == DataKind.endList) {
// You don't explicitly consume list end markers.
_byteOffsetIncrement = 1;
return false;
} else {
return true;
}
}
}
enum DataKind {
nil,
boolTrue,
boolFalse,
directEncodedUint8, // Encoded in the kind byte.
startList,
endList,
int8,
int16,
int32,
int64,
uint8,
uint16,
uint32,
uint64,
float64,
oneByteString,
twoByteString,
}
/// Must be set using `withSerializationMode` before doing any serialization or
/// deserialization.
SerializationMode get serializationMode {
SerializationMode? mode =
Zone.current[#serializationMode] as SerializationMode?;
if (mode == null) {
throw new StateError('No SerializationMode set, you must do all '
'serialization inside a call to `withSerializationMode`.');
}
return mode;
}
/// Returns the current deserializer factory for the zone.
Deserializer Function(Object?) get deserializerFactory {
switch (serializationMode) {
case SerializationMode.byteDataClient:
case SerializationMode.byteDataServer:
return (Object? message) => new ByteDataDeserializer(
new ByteData.sublistView(message as Uint8List));
case SerializationMode.jsonClient:
case SerializationMode.jsonServer:
return (Object? message) =>
new JsonDeserializer(message as Iterable<Object?>);
}
}
/// Returns the current serializer factory for the zone.
Serializer Function() get serializerFactory {
switch (serializationMode) {
case SerializationMode.byteDataClient:
case SerializationMode.byteDataServer:
return () => new ByteDataSerializer();
case SerializationMode.jsonClient:
case SerializationMode.jsonServer:
return () => new JsonSerializer();
}
}
/// Some objects are serialized differently on the client side versus the server
/// side. This indicates the different modes, as well as the format used.
enum SerializationMode {
byteDataClient,
byteDataServer,
jsonServer,
jsonClient,
}
extension IsClient on SerializationMode {
bool get isClient {
switch (this) {
case SerializationMode.byteDataClient:
case SerializationMode.jsonClient:
return true;
case SerializationMode.byteDataServer:
case SerializationMode.jsonServer:
return false;
}
}
}