mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
444 lines
15 KiB
Java
444 lines
15 KiB
Java
// 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.
|
|
|
|
package io.flutter.plugin.common;
|
|
|
|
import android.util.Log;
|
|
|
|
import io.flutter.BuildConfig;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.math.BigInteger;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.Charset;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
|
|
/**
|
|
* MessageCodec using the Flutter standard binary encoding.
|
|
*
|
|
* <p>This codec is guaranteed to be compatible with the corresponding
|
|
* <a href="https://docs.flutter.io/flutter/services/StandardMessageCodec-class.html">StandardMessageCodec</a>
|
|
* on the Dart side. These parts of the Flutter SDK are evolved synchronously.</p>
|
|
*
|
|
* <p>Supported messages are acyclic values of these forms:</p>
|
|
*
|
|
* <ul>
|
|
* <li>null</li>
|
|
* <li>Booleans</li>
|
|
* <li>Bytes, Shorts, Integers, Longs</li>
|
|
* <li>BigIntegers (see below)</li>
|
|
* <li>Floats, Doubles</li>
|
|
* <li>Strings</li>
|
|
* <li>byte[], int[], long[], double[]</li>
|
|
* <li>Lists of supported values</li>
|
|
* <li>Maps with supported keys and values</li>
|
|
* </ul>
|
|
*
|
|
* <p>On the Dart side, these values are represented as follows:</p>
|
|
*
|
|
* <ul>
|
|
* <li>null: null</li>
|
|
* <li>Boolean: bool</li>
|
|
* <li>Byte, Short, Integer, Long: int</li>
|
|
* <li>Float, Double: double</li>
|
|
* <li>String: String</li>
|
|
* <li>byte[]: Uint8List</li>
|
|
* <li>int[]: Int32List</li>
|
|
* <li>long[]: Int64List</li>
|
|
* <li>double[]: Float64List</li>
|
|
* <li>List: List</li>
|
|
* <li>Map: Map</li>
|
|
* </ul>
|
|
*
|
|
* <p>BigIntegers are represented in Dart as strings with the
|
|
* hexadecimal representation of the integer's value.</p>
|
|
*
|
|
* <p>To extend the codec, overwrite the writeValue and readValueOfType methods.</p>
|
|
*/
|
|
public class StandardMessageCodec implements MessageCodec<Object> {
|
|
private static final String TAG = "StandardMessageCodec#";
|
|
public static final StandardMessageCodec INSTANCE = new StandardMessageCodec();
|
|
|
|
@Override
|
|
public ByteBuffer encodeMessage(Object message) {
|
|
if (message == null) {
|
|
return null;
|
|
}
|
|
final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
|
|
writeValue(stream, message);
|
|
final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
|
|
buffer.put(stream.buffer(), 0, stream.size());
|
|
return buffer;
|
|
}
|
|
|
|
@Override
|
|
public Object decodeMessage(ByteBuffer message) {
|
|
if (message == null) {
|
|
return null;
|
|
}
|
|
message.order(ByteOrder.nativeOrder());
|
|
final Object value = readValue(message);
|
|
if (message.hasRemaining()) {
|
|
throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
|
|
private static final Charset UTF8 = Charset.forName("UTF8");
|
|
private static final byte NULL = 0;
|
|
private static final byte TRUE = 1;
|
|
private static final byte FALSE = 2;
|
|
private static final byte INT = 3;
|
|
private static final byte LONG = 4;
|
|
private static final byte BIGINT = 5;
|
|
private static final byte DOUBLE = 6;
|
|
private static final byte STRING = 7;
|
|
private static final byte BYTE_ARRAY = 8;
|
|
private static final byte INT_ARRAY = 9;
|
|
private static final byte LONG_ARRAY = 10;
|
|
private static final byte DOUBLE_ARRAY = 11;
|
|
private static final byte LIST = 12;
|
|
private static final byte MAP = 13;
|
|
|
|
/**
|
|
* Writes an int representing a size to the specified stream.
|
|
* Uses an expanding code of 1 to 5 bytes to optimize for small values.
|
|
*/
|
|
protected static final void writeSize(ByteArrayOutputStream stream, int value) {
|
|
if (BuildConfig.DEBUG && 0 > value) {
|
|
Log.e(TAG, "Attempted to write a negative size.");
|
|
}
|
|
if (value < 254) {
|
|
stream.write(value);
|
|
} else if (value <= 0xffff) {
|
|
stream.write(254);
|
|
writeChar(stream, value);
|
|
} else {
|
|
stream.write(255);
|
|
writeInt(stream, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the least significant two bytes of the specified int to the
|
|
* specified stream.
|
|
*/
|
|
protected static final void writeChar(ByteArrayOutputStream stream, int value) {
|
|
if (LITTLE_ENDIAN) {
|
|
stream.write(value);
|
|
stream.write(value >>> 8);
|
|
} else {
|
|
stream.write(value >>> 8);
|
|
stream.write(value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the specified int as 4 bytes to the specified stream.
|
|
*/
|
|
protected static final void writeInt(ByteArrayOutputStream stream, int value) {
|
|
if (LITTLE_ENDIAN) {
|
|
stream.write(value);
|
|
stream.write(value >>> 8);
|
|
stream.write(value >>> 16);
|
|
stream.write(value >>> 24);
|
|
} else {
|
|
stream.write(value >>> 24);
|
|
stream.write(value >>> 16);
|
|
stream.write(value >>> 8);
|
|
stream.write(value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the specified long as 8 bytes to the specified stream.
|
|
*/
|
|
protected static final void writeLong(ByteArrayOutputStream stream, long value) {
|
|
if (LITTLE_ENDIAN) {
|
|
stream.write((byte) value);
|
|
stream.write((byte) (value >>> 8));
|
|
stream.write((byte) (value >>> 16));
|
|
stream.write((byte) (value >>> 24));
|
|
stream.write((byte) (value >>> 32));
|
|
stream.write((byte) (value >>> 40));
|
|
stream.write((byte) (value >>> 48));
|
|
stream.write((byte) (value >>> 56));
|
|
} else {
|
|
stream.write((byte) (value >>> 56));
|
|
stream.write((byte) (value >>> 48));
|
|
stream.write((byte) (value >>> 40));
|
|
stream.write((byte) (value >>> 32));
|
|
stream.write((byte) (value >>> 24));
|
|
stream.write((byte) (value >>> 16));
|
|
stream.write((byte) (value >>> 8));
|
|
stream.write((byte) value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the specified double as 8 bytes to the specified stream.
|
|
*/
|
|
protected static final void writeDouble(ByteArrayOutputStream stream, double value) {
|
|
writeLong(stream, Double.doubleToLongBits(value));
|
|
}
|
|
|
|
/**
|
|
* Writes the length and then the actual bytes of the specified array to
|
|
* the specified stream.
|
|
*/
|
|
protected static final void writeBytes(ByteArrayOutputStream stream, byte[] bytes) {
|
|
writeSize(stream, bytes.length);
|
|
stream.write(bytes, 0, bytes.length);
|
|
}
|
|
|
|
/**
|
|
* Writes a number of padding bytes to the specified stream to ensure that
|
|
* the next value is aligned to a whole multiple of the specified alignment.
|
|
* An example usage with alignment = 8 is to ensure doubles are word-aligned
|
|
* in the stream.
|
|
*/
|
|
protected static final void writeAlignment(ByteArrayOutputStream stream, int alignment) {
|
|
final int mod = stream.size() % alignment;
|
|
if (mod != 0) {
|
|
for (int i = 0; i < alignment - mod; i++) {
|
|
stream.write(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes a type discriminator byte and then a byte serialization of the
|
|
* specified value to the specified stream.
|
|
*
|
|
* <p>Subclasses can extend the codec by overriding this method, calling
|
|
* super for values that the extension does not handle.</p>
|
|
*/
|
|
protected void writeValue(ByteArrayOutputStream stream, Object value) {
|
|
if (value == null || value.equals(null)) {
|
|
stream.write(NULL);
|
|
} else if (value == Boolean.TRUE) {
|
|
stream.write(TRUE);
|
|
} else if (value == Boolean.FALSE) {
|
|
stream.write(FALSE);
|
|
} else if (value instanceof Number) {
|
|
if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
|
|
stream.write(INT);
|
|
writeInt(stream, ((Number) value).intValue());
|
|
} else if (value instanceof Long) {
|
|
stream.write(LONG);
|
|
writeLong(stream, (long) value);
|
|
} else if (value instanceof Float || value instanceof Double) {
|
|
stream.write(DOUBLE);
|
|
writeAlignment(stream, 8);
|
|
writeDouble(stream, ((Number) value).doubleValue());
|
|
} else if (value instanceof BigInteger) {
|
|
stream.write(BIGINT);
|
|
writeBytes(stream,
|
|
((BigInteger) value).toString(16).getBytes(UTF8));
|
|
} else {
|
|
throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
|
|
}
|
|
} else if (value instanceof String) {
|
|
stream.write(STRING);
|
|
writeBytes(stream, ((String) value).getBytes(UTF8));
|
|
} else if (value instanceof byte[]) {
|
|
stream.write(BYTE_ARRAY);
|
|
writeBytes(stream, (byte[]) value);
|
|
} else if (value instanceof int[]) {
|
|
stream.write(INT_ARRAY);
|
|
final int[] array = (int[]) value;
|
|
writeSize(stream, array.length);
|
|
writeAlignment(stream, 4);
|
|
for (final int n : array) {
|
|
writeInt(stream, n);
|
|
}
|
|
} else if (value instanceof long[]) {
|
|
stream.write(LONG_ARRAY);
|
|
final long[] array = (long[]) value;
|
|
writeSize(stream, array.length);
|
|
writeAlignment(stream, 8);
|
|
for (final long n : array) {
|
|
writeLong(stream, n);
|
|
}
|
|
} else if (value instanceof double[]) {
|
|
stream.write(DOUBLE_ARRAY);
|
|
final double[] array = (double[]) value;
|
|
writeSize(stream, array.length);
|
|
writeAlignment(stream, 8);
|
|
for (final double d : array) {
|
|
writeDouble(stream, d);
|
|
}
|
|
} else if (value instanceof List) {
|
|
stream.write(LIST);
|
|
final List<?> list = (List) value;
|
|
writeSize(stream, list.size());
|
|
for (final Object o : list) {
|
|
writeValue(stream, o);
|
|
}
|
|
} else if (value instanceof Map) {
|
|
stream.write(MAP);
|
|
final Map<?, ?> map = (Map) value;
|
|
writeSize(stream, map.size());
|
|
for (final Entry<?, ?> entry: map.entrySet()) {
|
|
writeValue(stream, entry.getKey());
|
|
writeValue(stream, entry.getValue());
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Unsupported value: " + value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads an int representing a size as written by writeSize.
|
|
*/
|
|
protected static final int readSize(ByteBuffer buffer) {
|
|
if (!buffer.hasRemaining()) {
|
|
throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
final int value = buffer.get() & 0xff;
|
|
if (value < 254) {
|
|
return value;
|
|
} else if (value == 254) {
|
|
return buffer.getChar();
|
|
} else {
|
|
return buffer.getInt();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads a byte array as written by writeBytes.
|
|
*/
|
|
protected static final byte[] readBytes(ByteBuffer buffer) {
|
|
final int length = readSize(buffer);
|
|
final byte[] bytes = new byte[length];
|
|
buffer.get(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* Reads alignment padding bytes as written by writeAlignment.
|
|
*/
|
|
protected static final void readAlignment(ByteBuffer buffer, int alignment) {
|
|
final int mod = buffer.position() % alignment;
|
|
if (mod != 0) {
|
|
buffer.position(buffer.position() + alignment - mod);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads a value as written by writeValue.
|
|
*/
|
|
protected final Object readValue(ByteBuffer buffer) {
|
|
if (!buffer.hasRemaining()) {
|
|
throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
final byte type = buffer.get();
|
|
return readValueOfType(type, buffer);
|
|
}
|
|
|
|
/**
|
|
* Reads a value of the specified type.
|
|
*
|
|
* <p>Subclasses may extend the codec by overriding this method, calling
|
|
* super for types that the extension does not handle.</p>
|
|
*/
|
|
protected Object readValueOfType(byte type, ByteBuffer buffer) {
|
|
final Object result;
|
|
switch (type) {
|
|
case NULL:
|
|
result = null;
|
|
break;
|
|
case TRUE:
|
|
result = true;
|
|
break;
|
|
case FALSE:
|
|
result = false;
|
|
break;
|
|
case INT:
|
|
result = buffer.getInt();
|
|
break;
|
|
case LONG:
|
|
result = buffer.getLong();
|
|
break;
|
|
case BIGINT: {
|
|
final byte[] hex = readBytes(buffer);
|
|
result = new BigInteger(new String(hex, UTF8), 16);
|
|
break;
|
|
}
|
|
case DOUBLE:
|
|
readAlignment(buffer, 8);
|
|
result = buffer.getDouble();
|
|
break;
|
|
case STRING: {
|
|
final byte[] bytes = readBytes(buffer);
|
|
result = new String(bytes, UTF8);
|
|
break;
|
|
}
|
|
case BYTE_ARRAY: {
|
|
result = readBytes(buffer);
|
|
break;
|
|
}
|
|
case INT_ARRAY: {
|
|
final int length = readSize(buffer);
|
|
final int[] array = new int[length];
|
|
readAlignment(buffer, 4);
|
|
buffer.asIntBuffer().get(array);
|
|
result = array;
|
|
buffer.position(buffer.position() + 4 * length);
|
|
break;
|
|
}
|
|
case LONG_ARRAY: {
|
|
final int length = readSize(buffer);
|
|
final long[] array = new long[length];
|
|
readAlignment(buffer, 8);
|
|
buffer.asLongBuffer().get(array);
|
|
result = array;
|
|
buffer.position(buffer.position() + 8 * length);
|
|
break;
|
|
}
|
|
case DOUBLE_ARRAY: {
|
|
final int length = readSize(buffer);
|
|
final double[] array = new double[length];
|
|
readAlignment(buffer, 8);
|
|
buffer.asDoubleBuffer().get(array);
|
|
result = array;
|
|
buffer.position(buffer.position() + 8 * length);
|
|
break;
|
|
}
|
|
case LIST: {
|
|
final int size = readSize(buffer);
|
|
final List<Object> list = new ArrayList<>(size);
|
|
for (int i = 0; i < size; i++) {
|
|
list.add(readValue(buffer));
|
|
}
|
|
result = list;
|
|
break;
|
|
}
|
|
case MAP: {
|
|
final int size = readSize(buffer);
|
|
final Map<Object, Object> map = new HashMap<>();
|
|
for (int i = 0; i < size; i++) {
|
|
map.put(readValue(buffer), readValue(buffer));
|
|
}
|
|
result = map;
|
|
break;
|
|
}
|
|
default: throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static final class ExposedByteArrayOutputStream extends ByteArrayOutputStream {
|
|
byte[] buffer() {
|
|
return buf;
|
|
}
|
|
}
|
|
}
|