1 | // Copyright 2014 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 | import 'dart:convert'; |
6 | import 'dart:typed_data'; |
7 | |
8 | import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; |
9 | |
10 | import 'message_codec.dart'; |
11 | |
12 | export 'dart:typed_data' show ByteData; |
13 | |
14 | export 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; |
15 | |
16 | export 'message_codec.dart' show MethodCall; |
17 | |
18 | const int _writeBufferStartCapacity = 64; |
19 | |
20 | /// [MessageCodec] with unencoded binary messages represented using [ByteData]. |
21 | /// |
22 | /// On Android, messages will be represented using `java.nio.ByteBuffer`. |
23 | /// On iOS, messages will be represented using `NSData`. |
24 | /// |
25 | /// When sending outgoing messages from Android, be sure to use direct `ByteBuffer` |
26 | /// as opposed to indirect. The `wrap()` API provides indirect buffers by default |
27 | /// and you will get empty `ByteData` objects in Dart. |
28 | class BinaryCodec implements MessageCodec<ByteData> { |
29 | /// Creates a [MessageCodec] with unencoded binary messages represented using |
30 | /// [ByteData]. |
31 | const BinaryCodec(); |
32 | |
33 | @override |
34 | ByteData? decodeMessage(ByteData? message) => message; |
35 | |
36 | @override |
37 | ByteData? encodeMessage(ByteData? message) => message; |
38 | } |
39 | |
40 | /// [MessageCodec] with UTF-8 encoded String messages. |
41 | /// |
42 | /// On Android, messages will be represented using `java.util.String`. |
43 | /// On iOS, messages will be represented using `NSString`. |
44 | class StringCodec implements MessageCodec<String> { |
45 | /// Creates a [MessageCodec] with UTF-8 encoded String messages. |
46 | const StringCodec(); |
47 | |
48 | @override |
49 | String? decodeMessage(ByteData? message) { |
50 | if (message == null) { |
51 | return null; |
52 | } |
53 | return utf8.decode(Uint8List.sublistView(message)); |
54 | } |
55 | |
56 | @override |
57 | ByteData? encodeMessage(String? message) { |
58 | if (message == null) { |
59 | return null; |
60 | } |
61 | return ByteData.sublistView(utf8.encode(message)); |
62 | } |
63 | } |
64 | |
65 | /// [MessageCodec] with UTF-8 encoded JSON messages. |
66 | /// |
67 | /// Supported messages are acyclic values of these forms: |
68 | /// |
69 | /// * null |
70 | /// * [bool]s |
71 | /// * [num]s |
72 | /// * [String]s |
73 | /// * [List]s of supported values |
74 | /// * [Map]s from strings to supported values |
75 | /// |
76 | /// On Android, messages are decoded using the `org.json` library. |
77 | /// On iOS, messages are decoded using the `NSJSONSerialization` library. |
78 | /// In both cases, the use of top-level simple messages (null, [bool], [num], |
79 | /// and [String]) is supported (by the Flutter SDK). The decoded value will be |
80 | /// null/nil for null, and identical to what would result from decoding a |
81 | /// singleton JSON array with a Boolean, number, or string value, and then |
82 | /// extracting its single element. |
83 | /// |
84 | /// The type returned from [decodeMessage] is `dynamic` (not `Object?`), which |
85 | /// means *no type checking is performed on its return value*. It is strongly |
86 | /// recommended that the return value be immediately cast to a known type to |
87 | /// prevent runtime errors due to typos that the type checker could otherwise |
88 | /// catch. |
89 | class JSONMessageCodec implements MessageCodec<Object?> { |
90 | // The codec serializes messages as defined by the JSON codec of the |
91 | // dart:convert package. The format used must match the Android and |
92 | // iOS counterparts. |
93 | |
94 | /// Creates a [MessageCodec] with UTF-8 encoded JSON messages. |
95 | const JSONMessageCodec(); |
96 | |
97 | @override |
98 | ByteData? encodeMessage(Object? message) { |
99 | if (message == null) { |
100 | return null; |
101 | } |
102 | return const StringCodec().encodeMessage(json.encode(message)); |
103 | } |
104 | |
105 | @override |
106 | dynamic decodeMessage(ByteData? message) { |
107 | if (message == null) { |
108 | return message; |
109 | } |
110 | return json.decode(const StringCodec().decodeMessage(message)!); |
111 | } |
112 | } |
113 | |
114 | /// [MethodCodec] with UTF-8 encoded JSON method calls and result envelopes. |
115 | /// |
116 | /// Values supported as method arguments and result payloads are those supported |
117 | /// by [JSONMessageCodec]. |
118 | class JSONMethodCodec implements MethodCodec { |
119 | // The codec serializes method calls, and result envelopes as outlined below. |
120 | // This format must match the Android and iOS counterparts. |
121 | // |
122 | // * Individual values are serialized as defined by the JSON codec of the |
123 | // dart:convert package. |
124 | // * Method calls are serialized as two-element maps, with the method name |
125 | // keyed by 'method' and the arguments keyed by 'args'. |
126 | // * Reply envelopes are serialized as either: |
127 | // * one-element lists containing the successful result as its single |
128 | // element, or |
129 | // * three-element lists containing, in order, an error code String, an |
130 | // error message String, and an error details value. |
131 | |
132 | /// Creates a [MethodCodec] with UTF-8 encoded JSON method calls and result |
133 | /// envelopes. |
134 | const JSONMethodCodec(); |
135 | |
136 | @override |
137 | ByteData encodeMethodCall(MethodCall methodCall) { |
138 | return const JSONMessageCodec().encodeMessage(<String, Object?>{ |
139 | 'method': methodCall.method, |
140 | 'args': methodCall.arguments, |
141 | })!; |
142 | } |
143 | |
144 | @override |
145 | MethodCall decodeMethodCall(ByteData? methodCall) { |
146 | final Object? decoded = const JSONMessageCodec().decodeMessage(methodCall); |
147 | if (decoded is! Map) { |
148 | throw FormatException('Expected method call Map, got$decoded '); |
149 | } |
150 | if (decoded case {'method': final String method}) { |
151 | return MethodCall(method, decoded['args']); |
152 | } |
153 | throw FormatException('Invalid method call:$decoded '); |
154 | } |
155 | |
156 | @override |
157 | dynamic decodeEnvelope(ByteData envelope) { |
158 | final Object? decoded = const JSONMessageCodec().decodeMessage(envelope); |
159 | if (decoded is! List) { |
160 | throw FormatException('Expected envelope List, got$decoded '); |
161 | } |
162 | if (decoded.length == 1) { |
163 | return decoded[0]; |
164 | } |
165 | if (decoded.length == 3 && |
166 | decoded[0] is String && |
167 | (decoded[1] == null || decoded[1] is String)) { |
168 | throw PlatformException( |
169 | code: decoded[0] as String, |
170 | message: decoded[1] as String?, |
171 | details: decoded[2], |
172 | ); |
173 | } |
174 | if (decoded.length == 4 && |
175 | decoded[0] is String && |
176 | (decoded[1] == null || decoded[1] is String) && |
177 | (decoded[3] == null || decoded[3] is String)) { |
178 | throw PlatformException( |
179 | code: decoded[0] as String, |
180 | message: decoded[1] as String?, |
181 | details: decoded[2], |
182 | stacktrace: decoded[3] as String?, |
183 | ); |
184 | } |
185 | throw FormatException('Invalid envelope:$decoded '); |
186 | } |
187 | |
188 | @override |
189 | ByteData encodeSuccessEnvelope(Object? result) { |
190 | return const JSONMessageCodec().encodeMessage(<Object?>[result])!; |
191 | } |
192 | |
193 | @override |
194 | ByteData encodeErrorEnvelope({required String code, String? message, Object? details}) { |
195 | return const JSONMessageCodec().encodeMessage(<Object?>[code, message, details])!; |
196 | } |
197 | } |
198 | |
199 | /// [MessageCodec] using the Flutter standard binary encoding. |
200 | /// |
201 | /// Supported messages are acyclic values of these forms: |
202 | /// |
203 | /// * null |
204 | /// * [bool]s |
205 | /// * [num]s |
206 | /// * [String]s |
207 | /// * [Uint8List]s, [Int32List]s, [Int64List]s, [Float64List]s |
208 | /// * [List]s of supported values |
209 | /// * [Map]s from supported values to supported values |
210 | /// |
211 | /// Decoded values will use `List<Object?>` and `Map<Object?, Object?>` |
212 | /// irrespective of content. |
213 | /// |
214 | /// The type returned from [decodeMessage] is `dynamic` (not `Object?`), which |
215 | /// means *no type checking is performed on its return value*. It is strongly |
216 | /// recommended that the return value be immediately cast to a known type to |
217 | /// prevent runtime errors due to typos that the type checker could otherwise |
218 | /// catch. |
219 | /// |
220 | /// The codec is extensible by subclasses overriding [writeValue] and |
221 | /// [readValueOfType]. |
222 | /// |
223 | /// ## Android specifics |
224 | /// |
225 | /// On Android, messages are represented as follows: |
226 | /// |
227 | /// * null: null |
228 | /// * [bool]\: `java.lang.Boolean` |
229 | /// * [int]\: `java.lang.Integer` for values that are representable using 32-bit |
230 | /// two's complement; `java.lang.Long` otherwise |
231 | /// * [double]\: `java.lang.Double` |
232 | /// * [String]\: `java.lang.String` |
233 | /// * [Uint8List]\: `byte[]` |
234 | /// * [Int32List]\: `int[]` |
235 | /// * [Int64List]\: `long[]` |
236 | /// * [Float64List]\: `double[]` |
237 | /// * [List]\: `java.util.ArrayList` |
238 | /// * [Map]\: `java.util.HashMap` |
239 | /// |
240 | /// When sending a `java.math.BigInteger` from Java, it is converted into a |
241 | /// [String] with the hexadecimal representation of the integer. (The value is |
242 | /// tagged as being a big integer; subclasses of this class could be made to |
243 | /// support it natively; see the discussion at [writeValue].) This codec does |
244 | /// not support sending big integers from Dart. |
245 | /// |
246 | /// ## iOS specifics |
247 | /// |
248 | /// On iOS, messages are represented as follows: |
249 | /// |
250 | /// * null: nil |
251 | /// * [bool]\: `NSNumber numberWithBool:` |
252 | /// * [int]\: `NSNumber numberWithInt:` for values that are representable using |
253 | /// 32-bit two's complement; `NSNumber numberWithLong:` otherwise |
254 | /// * [double]\: `NSNumber numberWithDouble:` |
255 | /// * [String]\: `NSString` |
256 | /// * [Uint8List], [Int32List], [Int64List], [Float64List]\: |
257 | /// `FlutterStandardTypedData` |
258 | /// * [List]\: `NSArray` |
259 | /// * [Map]\: `NSDictionary` |
260 | class StandardMessageCodec implements MessageCodec<Object?> { |
261 | /// Creates a [MessageCodec] using the Flutter standard binary encoding. |
262 | const StandardMessageCodec(); |
263 | |
264 | // The codec serializes messages as outlined below. This format must match the |
265 | // Android and iOS counterparts and cannot change (as it's possible for |
266 | // someone to end up using this for persistent storage). |
267 | // |
268 | // * A single byte with one of the constant values below determines the |
269 | // type of the value. |
270 | // * The serialization of the value itself follows the type byte. |
271 | // * Numbers are represented using the host endianness throughout. |
272 | // * Lengths and sizes of serialized parts are encoded using an expanding |
273 | // format optimized for the common case of small non-negative integers: |
274 | // * values 0..253 inclusive using one byte with that value; |
275 | // * values 254..2^16 inclusive using three bytes, the first of which is |
276 | // 254, the next two the usual unsigned representation of the value; |
277 | // * values 2^16+1..2^32 inclusive using five bytes, the first of which is |
278 | // 255, the next four the usual unsigned representation of the value. |
279 | // * null, true, and false have empty serialization; they are encoded directly |
280 | // in the type byte (using _valueNull, _valueTrue, _valueFalse) |
281 | // * Integers representable in 32 bits are encoded using 4 bytes two's |
282 | // complement representation. |
283 | // * Larger integers are encoded using 8 bytes two's complement |
284 | // representation. |
285 | // * doubles are encoded using the IEEE 754 64-bit double-precision binary |
286 | // format. Zero bytes are added before the encoded double value to align it |
287 | // to a 64 bit boundary in the full message. |
288 | // * Strings are encoded using their UTF-8 representation. First the length |
289 | // of that in bytes is encoded using the expanding format, then follows the |
290 | // UTF-8 encoding itself. |
291 | // * Uint8Lists, Int32Lists, Int64Lists, Float32Lists, and Float64Lists are |
292 | // encoded by first encoding the list's element count in the expanding |
293 | // format, then the smallest number of zero bytes needed to align the |
294 | // position in the full message with a multiple of the number of bytes per |
295 | // element, then the encoding of the list elements themselves, end-to-end |
296 | // with no additional type information, using two's complement or IEEE 754 |
297 | // as applicable. |
298 | // * Lists are encoded by first encoding their length in the expanding format, |
299 | // then follows the recursive encoding of each element value, including the |
300 | // type byte (Lists are assumed to be heterogeneous). |
301 | // * Maps are encoded by first encoding their length in the expanding format, |
302 | // then follows the recursive encoding of each key/value pair, including the |
303 | // type byte for both (Maps are assumed to be heterogeneous). |
304 | // |
305 | // The type labels below must not change, since it's possible for this interface |
306 | // to be used for persistent storage. |
307 | static const int _valueNull = 0; |
308 | static const int _valueTrue = 1; |
309 | static const int _valueFalse = 2; |
310 | static const int _valueInt32 = 3; |
311 | static const int _valueInt64 = 4; |
312 | static const int _valueLargeInt = 5; |
313 | static const int _valueFloat64 = 6; |
314 | static const int _valueString = 7; |
315 | static const int _valueUint8List = 8; |
316 | static const int _valueInt32List = 9; |
317 | static const int _valueInt64List = 10; |
318 | static const int _valueFloat64List = 11; |
319 | static const int _valueList = 12; |
320 | static const int _valueMap = 13; |
321 | static const int _valueFloat32List = 14; |
322 | |
323 | @override |
324 | ByteData? encodeMessage(Object? message) { |
325 | if (message == null) { |
326 | return null; |
327 | } |
328 | final WriteBuffer buffer = WriteBuffer(startCapacity: _writeBufferStartCapacity); |
329 | writeValue(buffer, message); |
330 | return buffer.done(); |
331 | } |
332 | |
333 | @override |
334 | dynamic decodeMessage(ByteData? message) { |
335 | if (message == null) { |
336 | return null; |
337 | } |
338 | final ReadBuffer buffer = ReadBuffer(message); |
339 | final Object? result = readValue(buffer); |
340 | if (buffer.hasRemaining) { |
341 | throw const FormatException('Message corrupted'); |
342 | } |
343 | return result; |
344 | } |
345 | |
346 | /// Writes [value] to [buffer] by first writing a type discriminator |
347 | /// byte, then the value itself. |
348 | /// |
349 | /// This method may be called recursively to serialize container values. |
350 | /// |
351 | /// Type discriminators 0 through 127 inclusive are reserved for use by the |
352 | /// base class, as follows: |
353 | /// |
354 | /// * null = 0 |
355 | /// * true = 1 |
356 | /// * false = 2 |
357 | /// * 32 bit integer = 3 |
358 | /// * 64 bit integer = 4 |
359 | /// * larger integers = 5 (see below) |
360 | /// * 64 bit floating-point number = 6 |
361 | /// * String = 7 |
362 | /// * Uint8List = 8 |
363 | /// * Int32List = 9 |
364 | /// * Int64List = 10 |
365 | /// * Float64List = 11 |
366 | /// * List = 12 |
367 | /// * Map = 13 |
368 | /// * Float32List = 14 |
369 | /// * Reserved for future expansion: 15..127 |
370 | /// |
371 | /// The codec can be extended by overriding this method, calling super |
372 | /// for values that the extension does not handle. Type discriminators |
373 | /// used by extensions must be greater than or equal to 128 in order to avoid |
374 | /// clashes with any later extensions to the base class. |
375 | /// |
376 | /// The "larger integers" type, 5, is never used by [writeValue]. A subclass |
377 | /// could represent big integers from another package using that type. The |
378 | /// format is first the type byte (0x05), then the actual number as an ASCII |
379 | /// string giving the hexadecimal representation of the integer, with the |
380 | /// string's length as encoded by [writeSize] followed by the string bytes. On |
381 | /// Android, that would get converted to a `java.math.BigInteger` object. On |
382 | /// iOS, the string representation is returned. |
383 | void writeValue(WriteBuffer buffer, Object? value) { |
384 | if (value == null) { |
385 | buffer.putUint8(_valueNull); |
386 | } else if (value is bool) { |
387 | buffer.putUint8(value ? _valueTrue : _valueFalse); |
388 | } else if (value is double) { |
389 | // Double precedes int because in JS everything is a double. |
390 | // Therefore in JS, both `is int` and `is double` always |
391 | // return `true`. If we check int first, we'll end up treating |
392 | // all numbers as ints and attempt the int32/int64 conversion, |
393 | // which is wrong. This precedence rule is irrelevant when |
394 | // decoding because we use tags to detect the type of value. |
395 | buffer.putUint8(_valueFloat64); |
396 | buffer.putFloat64(value); |
397 | // ignore: avoid_double_and_int_checks, JS code always goes through the `double` path above |
398 | } else if (value is int) { |
399 | if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { |
400 | buffer.putUint8(_valueInt32); |
401 | buffer.putInt32(value); |
402 | } else { |
403 | buffer.putUint8(_valueInt64); |
404 | buffer.putInt64(value); |
405 | } |
406 | } else if (value is String) { |
407 | buffer.putUint8(_valueString); |
408 | final Uint8List asciiBytes = Uint8List(value.length); |
409 | Uint8List? utf8Bytes; |
410 | int utf8Offset = 0; |
411 | // Only do utf8 encoding if we encounter non-ascii characters. |
412 | for (int i = 0; i < value.length; i += 1) { |
413 | final int char = value.codeUnitAt(i); |
414 | if (char <= 0x7f) { |
415 | asciiBytes[i] = char; |
416 | } else { |
417 | utf8Bytes = utf8.encode(value.substring(i)); |
418 | utf8Offset = i; |
419 | break; |
420 | } |
421 | } |
422 | if (utf8Bytes != null) { |
423 | writeSize(buffer, utf8Offset + utf8Bytes.length); |
424 | buffer.putUint8List(Uint8List.sublistView(asciiBytes, 0, utf8Offset)); |
425 | buffer.putUint8List(utf8Bytes); |
426 | } else { |
427 | writeSize(buffer, asciiBytes.length); |
428 | buffer.putUint8List(asciiBytes); |
429 | } |
430 | } else if (value is Uint8List) { |
431 | buffer.putUint8(_valueUint8List); |
432 | writeSize(buffer, value.length); |
433 | buffer.putUint8List(value); |
434 | } else if (value is Int32List) { |
435 | buffer.putUint8(_valueInt32List); |
436 | writeSize(buffer, value.length); |
437 | buffer.putInt32List(value); |
438 | } else if (value is Int64List) { |
439 | buffer.putUint8(_valueInt64List); |
440 | writeSize(buffer, value.length); |
441 | buffer.putInt64List(value); |
442 | } else if (value is Float32List) { |
443 | buffer.putUint8(_valueFloat32List); |
444 | writeSize(buffer, value.length); |
445 | buffer.putFloat32List(value); |
446 | } else if (value is Float64List) { |
447 | buffer.putUint8(_valueFloat64List); |
448 | writeSize(buffer, value.length); |
449 | buffer.putFloat64List(value); |
450 | } else if (value is List) { |
451 | buffer.putUint8(_valueList); |
452 | writeSize(buffer, value.length); |
453 | for (final Object? item in value) { |
454 | writeValue(buffer, item); |
455 | } |
456 | } else if (value is Map) { |
457 | buffer.putUint8(_valueMap); |
458 | writeSize(buffer, value.length); |
459 | value.forEach((Object? key, Object? value) { |
460 | writeValue(buffer, key); |
461 | writeValue(buffer, value); |
462 | }); |
463 | } else { |
464 | throw ArgumentError.value(value); |
465 | } |
466 | } |
467 | |
468 | /// Reads a value from [buffer] as written by [writeValue]. |
469 | /// |
470 | /// This method is intended for use by subclasses overriding |
471 | /// [readValueOfType]. |
472 | Object? readValue(ReadBuffer buffer) { |
473 | if (!buffer.hasRemaining) { |
474 | throw const FormatException('Message corrupted'); |
475 | } |
476 | final int type = buffer.getUint8(); |
477 | return readValueOfType(type, buffer); |
478 | } |
479 | |
480 | /// Reads a value of the indicated [type] from [buffer]. |
481 | /// |
482 | /// The codec can be extended by overriding this method, calling super for |
483 | /// types that the extension does not handle. See the discussion at |
484 | /// [writeValue]. |
485 | Object? readValueOfType(int type, ReadBuffer buffer) { |
486 | switch (type) { |
487 | case _valueNull: |
488 | return null; |
489 | case _valueTrue: |
490 | return true; |
491 | case _valueFalse: |
492 | return false; |
493 | case _valueInt32: |
494 | return buffer.getInt32(); |
495 | case _valueInt64: |
496 | return buffer.getInt64(); |
497 | case _valueFloat64: |
498 | return buffer.getFloat64(); |
499 | case _valueLargeInt: |
500 | case _valueString: |
501 | final int length = readSize(buffer); |
502 | return utf8.decoder.convert(buffer.getUint8List(length)); |
503 | case _valueUint8List: |
504 | final int length = readSize(buffer); |
505 | return buffer.getUint8List(length); |
506 | case _valueInt32List: |
507 | final int length = readSize(buffer); |
508 | return buffer.getInt32List(length); |
509 | case _valueInt64List: |
510 | final int length = readSize(buffer); |
511 | return buffer.getInt64List(length); |
512 | case _valueFloat32List: |
513 | final int length = readSize(buffer); |
514 | return buffer.getFloat32List(length); |
515 | case _valueFloat64List: |
516 | final int length = readSize(buffer); |
517 | return buffer.getFloat64List(length); |
518 | case _valueList: |
519 | final int length = readSize(buffer); |
520 | final List<Object?> result = List<Object?>.filled(length, null); |
521 | for (int i = 0; i < length; i++) { |
522 | result[i] = readValue(buffer); |
523 | } |
524 | return result; |
525 | case _valueMap: |
526 | final int length = readSize(buffer); |
527 | final Map<Object?, Object?> result = <Object?, Object?>{}; |
528 | for (int i = 0; i < length; i++) { |
529 | result[readValue(buffer)] = readValue(buffer); |
530 | } |
531 | return result; |
532 | default: |
533 | throw const FormatException('Message corrupted'); |
534 | } |
535 | } |
536 | |
537 | /// Writes a non-negative 32-bit integer [value] to [buffer] |
538 | /// using an expanding 1-5 byte encoding that optimizes for small values. |
539 | /// |
540 | /// This method is intended for use by subclasses overriding |
541 | /// [writeValue]. |
542 | void writeSize(WriteBuffer buffer, int value) { |
543 | assert(0 <= value && value <= 0xffffffff); |
544 | if (value < 254) { |
545 | buffer.putUint8(value); |
546 | } else if (value <= 0xffff) { |
547 | buffer.putUint8(254); |
548 | buffer.putUint16(value); |
549 | } else { |
550 | buffer.putUint8(255); |
551 | buffer.putUint32(value); |
552 | } |
553 | } |
554 | |
555 | /// Reads a non-negative int from [buffer] as written by [writeSize]. |
556 | /// |
557 | /// This method is intended for use by subclasses overriding |
558 | /// [readValueOfType]. |
559 | int readSize(ReadBuffer buffer) { |
560 | final int value = buffer.getUint8(); |
561 | return switch (value) { |
562 | 254 => buffer.getUint16(), |
563 | 255 => buffer.getUint32(), |
564 | _ => value, |
565 | }; |
566 | } |
567 | } |
568 | |
569 | /// [MethodCodec] using the Flutter standard binary encoding. |
570 | /// |
571 | /// The standard codec is guaranteed to be compatible with the corresponding |
572 | /// standard codec for FlutterMethodChannels on the host platform. These parts |
573 | /// of the Flutter SDK are evolved synchronously. |
574 | /// |
575 | /// Values supported as method arguments and result payloads are those supported |
576 | /// by [StandardMessageCodec]. |
577 | class StandardMethodCodec implements MethodCodec { |
578 | // The codec method calls, and result envelopes as outlined below. This format |
579 | // must match the Android and iOS counterparts. |
580 | // |
581 | // * Individual values are encoded using [StandardMessageCodec]. |
582 | // * Method calls are encoded using the concatenation of the encoding |
583 | // of the method name String and the arguments value. |
584 | // * Reply envelopes are encoded using first a single byte to distinguish the |
585 | // success case (0) from the error case (1). Then follows: |
586 | // * In the success case, the encoding of the result value. |
587 | // * In the error case, the concatenation of the encoding of the error code |
588 | // string, the error message string, and the error details value. |
589 | |
590 | /// Creates a [MethodCodec] using the Flutter standard binary encoding. |
591 | const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]); |
592 | |
593 | /// The message codec that this method codec uses for encoding values. |
594 | final StandardMessageCodec messageCodec; |
595 | |
596 | @override |
597 | ByteData encodeMethodCall(MethodCall methodCall) { |
598 | final WriteBuffer buffer = WriteBuffer(startCapacity: _writeBufferStartCapacity); |
599 | messageCodec.writeValue(buffer, methodCall.method); |
600 | messageCodec.writeValue(buffer, methodCall.arguments); |
601 | return buffer.done(); |
602 | } |
603 | |
604 | @override |
605 | MethodCall decodeMethodCall(ByteData? methodCall) { |
606 | final ReadBuffer buffer = ReadBuffer(methodCall!); |
607 | final Object? method = messageCodec.readValue(buffer); |
608 | final Object? arguments = messageCodec.readValue(buffer); |
609 | if (method is String && !buffer.hasRemaining) { |
610 | return MethodCall(method, arguments); |
611 | } else { |
612 | throw const FormatException('Invalid method call'); |
613 | } |
614 | } |
615 | |
616 | @override |
617 | ByteData encodeSuccessEnvelope(Object? result) { |
618 | final WriteBuffer buffer = WriteBuffer(startCapacity: _writeBufferStartCapacity); |
619 | buffer.putUint8(0); |
620 | messageCodec.writeValue(buffer, result); |
621 | return buffer.done(); |
622 | } |
623 | |
624 | @override |
625 | ByteData encodeErrorEnvelope({required String code, String? message, Object? details}) { |
626 | final WriteBuffer buffer = WriteBuffer(startCapacity: _writeBufferStartCapacity); |
627 | buffer.putUint8(1); |
628 | messageCodec.writeValue(buffer, code); |
629 | messageCodec.writeValue(buffer, message); |
630 | messageCodec.writeValue(buffer, details); |
631 | return buffer.done(); |
632 | } |
633 | |
634 | @override |
635 | dynamic decodeEnvelope(ByteData envelope) { |
636 | // First byte is zero in success case, and non-zero otherwise. |
637 | if (envelope.lengthInBytes == 0) { |
638 | throw const FormatException('Expected envelope, got nothing'); |
639 | } |
640 | final ReadBuffer buffer = ReadBuffer(envelope); |
641 | if (buffer.getUint8() == 0) { |
642 | return messageCodec.readValue(buffer); |
643 | } |
644 | final Object? errorCode = messageCodec.readValue(buffer); |
645 | final Object? errorMessage = messageCodec.readValue(buffer); |
646 | final Object? errorDetails = messageCodec.readValue(buffer); |
647 | final String? errorStacktrace = |
648 | buffer.hasRemaining ? messageCodec.readValue(buffer) as String? : null; |
649 | if (errorCode is String && |
650 | (errorMessage == null || errorMessage is String) && |
651 | !buffer.hasRemaining) { |
652 | throw PlatformException( |
653 | code: errorCode, |
654 | message: errorMessage as String?, |
655 | details: errorDetails, |
656 | stacktrace: errorStacktrace, |
657 | ); |
658 | } else { |
659 | throw const FormatException('Invalid envelope'); |
660 | } |
661 | } |
662 | } |
663 |
Definitions
- _writeBufferStartCapacity
- BinaryCodec
- BinaryCodec
- decodeMessage
- encodeMessage
- StringCodec
- StringCodec
- decodeMessage
- encodeMessage
- JSONMessageCodec
- JSONMessageCodec
- encodeMessage
- decodeMessage
- JSONMethodCodec
- JSONMethodCodec
- encodeMethodCall
- decodeMethodCall
- decodeEnvelope
- encodeSuccessEnvelope
- encodeErrorEnvelope
- StandardMessageCodec
- StandardMessageCodec
- encodeMessage
- decodeMessage
- writeValue
- readValue
- readValueOfType
- writeSize
- readSize
- StandardMethodCodec
- StandardMethodCodec
- encodeMethodCall
- decodeMethodCall
- encodeSuccessEnvelope
- encodeErrorEnvelope
Learn more about Flutter for embedded and desktop on industrialflutter.com