• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package io.flutter.plugin.common;
6 
7 import android.util.Log;
8 
9 import io.flutter.BuildConfig;
10 
11 import java.io.ByteArrayOutputStream;
12 import java.math.BigInteger;
13 import java.nio.ByteBuffer;
14 import java.nio.ByteOrder;
15 import java.nio.charset.Charset;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 
22 /**
23  * MessageCodec using the Flutter standard binary encoding.
24  *
25  * <p>This codec is guaranteed to be compatible with the corresponding
26  * <a href="https://docs.flutter.io/flutter/services/StandardMessageCodec-class.html">StandardMessageCodec</a>
27  * on the Dart side. These parts of the Flutter SDK are evolved synchronously.</p>
28  *
29  * <p>Supported messages are acyclic values of these forms:</p>
30  *
31  * <ul>
32  *     <li>null</li>
33  *     <li>Booleans</li>
34  *     <li>Bytes, Shorts, Integers, Longs</li>
35  *     <li>BigIntegers (see below)</li>
36  *     <li>Floats, Doubles</li>
37  *     <li>Strings</li>
38  *     <li>byte[], int[], long[], double[]</li>
39  *     <li>Lists of supported values</li>
40  *     <li>Maps with supported keys and values</li>
41  * </ul>
42  *
43  * <p>On the Dart side, these values are represented as follows:</p>
44  *
45  * <ul>
46  *     <li>null: null</li>
47  *     <li>Boolean: bool</li>
48  *     <li>Byte, Short, Integer, Long: int</li>
49  *     <li>Float, Double: double</li>
50  *     <li>String: String</li>
51  *     <li>byte[]: Uint8List</li>
52  *     <li>int[]: Int32List</li>
53  *     <li>long[]: Int64List</li>
54  *     <li>double[]: Float64List</li>
55  *     <li>List: List</li>
56  *     <li>Map: Map</li>
57  * </ul>
58  *
59  * <p>BigIntegers are represented in Dart as strings with the
60  * hexadecimal representation of the integer's value.</p>
61  *
62  * <p>To extend the codec, overwrite the writeValue and readValueOfType methods.</p>
63  */
64 public class StandardMessageCodec implements MessageCodec<Object> {
65     private static final String TAG = "StandardMessageCodec#";
66     public static final StandardMessageCodec INSTANCE = new StandardMessageCodec();
67 
68     @Override
encodeMessage(Object message)69     public ByteBuffer encodeMessage(Object message) {
70         if (message == null) {
71             return null;
72         }
73         final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
74         writeValue(stream, message);
75         final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
76         buffer.put(stream.buffer(), 0, stream.size());
77         return buffer;
78     }
79 
80     @Override
decodeMessage(ByteBuffer message)81     public Object decodeMessage(ByteBuffer message) {
82         if (message == null) {
83             return null;
84         }
85         message.order(ByteOrder.nativeOrder());
86         final Object value = readValue(message);
87         if (message.hasRemaining()) {
88             throw new IllegalArgumentException("Message corrupted");
89         }
90         return value;
91     }
92 
93     private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
94     private static final Charset UTF8 = Charset.forName("UTF8");
95     private static final byte NULL = 0;
96     private static final byte TRUE = 1;
97     private static final byte FALSE = 2;
98     private static final byte INT = 3;
99     private static final byte LONG = 4;
100     private static final byte BIGINT = 5;
101     private static final byte DOUBLE = 6;
102     private static final byte STRING = 7;
103     private static final byte BYTE_ARRAY = 8;
104     private static final byte INT_ARRAY = 9;
105     private static final byte LONG_ARRAY = 10;
106     private static final byte DOUBLE_ARRAY = 11;
107     private static final byte LIST = 12;
108     private static final byte MAP = 13;
109 
110     /**
111      * Writes an int representing a size to the specified stream.
112      * Uses an expanding code of 1 to 5 bytes to optimize for small values.
113      */
writeSize(ByteArrayOutputStream stream, int value)114     protected static final void writeSize(ByteArrayOutputStream stream, int value) {
115         if (BuildConfig.DEBUG && 0 > value) {
116             Log.e(TAG, "Attempted to write a negative size.");
117         }
118         if (value < 254) {
119             stream.write(value);
120         } else if (value <= 0xffff) {
121             stream.write(254);
122             writeChar(stream, value);
123         } else {
124             stream.write(255);
125             writeInt(stream, value);
126         }
127     }
128 
129     /**
130      * Writes the least significant two bytes of the specified int to the
131      * specified stream.
132      */
writeChar(ByteArrayOutputStream stream, int value)133     protected static final void writeChar(ByteArrayOutputStream stream, int value) {
134         if (LITTLE_ENDIAN) {
135             stream.write(value);
136             stream.write(value >>> 8);
137         } else {
138             stream.write(value >>> 8);
139             stream.write(value);
140         }
141     }
142 
143     /**
144      * Writes the specified int as 4 bytes to the specified stream.
145      */
writeInt(ByteArrayOutputStream stream, int value)146     protected static final void writeInt(ByteArrayOutputStream stream, int value) {
147         if (LITTLE_ENDIAN) {
148             stream.write(value);
149             stream.write(value >>> 8);
150             stream.write(value >>> 16);
151             stream.write(value >>> 24);
152         } else {
153             stream.write(value >>> 24);
154             stream.write(value >>> 16);
155             stream.write(value >>> 8);
156             stream.write(value);
157         }
158     }
159 
160     /**
161      * Writes the specified long as 8 bytes to the specified stream.
162      */
writeLong(ByteArrayOutputStream stream, long value)163     protected static final void writeLong(ByteArrayOutputStream stream, long value) {
164         if (LITTLE_ENDIAN) {
165             stream.write((byte) value);
166             stream.write((byte) (value >>> 8));
167             stream.write((byte) (value >>> 16));
168             stream.write((byte) (value >>> 24));
169             stream.write((byte) (value >>> 32));
170             stream.write((byte) (value >>> 40));
171             stream.write((byte) (value >>> 48));
172             stream.write((byte) (value >>> 56));
173         } else {
174             stream.write((byte) (value >>> 56));
175             stream.write((byte) (value >>> 48));
176             stream.write((byte) (value >>> 40));
177             stream.write((byte) (value >>> 32));
178             stream.write((byte) (value >>> 24));
179             stream.write((byte) (value >>> 16));
180             stream.write((byte) (value >>> 8));
181             stream.write((byte) value);
182         }
183     }
184 
185     /**
186      * Writes the specified double as 8 bytes to the specified stream.
187      */
writeDouble(ByteArrayOutputStream stream, double value)188     protected static final void writeDouble(ByteArrayOutputStream stream, double value) {
189         writeLong(stream, Double.doubleToLongBits(value));
190     }
191 
192     /**
193      * Writes the length and then the actual bytes of the specified array to
194      * the specified stream.
195      */
writeBytes(ByteArrayOutputStream stream, byte[] bytes)196     protected static final void writeBytes(ByteArrayOutputStream stream, byte[] bytes) {
197         writeSize(stream, bytes.length);
198         stream.write(bytes, 0, bytes.length);
199     }
200 
201     /**
202      * Writes a number of padding bytes to the specified stream to ensure that
203      * the next value is aligned to a whole multiple of the specified alignment.
204      * An example usage with alignment = 8 is to ensure doubles are word-aligned
205      * in the stream.
206      */
writeAlignment(ByteArrayOutputStream stream, int alignment)207     protected static final void writeAlignment(ByteArrayOutputStream stream, int alignment) {
208         final int mod = stream.size() % alignment;
209         if (mod != 0) {
210             for (int i = 0; i < alignment - mod; i++) {
211                 stream.write(0);
212             }
213         }
214     }
215 
216     /**
217      * Writes a type discriminator byte and then a byte serialization of the
218      * specified value to the specified stream.
219      *
220      * <p>Subclasses can extend the codec by overriding this method, calling
221      * super for values that the extension does not handle.</p>
222      */
writeValue(ByteArrayOutputStream stream, Object value)223     protected void writeValue(ByteArrayOutputStream stream, Object value) {
224         if (value == null) {
225             stream.write(NULL);
226         } else if (value == Boolean.TRUE) {
227             stream.write(TRUE);
228         } else if (value == Boolean.FALSE) {
229             stream.write(FALSE);
230         } else if (value instanceof Number) {
231             if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
232                 stream.write(INT);
233                 writeInt(stream, ((Number) value).intValue());
234             } else if (value instanceof Long) {
235                 stream.write(LONG);
236                 writeLong(stream, (long) value);
237             } else if (value instanceof Float || value instanceof Double) {
238                 stream.write(DOUBLE);
239                 writeAlignment(stream, 8);
240                 writeDouble(stream, ((Number) value).doubleValue());
241             } else if (value instanceof BigInteger) {
242                 stream.write(BIGINT);
243                 writeBytes(stream,
244                     ((BigInteger) value).toString(16).getBytes(UTF8));
245             } else {
246                 throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
247             }
248         } else if (value instanceof String) {
249             stream.write(STRING);
250             writeBytes(stream, ((String) value).getBytes(UTF8));
251         } else if (value instanceof byte[]) {
252             stream.write(BYTE_ARRAY);
253             writeBytes(stream, (byte[]) value);
254         } else if (value instanceof int[]) {
255             stream.write(INT_ARRAY);
256             final int[] array = (int[]) value;
257             writeSize(stream, array.length);
258             writeAlignment(stream, 4);
259             for (final int n : array) {
260                 writeInt(stream, n);
261             }
262         } else if (value instanceof long[]) {
263             stream.write(LONG_ARRAY);
264             final long[] array = (long[]) value;
265             writeSize(stream, array.length);
266             writeAlignment(stream, 8);
267             for (final long n : array) {
268                 writeLong(stream, n);
269             }
270         } else if (value instanceof double[]) {
271             stream.write(DOUBLE_ARRAY);
272             final double[] array = (double[]) value;
273             writeSize(stream, array.length);
274             writeAlignment(stream, 8);
275             for (final double d : array) {
276                 writeDouble(stream, d);
277             }
278         } else if (value instanceof List) {
279             stream.write(LIST);
280             final List<?> list = (List) value;
281             writeSize(stream, list.size());
282             for (final Object o : list) {
283                 writeValue(stream, o);
284             }
285         } else if (value instanceof Map) {
286             stream.write(MAP);
287             final Map<?, ?> map = (Map) value;
288             writeSize(stream, map.size());
289             for (final Entry<?, ?> entry: map.entrySet()) {
290                 writeValue(stream, entry.getKey());
291                 writeValue(stream, entry.getValue());
292             }
293         } else {
294             throw new IllegalArgumentException("Unsupported value: " + value);
295         }
296     }
297 
298     /**
299      * Reads an int representing a size as written by writeSize.
300      */
readSize(ByteBuffer buffer)301     protected static final int readSize(ByteBuffer buffer) {
302         if (!buffer.hasRemaining()) {
303             throw new IllegalArgumentException("Message corrupted");
304         }
305         final int value = buffer.get() & 0xff;
306         if (value < 254) {
307             return value;
308         } else if (value == 254) {
309             return buffer.getChar();
310         } else {
311             return buffer.getInt();
312         }
313     }
314 
315     /**
316      * Reads a byte array as written by writeBytes.
317      */
readBytes(ByteBuffer buffer)318     protected static final byte[] readBytes(ByteBuffer buffer) {
319         final int length = readSize(buffer);
320         final byte[] bytes = new byte[length];
321         buffer.get(bytes);
322         return bytes;
323     }
324 
325     /**
326      * Reads alignment padding bytes as written by writeAlignment.
327      */
readAlignment(ByteBuffer buffer, int alignment)328     protected static final void readAlignment(ByteBuffer buffer, int alignment) {
329         final int mod = buffer.position() % alignment;
330         if (mod != 0) {
331             buffer.position(buffer.position() + alignment - mod);
332         }
333     }
334 
335     /**
336      * Reads a value as written by writeValue.
337      */
readValue(ByteBuffer buffer)338     protected final Object readValue(ByteBuffer buffer) {
339         if (!buffer.hasRemaining()) {
340             throw new IllegalArgumentException("Message corrupted");
341         }
342         final byte type = buffer.get();
343         return readValueOfType(type, buffer);
344     }
345 
346     /**
347      * Reads a value of the specified type.
348      *
349      * <p>Subclasses may extend the codec by overriding this method, calling
350      * super for types that the extension does not handle.</p>
351      */
readValueOfType(byte type, ByteBuffer buffer)352     protected Object readValueOfType(byte type, ByteBuffer buffer) {
353         final Object result;
354         switch (type) {
355             case NULL:
356                 result = null;
357                 break;
358             case TRUE:
359                 result = true;
360                 break;
361             case FALSE:
362                 result = false;
363                 break;
364             case INT:
365                 result = buffer.getInt();
366                 break;
367             case LONG:
368                 result = buffer.getLong();
369                 break;
370             case BIGINT: {
371                 final byte[] hex = readBytes(buffer);
372                 result = new BigInteger(new String(hex, UTF8), 16);
373                 break;
374             }
375             case DOUBLE:
376                 readAlignment(buffer, 8);
377                 result = buffer.getDouble();
378                 break;
379             case STRING: {
380                 final byte[] bytes = readBytes(buffer);
381                 result = new String(bytes, UTF8);
382                 break;
383             }
384             case BYTE_ARRAY: {
385                 result = readBytes(buffer);
386                 break;
387             }
388             case INT_ARRAY: {
389                 final int length = readSize(buffer);
390                 final int[] array = new int[length];
391                 readAlignment(buffer, 4);
392                 buffer.asIntBuffer().get(array);
393                 result = array;
394                 buffer.position(buffer.position() + 4 * length);
395                 break;
396             }
397             case LONG_ARRAY: {
398                 final int length = readSize(buffer);
399                 final long[] array = new long[length];
400                 readAlignment(buffer, 8);
401                 buffer.asLongBuffer().get(array);
402                 result = array;
403                 buffer.position(buffer.position() + 8 * length);
404                 break;
405             }
406             case DOUBLE_ARRAY: {
407                 final int length = readSize(buffer);
408                 final double[] array = new double[length];
409                 readAlignment(buffer, 8);
410                 buffer.asDoubleBuffer().get(array);
411                 result = array;
412                 buffer.position(buffer.position() + 8 * length);
413                 break;
414             }
415             case LIST: {
416                 final int size = readSize(buffer);
417                 final List<Object> list = new ArrayList<>(size);
418                 for (int i = 0; i < size; i++) {
419                     list.add(readValue(buffer));
420                 }
421                 result = list;
422                 break;
423             }
424             case MAP: {
425                 final int size = readSize(buffer);
426                 final Map<Object, Object> map = new HashMap<>();
427                 for (int i = 0; i < size; i++) {
428                     map.put(readValue(buffer), readValue(buffer));
429                 }
430                 result = map;
431                 break;
432             }
433             default: throw new IllegalArgumentException("Message corrupted");
434         }
435         return result;
436     }
437 
438     static final class ExposedByteArrayOutputStream extends ByteArrayOutputStream {
buffer()439         byte[] buffer() {
440             return buf;
441         }
442     }
443 }
444