• 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
5part of engine;
6
7/// [MessageCodec] with unencoded binary messages represented using [ByteData].
8///
9/// On Android, messages will be represented using `java.nio.ByteBuffer`.
10/// On iOS, messages will be represented using `NSData`.
11class BinaryCodec implements MessageCodec<ByteData> {
12  /// Creates a [MessageCodec] with unencoded binary messages represented using
13  /// [ByteData].
14  const BinaryCodec();
15
16  @override
17  ByteData decodeMessage(ByteData message) => message;
18
19  @override
20  ByteData encodeMessage(ByteData message) => message;
21}
22
23/// [MessageCodec] with UTF-8 encoded String messages.
24///
25/// On Android, messages will be represented using `java.util.String`.
26/// On iOS, messages will be represented using `NSString`.
27class StringCodec implements MessageCodec<String> {
28  /// Creates a [MessageCodec] with UTF-8 encoded String messages.
29  const StringCodec();
30
31  @override
32  String decodeMessage(ByteData message) {
33    if (message == null) {
34      return null;
35    }
36    return utf8.decoder.convert(message.buffer.asUint8List());
37  }
38
39  @override
40  ByteData encodeMessage(String message) {
41    if (message == null) {
42      return null;
43    }
44    final Uint8List encoded = utf8.encoder.convert(message);
45    return encoded.buffer.asByteData();
46  }
47}
48
49/// [MessageCodec] with UTF-8 encoded JSON messages.
50///
51/// Supported messages are acyclic values of these forms:
52///
53///  * null
54///  * [bool]s
55///  * [num]s
56///  * [String]s
57///  * [List]s of supported values
58///  * [Map]s from strings to supported values
59///
60/// On Android, messages are decoded using the `org.json` library.
61/// On iOS, messages are decoded using the `NSJSONSerialization` library.
62/// In both cases, the use of top-level simple messages (null, [bool], [num],
63/// and [String]) is supported (by the Flutter SDK). The decoded value will be
64/// null/nil for null, and identical to what would result from decoding a
65/// singleton JSON array with a Boolean, number, or string value, and then
66/// extracting its single element.
67class JSONMessageCodec implements MessageCodec<dynamic> {
68  // The codec serializes messages as defined by the JSON codec of the
69  // dart:convert package. The format used must match the Android and
70  // iOS counterparts.
71
72  /// Creates a [MessageCodec] with UTF-8 encoded JSON messages.
73  const JSONMessageCodec();
74
75  @override
76  ByteData encodeMessage(dynamic message) {
77    if (message == null) {
78      return null;
79    }
80    return const StringCodec().encodeMessage(json.encode(message));
81  }
82
83  @override
84  dynamic decodeMessage(ByteData message) {
85    if (message == null) {
86      return message;
87    }
88    return json.decode(const StringCodec().decodeMessage(message));
89  }
90}
91
92/// [MethodCodec] with UTF-8 encoded JSON method calls and result envelopes.
93///
94/// Values supported as method arguments and result payloads are those supported
95/// by [JSONMessageCodec].
96class JSONMethodCodec implements MethodCodec {
97  // The codec serializes method calls, and result envelopes as outlined below.
98  // This format must match the Android and iOS counterparts.
99  //
100  // * Individual values are serialized as defined by the JSON codec of the
101  //   dart:convert package.
102  // * Method calls are serialized as two-element maps, with the method name
103  //   keyed by 'method' and the arguments keyed by 'args'.
104  // * Reply envelopes are serialized as either:
105  //   * one-element lists containing the successful result as its single
106  //     element, or
107  //   * three-element lists containing, in order, an error code String, an
108  //     error message String, and an error details value.
109
110  /// Creates a [MethodCodec] with UTF-8 encoded JSON method calls and result
111  /// envelopes.
112  const JSONMethodCodec();
113
114  @override
115  ByteData encodeMethodCall(MethodCall call) {
116    return const JSONMessageCodec().encodeMessage(<String, dynamic>{
117      'method': call.method,
118      'args': call.arguments,
119    });
120  }
121
122  @override
123  MethodCall decodeMethodCall(ByteData methodCall) {
124    final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall);
125    if (decoded is! Map) {
126      throw FormatException('Expected method call Map, got $decoded');
127    }
128    final dynamic method = decoded['method'];
129    final dynamic arguments = decoded['args'];
130    if (method is String) {
131      return MethodCall(method, arguments);
132    }
133    throw FormatException('Invalid method call: $decoded');
134  }
135
136  @override
137  dynamic decodeEnvelope(ByteData envelope) {
138    final dynamic decoded = const JSONMessageCodec().decodeMessage(envelope);
139    if (decoded is! List) {
140      throw FormatException('Expected envelope List, got $decoded');
141    }
142    if (decoded.length == 1) {
143      return decoded[0];
144    }
145    if (decoded.length == 3 &&
146        decoded[0] is String &&
147        (decoded[1] == null || decoded[1] is String)) {
148      throw PlatformException(
149        code: decoded[0],
150        message: decoded[1],
151        details: decoded[2],
152      );
153    }
154    throw FormatException('Invalid envelope: $decoded');
155  }
156
157  @override
158  ByteData encodeSuccessEnvelope(dynamic result) {
159    return const JSONMessageCodec().encodeMessage(<dynamic>[result]);
160  }
161
162  @override
163  ByteData encodeErrorEnvelope(
164      {@required String code, String message, dynamic details}) {
165    assert(code != null);
166    return const JSONMessageCodec()
167        .encodeMessage(<dynamic>[code, message, details]);
168  }
169}
170
171/// [MessageCodec] using the Flutter standard binary encoding.
172///
173/// Supported messages are acyclic values of these forms:
174///
175///  * null
176///  * [bool]s
177///  * [num]s
178///  * [String]s
179///  * [Uint8List]s, [Int32List]s, [Int64List]s, [Float64List]s
180///  * [List]s of supported values
181///  * [Map]s from supported values to supported values
182///
183/// Decoded values will use `List<dynamic>` and `Map<dynamic, dynamic>`
184/// irrespective of content.
185///
186/// On Android, messages are represented as follows:
187///
188///  * null: null
189///  * [bool]\: `java.lang.Boolean`
190///  * [int]\: `java.lang.Integer` for values that are representable using 32-bit
191///    two's complement; `java.lang.Long` otherwise
192///  * [double]\: `java.lang.Double`
193///  * [String]\: `java.lang.String`
194///  * [Uint8List]\: `byte[]`
195///  * [Int32List]\: `int[]`
196///  * [Int64List]\: `long[]`
197///  * [Float64List]\: `double[]`
198///  * [List]\: `java.util.ArrayList`
199///  * [Map]\: `java.util.HashMap`
200///
201/// On iOS, messages are represented as follows:
202///
203///  * null: nil
204///  * [bool]\: `NSNumber numberWithBool:`
205///  * [int]\: `NSNumber numberWithInt:` for values that are representable using
206///    32-bit two's complement; `NSNumber numberWithLong:` otherwise
207///  * [double]\: `NSNumber numberWithDouble:`
208///  * [String]\: `NSString`
209///  * [Uint8List], [Int32List], [Int64List], [Float64List]\:
210///    `FlutterStandardTypedData`
211///  * [List]\: `NSArray`
212///  * [Map]\: `NSDictionary`
213///
214/// The codec is extensible by subclasses overriding [writeValue] and
215/// [readValueOfType].
216class StandardMessageCodec implements MessageCodec<dynamic> {
217  // The codec serializes messages as outlined below. This format must
218  // match the Android and iOS counterparts.
219  //
220  // * A single byte with one of the constant values below determines the
221  //   type of the value.
222  // * The serialization of the value itself follows the type byte.
223  // * Numbers are represented using the host endianness throughout.
224  // * Lengths and sizes of serialized parts are encoded using an expanding
225  //   format optimized for the common case of small non-negative integers:
226  //   * values 0..253 inclusive using one byte with that value;
227  //   * values 254..2^16 inclusive using three bytes, the first of which is
228  //     254, the next two the usual unsigned representation of the value;
229  //   * values 2^16+1..2^32 inclusive using five bytes, the first of which is
230  //     255, the next four the usual unsigned representation of the value.
231  // * null, true, and false have empty serialization; they are encoded directly
232  //   in the type byte (using _kNull, _kTrue, _kFalse)
233  // * Integers representable in 32 bits are encoded using 4 bytes two's
234  //   complement representation.
235  // * Larger integers are encoded using 8 bytes two's complement
236  //   representation.
237  // * doubles are encoded using the IEEE 754 64-bit double-precision binary
238  //   format.
239  // * Strings are encoded using their UTF-8 representation. First the length
240  //   of that in bytes is encoded using the expanding format, then follows the
241  //   UTF-8 encoding itself.
242  // * Uint8Lists, Int32Lists, Int64Lists, and Float64Lists are encoded by first
243  //   encoding the list's element count in the expanding format, then the
244  //   smallest number of zero bytes needed to align the position in the full
245  //   message with a multiple of the number of bytes per element, then the
246  //   encoding of the list elements themselves, end-to-end with no additional
247  //   type information, using two's complement or IEEE 754 as applicable.
248  // * Lists are encoded by first encoding their length in the expanding format,
249  //   then follows the recursive encoding of each element value, including the
250  //   type byte (Lists are assumed to be heterogeneous).
251  // * Maps are encoded by first encoding their length in the expanding format,
252  //   then follows the recursive encoding of each key/value pair, including the
253  //   type byte for both (Maps are assumed to be heterogeneous).
254  static const int _valueNull = 0;
255  static const int _valueTrue = 1;
256  static const int _valueFalse = 2;
257  static const int _valueInt32 = 3;
258  static const int _valueInt64 = 4;
259  static const int _valueLargeInt = 5;
260  static const int _valueFloat64 = 6;
261  static const int _valueString = 7;
262  static const int _valueUint8List = 8;
263  static const int _valueInt32List = 9;
264  static const int _valueInt64List = 10;
265  static const int _valueFloat64List = 11;
266  static const int _valueList = 12;
267  static const int _valueMap = 13;
268
269  /// Creates a [MessageCodec] using the Flutter standard binary encoding.
270  const StandardMessageCodec();
271
272  @override
273  ByteData encodeMessage(dynamic message) {
274    if (message == null) return null;
275    final WriteBuffer buffer = WriteBuffer();
276    writeValue(buffer, message);
277    return buffer.done();
278  }
279
280  @override
281  dynamic decodeMessage(ByteData message) {
282    if (message == null) return null;
283    final ReadBuffer buffer = ReadBuffer(message);
284    final dynamic result = readValue(buffer);
285    if (buffer.hasRemaining) throw const FormatException('Message corrupted');
286    return result;
287  }
288
289  /// Writes [value] to [buffer] by first writing a type discriminator
290  /// byte, then the value itself.
291  ///
292  /// This method may be called recursively to serialize container values.
293  ///
294  /// Type discriminators 0 through 127 inclusive are reserved for use by the
295  /// base class.
296  ///
297  /// The codec can be extended by overriding this method, calling super
298  /// for values that the extension does not handle. Type discriminators
299  /// used by extensions must be greater than or equal to 128 in order to avoid
300  /// clashes with any later extensions to the base class.
301  void writeValue(WriteBuffer buffer, dynamic value) {
302    if (value == null) {
303      buffer.putUint8(_valueNull);
304    } else if (value is bool) {
305      buffer.putUint8(value ? _valueTrue : _valueFalse);
306      // TODO(flutter_web): upstream double/int if/else swap.
307    } else if (value is double) {
308      buffer.putUint8(_valueFloat64);
309      buffer.putFloat64(value);
310    } else if (value is int) {
311      if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
312        buffer.putUint8(_valueInt32);
313        buffer.putInt32(value);
314      } else {
315        buffer.putUint8(_valueInt64);
316        buffer.putInt64(value);
317      }
318    } else if (value is String) {
319      buffer.putUint8(_valueString);
320      final List<int> bytes = utf8.encoder.convert(value);
321      writeSize(buffer, bytes.length);
322      buffer.putUint8List(bytes);
323    } else if (value is Uint8List) {
324      buffer.putUint8(_valueUint8List);
325      writeSize(buffer, value.length);
326      buffer.putUint8List(value);
327    } else if (value is Int32List) {
328      buffer.putUint8(_valueInt32List);
329      writeSize(buffer, value.length);
330      buffer.putInt32List(value);
331    } else if (value is Int64List) {
332      buffer.putUint8(_valueInt64List);
333      writeSize(buffer, value.length);
334      buffer.putInt64List(value);
335    } else if (value is Float64List) {
336      buffer.putUint8(_valueFloat64List);
337      writeSize(buffer, value.length);
338      buffer.putFloat64List(value);
339    } else if (value is List) {
340      buffer.putUint8(_valueList);
341      writeSize(buffer, value.length);
342      for (final dynamic item in value) {
343        writeValue(buffer, item);
344      }
345    } else if (value is Map) {
346      buffer.putUint8(_valueMap);
347      writeSize(buffer, value.length);
348      value.forEach((dynamic key, dynamic value) {
349        writeValue(buffer, key);
350        writeValue(buffer, value);
351      });
352    } else {
353      throw new ArgumentError.value(value);
354    }
355  }
356
357  /// Reads a value from [buffer] as written by [writeValue].
358  ///
359  /// This method is intended for use by subclasses overriding
360  /// [readValueOfType].
361  dynamic readValue(ReadBuffer buffer) {
362    if (!buffer.hasRemaining) throw const FormatException('Message corrupted');
363    final int type = buffer.getUint8();
364    return readValueOfType(type, buffer);
365  }
366
367  /// Reads a value of the indicated [type] from [buffer].
368  ///
369  /// The codec can be extended by overriding this method, calling super
370  /// for types that the extension does not handle.
371  dynamic readValueOfType(int type, ReadBuffer buffer) {
372    dynamic result;
373    switch (type) {
374      case _valueNull:
375        result = null;
376        break;
377      case _valueTrue:
378        result = true;
379        break;
380      case _valueFalse:
381        result = false;
382        break;
383      case _valueInt32:
384        result = buffer.getInt32();
385        break;
386      case _valueInt64:
387        result = buffer.getInt64();
388        break;
389      case _valueLargeInt:
390        // Flutter Engine APIs to use large ints have been deprecated on
391        // 2018-01-09 and will be made unavailable.
392        // TODO(mravn): remove this case once the APIs are unavailable.
393        final int length = readSize(buffer);
394        final String hex = utf8.decoder.convert(buffer.getUint8List(length));
395        result = int.parse(hex, radix: 16);
396        break;
397      case _valueFloat64:
398        result = buffer.getFloat64();
399        break;
400      case _valueString:
401        final int length = readSize(buffer);
402        result = utf8.decoder.convert(buffer.getUint8List(length));
403        break;
404      case _valueUint8List:
405        final int length = readSize(buffer);
406        result = buffer.getUint8List(length);
407        break;
408      case _valueInt32List:
409        final int length = readSize(buffer);
410        result = buffer.getInt32List(length);
411        break;
412      case _valueInt64List:
413        final int length = readSize(buffer);
414        result = buffer.getInt64List(length);
415        break;
416      case _valueFloat64List:
417        final int length = readSize(buffer);
418        result = buffer.getFloat64List(length);
419        break;
420      case _valueList:
421        final int length = readSize(buffer);
422        result = List<dynamic>(length);
423        for (int i = 0; i < length; i++) {
424          result[i] = readValue(buffer);
425        }
426        break;
427      case _valueMap:
428        final int length = readSize(buffer);
429        result = <dynamic, dynamic>{};
430        for (int i = 0; i < length; i++) {
431          result[readValue(buffer)] = readValue(buffer);
432        }
433        break;
434      default:
435        throw const FormatException('Message corrupted');
436    }
437    return result;
438  }
439
440  /// Writes a non-negative 32-bit integer [value] to [buffer]
441  /// using an expanding 1-5 byte encoding that optimizes for small values.
442  ///
443  /// This method is intended for use by subclasses overriding
444  /// [writeValue].
445  void writeSize(WriteBuffer buffer, int value) {
446    assert(0 <= value && value <= 0xffffffff);
447    if (value < 254) {
448      buffer.putUint8(value);
449    } else if (value <= 0xffff) {
450      buffer.putUint8(254);
451      buffer.putUint16(value);
452    } else {
453      buffer.putUint8(255);
454      buffer.putUint32(value);
455    }
456  }
457
458  /// Reads a non-negative int from [buffer] as written by [writeSize].
459  ///
460  /// This method is intended for use by subclasses overriding
461  /// [readValueOfType].
462  int readSize(ReadBuffer buffer) {
463    final int value = buffer.getUint8();
464    switch (value) {
465      case 254:
466        return buffer.getUint16();
467      case 255:
468        return buffer.getUint32();
469      default:
470        return value;
471    }
472  }
473}
474
475/// [MethodCodec] using the Flutter standard binary encoding.
476///
477/// The standard codec is guaranteed to be compatible with the corresponding
478/// standard codec for FlutterMethodChannels on the host platform. These parts
479/// of the Flutter SDK are evolved synchronously.
480///
481/// Values supported as method arguments and result payloads are those supported
482/// by [StandardMessageCodec].
483class StandardMethodCodec implements MethodCodec {
484  // The codec method calls, and result envelopes as outlined below. This format
485  // must match the Android and iOS counterparts.
486  //
487  // * Individual values are encoded using [StandardMessageCodec].
488  // * Method calls are encoded using the concatenation of the encoding
489  //   of the method name String and the arguments value.
490  // * Reply envelopes are encoded using first a single byte to distinguish the
491  //   success case (0) from the error case (1). Then follows:
492  //   * In the success case, the encoding of the result value.
493  //   * In the error case, the concatenation of the encoding of the error code
494  //     string, the error message string, and the error details value.
495
496  /// Creates a [MethodCodec] using the Flutter standard binary encoding.
497  const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]);
498
499  /// The message codec that this method codec uses for encoding values.
500  final StandardMessageCodec messageCodec;
501
502  @override
503  ByteData encodeMethodCall(MethodCall call) {
504    final WriteBuffer buffer = WriteBuffer();
505    messageCodec.writeValue(buffer, call.method);
506    messageCodec.writeValue(buffer, call.arguments);
507    return buffer.done();
508  }
509
510  @override
511  MethodCall decodeMethodCall(ByteData methodCall) {
512    final ReadBuffer buffer = ReadBuffer(methodCall);
513    final dynamic method = messageCodec.readValue(buffer);
514    final dynamic arguments = messageCodec.readValue(buffer);
515    if (method is String && !buffer.hasRemaining)
516      return MethodCall(method, arguments);
517    else
518      throw const FormatException('Invalid method call');
519  }
520
521  @override
522  ByteData encodeSuccessEnvelope(dynamic result) {
523    final WriteBuffer buffer = WriteBuffer();
524    buffer.putUint8(0);
525    messageCodec.writeValue(buffer, result);
526    return buffer.done();
527  }
528
529  @override
530  ByteData encodeErrorEnvelope(
531      {@required String code, String message, dynamic details}) {
532    final WriteBuffer buffer = WriteBuffer();
533    buffer.putUint8(1);
534    messageCodec.writeValue(buffer, code);
535    messageCodec.writeValue(buffer, message);
536    messageCodec.writeValue(buffer, details);
537    return buffer.done();
538  }
539
540  @override
541  dynamic decodeEnvelope(ByteData envelope) {
542    // First byte is zero in success case, and non-zero otherwise.
543    if (envelope.lengthInBytes == 0)
544      throw const FormatException('Expected envelope, got nothing');
545    final ReadBuffer buffer = ReadBuffer(envelope);
546    if (buffer.getUint8() == 0) return messageCodec.readValue(buffer);
547    final dynamic errorCode = messageCodec.readValue(buffer);
548    final dynamic errorMessage = messageCodec.readValue(buffer);
549    final dynamic errorDetails = messageCodec.readValue(buffer);
550    if (errorCode is String &&
551        (errorMessage == null || errorMessage is String) &&
552        !buffer.hasRemaining)
553      throw PlatformException(
554          code: errorCode, message: errorMessage, details: errorDetails);
555    else
556      throw const FormatException('Invalid envelope');
557  }
558}
559