mirror of
https://github.com/encounter/engine.git
synced 2026-03-30 11:09:55 -07:00
335 lines
12 KiB
Java
335 lines
12 KiB
Java
// Copyright 2017 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package io.flutter.plugin.common;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.math.BigInteger;
|
|
import java.nio.ByteBuffer;
|
|
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.
|
|
*
|
|
* The standard encoding is guaranteed to be compatible with the corresponding standard codec
|
|
* for PlatformMessageChannels on the Flutter side. These parts of the Flutter SDK are evolved
|
|
* synchronously.
|
|
*
|
|
* Supported messages are acyclic values of these forms:
|
|
*
|
|
* <ul>
|
|
* <li>null</li>
|
|
* <li>Booleans</li>
|
|
* <li>Bytes, Shorts, Integers, Longs, BigIntegers</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>
|
|
*/
|
|
public final class StandardMessageCodec implements MessageCodec<Object> {
|
|
// This codec must match the Dart codec of the same name in package flutter/services.
|
|
public static final StandardMessageCodec INSTANCE = new StandardMessageCodec();
|
|
|
|
private 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 || !message.hasRemaining()) {
|
|
return null;
|
|
}
|
|
final Object value = readValue(message);
|
|
if (message.hasRemaining()) {
|
|
throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
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;
|
|
|
|
private static void writeSize(ByteArrayOutputStream stream, int value) {
|
|
assert 0 <= value;
|
|
if (value < 254) {
|
|
stream.write(value);
|
|
} else if (value <= 0xffff) {
|
|
stream.write(254);
|
|
stream.write(value >>> 8);
|
|
stream.write(value);
|
|
} else {
|
|
stream.write(255);
|
|
writeInt(stream, value);
|
|
}
|
|
}
|
|
|
|
private static void writeInt(ByteArrayOutputStream stream, int value) {
|
|
stream.write(value >>> 24);
|
|
stream.write(value >>> 16);
|
|
stream.write(value >>> 8);
|
|
stream.write(value);
|
|
}
|
|
|
|
private static void writeLong(ByteArrayOutputStream stream, long value) {
|
|
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);
|
|
}
|
|
|
|
private static void writeDouble(ByteArrayOutputStream stream, double value) {
|
|
writeLong(stream, Double.doubleToLongBits(value));
|
|
}
|
|
|
|
private static void writeBytes(ByteArrayOutputStream stream, byte[] bytes) {
|
|
writeSize(stream, bytes.length);
|
|
stream.write(bytes, 0, bytes.length);
|
|
}
|
|
|
|
private static 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void writeValue(ByteArrayOutputStream stream, Object value) {
|
|
if (value == 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);
|
|
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);
|
|
}
|
|
}
|
|
|
|
private static 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.get() & 0xff) << 8)
|
|
| ((buffer.get() & 0xff));
|
|
} else {
|
|
return ((buffer.get() & 0xff) << 24)
|
|
| ((buffer.get() & 0xff) << 16)
|
|
| ((buffer.get() & 0xff) << 8)
|
|
| ((buffer.get() & 0xff));
|
|
}
|
|
}
|
|
|
|
private static byte[] readBytes(ByteBuffer buffer) {
|
|
final int length = readSize(buffer);
|
|
final byte[] bytes = new byte[length];
|
|
buffer.get(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
private static void readAlignment(ByteBuffer buffer, int alignment) {
|
|
final int mod = buffer.position() % alignment;
|
|
if (mod != 0) {
|
|
buffer.position(buffer.position() + alignment - mod);
|
|
}
|
|
}
|
|
|
|
static Object readValue(ByteBuffer buffer) {
|
|
if (!buffer.hasRemaining()) {
|
|
throw new IllegalArgumentException("Message corrupted");
|
|
}
|
|
final Object result;
|
|
switch (buffer.get()) {
|
|
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:
|
|
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;
|
|
}
|
|
}
|
|
}
|