| // 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. |
| |
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" |
| |
| #include "gtest/gtest.h" |
| |
| FLUTTER_ASSERT_ARC |
| |
| @interface Pair : NSObject |
| @property(atomic, readonly, strong, nullable) NSObject* left; |
| @property(atomic, readonly, strong, nullable) NSObject* right; |
| - (instancetype)initWithLeft:(NSObject*)first right:(NSObject*)right; |
| @end |
| |
| @implementation Pair |
| - (instancetype)initWithLeft:(NSObject*)left right:(NSObject*)right { |
| self = [super init]; |
| if (self) { |
| _left = left; |
| _right = right; |
| } |
| return self; |
| } |
| @end |
| |
| static const UInt8 kDATE = 128; |
| static const UInt8 kPAIR = 129; |
| |
| @interface ExtendedWriter : FlutterStandardWriter |
| - (void)writeValue:(id)value; |
| @end |
| |
| @implementation ExtendedWriter |
| - (void)writeValue:(id)value { |
| if ([value isKindOfClass:[NSDate class]]) { |
| [self writeByte:kDATE]; |
| NSDate* date = value; |
| NSTimeInterval time = date.timeIntervalSince1970; |
| SInt64 ms = (SInt64)(time * 1000.0); |
| [self writeBytes:&ms length:8]; |
| } else if ([value isKindOfClass:[Pair class]]) { |
| Pair* pair = value; |
| [self writeByte:kPAIR]; |
| [self writeValue:pair.left]; |
| [self writeValue:pair.right]; |
| } else { |
| [super writeValue:value]; |
| } |
| } |
| @end |
| |
| @interface ExtendedReader : FlutterStandardReader |
| - (id)readValueOfType:(UInt8)type; |
| @end |
| |
| @implementation ExtendedReader |
| - (id)readValueOfType:(UInt8)type { |
| switch (type) { |
| case kDATE: { |
| SInt64 value; |
| [self readBytes:&value length:8]; |
| NSTimeInterval time = [NSNumber numberWithLong:value].doubleValue / 1000.0; |
| return [NSDate dateWithTimeIntervalSince1970:time]; |
| } |
| case kPAIR: { |
| return [[Pair alloc] initWithLeft:[self readValue] right:[self readValue]]; |
| } |
| default: |
| return [super readValueOfType:type]; |
| } |
| } |
| @end |
| |
| @interface ExtendedReaderWriter : FlutterStandardReaderWriter |
| - (FlutterStandardWriter*)writerWithData:(NSMutableData*)data; |
| - (FlutterStandardReader*)readerWithData:(NSData*)data; |
| @end |
| |
| @implementation ExtendedReaderWriter |
| - (FlutterStandardWriter*)writerWithData:(NSMutableData*)data { |
| return [[ExtendedWriter alloc] initWithData:data]; |
| } |
| - (FlutterStandardReader*)readerWithData:(NSData*)data { |
| return [[ExtendedReader alloc] initWithData:data]; |
| } |
| @end |
| |
| static void CheckEncodeDecode(id value, NSData* expectedEncoding) { |
| FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; |
| NSData* encoded = [codec encode:value]; |
| if (expectedEncoding == nil) { |
| ASSERT_TRUE(encoded == nil); |
| } else { |
| ASSERT_TRUE([encoded isEqual:expectedEncoding]); |
| } |
| id decoded = [codec decode:encoded]; |
| if (value == nil || value == [NSNull null]) { |
| ASSERT_TRUE(decoded == nil); |
| } else { |
| ASSERT_TRUE([value isEqual:decoded]); |
| } |
| } |
| |
| static void CheckEncodeDecode(id value) { |
| FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; |
| NSData* encoded = [codec encode:value]; |
| id decoded = [codec decode:encoded]; |
| if (value == nil || value == [NSNull null]) { |
| ASSERT_TRUE(decoded == nil); |
| } else { |
| ASSERT_TRUE([value isEqual:decoded]); |
| } |
| } |
| |
| TEST(FlutterStandardCodec, CanDecodeZeroLength) { |
| FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; |
| id decoded = [codec decode:[NSData data]]; |
| ASSERT_TRUE(decoded == nil); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeNil) { |
| CheckEncodeDecode(nil, nil); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeNSNull) { |
| uint8_t bytes[1] = {0x00}; |
| CheckEncodeDecode([NSNull null], [NSData dataWithBytes:bytes length:1]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeYes) { |
| uint8_t bytes[1] = {0x01}; |
| CheckEncodeDecode(@YES, [NSData dataWithBytes:bytes length:1]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeNo) { |
| uint8_t bytes[1] = {0x02}; |
| CheckEncodeDecode(@NO, [NSData dataWithBytes:bytes length:1]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeUInt8) { |
| uint8_t bytes[5] = {0x03, 0xfe, 0x00, 0x00, 0x00}; |
| UInt8 value = 0xfe; |
| CheckEncodeDecode(@(value), [NSData dataWithBytes:bytes length:5]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeUInt16) { |
| uint8_t bytes[5] = {0x03, 0xdc, 0xfe, 0x00, 0x00}; |
| UInt16 value = 0xfedc; |
| CheckEncodeDecode(@(value), [NSData dataWithBytes:bytes length:5]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeUInt32) { |
| uint8_t bytes[9] = {0x04, 0x09, 0xba, 0xdc, 0xfe, 0x00, 0x00, 0x00, 0x00}; |
| UInt32 value = 0xfedcba09; |
| CheckEncodeDecode(@(value), [NSData dataWithBytes:bytes length:9]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeUInt64) { |
| FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; |
| UInt64 u64 = 0xfffffffffffffffa; |
| uint8_t bytes[9] = {0x04, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| NSData* encoded = [codec encode:@(u64)]; |
| ASSERT_TRUE([encoded isEqual:[NSData dataWithBytes:bytes length:9]]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeSInt8) { |
| uint8_t bytes[5] = {0x03, 0xfe, 0xff, 0xff, 0xff}; |
| SInt8 value = 0xfe; |
| CheckEncodeDecode(@(value), [NSData dataWithBytes:bytes length:5]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeSInt16) { |
| uint8_t bytes[5] = {0x03, 0xdc, 0xfe, 0xff, 0xff}; |
| SInt16 value = 0xfedc; |
| CheckEncodeDecode(@(value), [NSData dataWithBytes:bytes length:5]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeSInt32) { |
| uint8_t bytes[5] = {0x03, 0x78, 0x56, 0x34, 0x12}; |
| CheckEncodeDecode(@(0x12345678), [NSData dataWithBytes:bytes length:5]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeSInt64) { |
| uint8_t bytes[9] = {0x04, 0xef, 0xcd, 0xab, 0x90, 0x78, 0x56, 0x34, 0x12}; |
| CheckEncodeDecode(@(0x1234567890abcdef), [NSData dataWithBytes:bytes length:9]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeFloat32) { |
| uint8_t bytes[16] = {0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x60, 0xfb, 0x21, 0x09, 0x40}; |
| CheckEncodeDecode(@3.1415927f, [NSData dataWithBytes:bytes length:16]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeFloat64) { |
| uint8_t bytes[16] = {0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}; |
| CheckEncodeDecode(@3.14159265358979311599796346854, [NSData dataWithBytes:bytes length:16]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeString) { |
| uint8_t bytes[13] = {0x07, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, |
| 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}; |
| CheckEncodeDecode(@"hello world", [NSData dataWithBytes:bytes length:13]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeStringWithNonAsciiCodePoint) { |
| uint8_t bytes[7] = {0x07, 0x05, 0x68, 0xe2, 0x98, 0xba, 0x77}; |
| CheckEncodeDecode(@"h\u263Aw", [NSData dataWithBytes:bytes length:7]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeStringWithNonBMPCodePoint) { |
| uint8_t bytes[8] = {0x07, 0x06, 0x68, 0xf0, 0x9f, 0x98, 0x82, 0x77}; |
| CheckEncodeDecode(@"h\U0001F602w", [NSData dataWithBytes:bytes length:8]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeArray) { |
| NSArray* value = @[ [NSNull null], @"hello", @3.14, @47, @{@42 : @"nested"} ]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeDictionary) { |
| NSDictionary* value = |
| @{@"a" : @3.14, |
| @"b" : @47, |
| [NSNull null] : [NSNull null], |
| @3.14 : @[ @"nested" ]}; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeByteArray) { |
| uint8_t bytes[4] = {0xBA, 0x5E, 0xBA, 0x11}; |
| NSData* data = [NSData dataWithBytes:bytes length:4]; |
| FlutterStandardTypedData* value = [FlutterStandardTypedData typedDataWithBytes:data]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeNSData) { |
| FlutterStandardMessageCodec* codec = [FlutterStandardMessageCodec sharedInstance]; |
| uint8_t bytes[4] = {0xBA, 0x5E, 0xBA, 0x11}; |
| NSData* data = [NSData dataWithBytes:bytes length:4]; |
| FlutterStandardTypedData* standardData = [FlutterStandardTypedData typedDataWithBytes:data]; |
| |
| NSData* encoded = [codec encode:data]; |
| ASSERT_TRUE([encoded isEqual:[codec encode:standardData]]); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeInt32Array) { |
| uint8_t bytes[8] = {0xBA, 0x5E, 0xBA, 0x11, 0xff, 0xff, 0xff, 0xff}; |
| NSData* data = [NSData dataWithBytes:bytes length:8]; |
| FlutterStandardTypedData* value = [FlutterStandardTypedData typedDataWithInt32:data]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeInt64Array) { |
| uint8_t bytes[8] = {0xBA, 0x5E, 0xBA, 0x11, 0xff, 0xff, 0xff, 0xff}; |
| NSData* data = [NSData dataWithBytes:bytes length:8]; |
| FlutterStandardTypedData* value = [FlutterStandardTypedData typedDataWithInt64:data]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeFloat32Array) { |
| uint8_t bytes[8] = {0xd8, 0x0f, 0x49, 0x40, 0x00, 0x00, 0x7a, 0x44}; |
| NSData* data = [NSData dataWithBytes:bytes length:8]; |
| FlutterStandardTypedData* value = [FlutterStandardTypedData typedDataWithFloat32:data]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, CanEncodeAndDecodeFloat64Array) { |
| uint8_t bytes[16] = {0xBA, 0x5E, 0xBA, 0x11, 0xff, 0xff, 0xff, 0xff, |
| 0xBA, 0x5E, 0xBA, 0x11, 0xff, 0xff, 0xff, 0xff}; |
| NSData* data = [NSData dataWithBytes:bytes length:16]; |
| FlutterStandardTypedData* value = [FlutterStandardTypedData typedDataWithFloat64:data]; |
| CheckEncodeDecode(value); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesMethodCallsWithNilArguments) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| FlutterMethodCall* call = [FlutterMethodCall methodCallWithMethodName:@"hello" arguments:nil]; |
| NSData* encoded = [codec encodeMethodCall:call]; |
| FlutterMethodCall* decoded = [codec decodeMethodCall:encoded]; |
| ASSERT_TRUE([decoded isEqual:call]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesMethodCallsWithSingleArgument) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| FlutterMethodCall* call = [FlutterMethodCall methodCallWithMethodName:@"hello" arguments:@42]; |
| NSData* encoded = [codec encodeMethodCall:call]; |
| FlutterMethodCall* decoded = [codec decodeMethodCall:encoded]; |
| ASSERT_TRUE([decoded isEqual:call]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesMethodCallsWithArgumentList) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| NSArray* arguments = @[ @42, @"world" ]; |
| FlutterMethodCall* call = [FlutterMethodCall methodCallWithMethodName:@"hello" |
| arguments:arguments]; |
| NSData* encoded = [codec encodeMethodCall:call]; |
| FlutterMethodCall* decoded = [codec decodeMethodCall:encoded]; |
| ASSERT_TRUE([decoded isEqual:call]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithNilResult) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| NSData* encoded = [codec encodeSuccessEnvelope:nil]; |
| id decoded = [codec decodeEnvelope:encoded]; |
| ASSERT_TRUE(decoded == nil); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithSingleResult) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| NSData* encoded = [codec encodeSuccessEnvelope:@42]; |
| id decoded = [codec decodeEnvelope:encoded]; |
| ASSERT_TRUE([decoded isEqual:@42]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesSuccessEnvelopesWithResultMap) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| NSDictionary* result = @{@"a" : @42, @42 : @"a"}; |
| NSData* encoded = [codec encodeSuccessEnvelope:result]; |
| id decoded = [codec decodeEnvelope:encoded]; |
| ASSERT_TRUE([decoded isEqual:result]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesErrorEnvelopes) { |
| FlutterStandardMethodCodec* codec = [FlutterStandardMethodCodec sharedInstance]; |
| NSDictionary* details = @{@"a" : @42, @42 : @"a"}; |
| FlutterError* error = [FlutterError errorWithCode:@"errorCode" |
| message:@"something failed" |
| details:details]; |
| NSData* encoded = [codec encodeErrorEnvelope:error]; |
| id decoded = [codec decodeEnvelope:encoded]; |
| ASSERT_TRUE([decoded isEqual:error]); |
| } |
| |
| TEST(FlutterStandardCodec, HandlesSubclasses) { |
| ExtendedReaderWriter* extendedReaderWriter = [[ExtendedReaderWriter alloc] init]; |
| FlutterStandardMessageCodec* codec = |
| [FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]; |
| Pair* pair = [[Pair alloc] initWithLeft:@1 right:@2]; |
| NSData* encoded = [codec encode:pair]; |
| Pair* decoded = [codec decode:encoded]; |
| ASSERT_TRUE([pair.left isEqual:decoded.left]); |
| ASSERT_TRUE([pair.right isEqual:decoded.right]); |
| } |