diff --git a/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm b/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm index 373c73eba60fa..fc0fd0ca16707 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterStandardCodec.mm @@ -211,114 +211,144 @@ - (instancetype)initWithData:(NSMutableData*)data { } - (void)writeByte:(UInt8)value { - [_data appendBytes:&value length:1]; + FlutterStandardCodecHelperWriteByte((__bridge CFMutableDataRef)_data, value); } - (void)writeBytes:(const void*)bytes length:(NSUInteger)length { - [_data appendBytes:bytes length:length]; + FlutterStandardCodecHelperWriteBytes((__bridge CFMutableDataRef)_data, bytes, length); } - (void)writeData:(NSData*)data { - [_data appendData:data]; + FlutterStandardCodecHelperWriteData((__bridge CFMutableDataRef)_data, (__bridge CFDataRef)data); } - (void)writeSize:(UInt32)size { - if (size < 254) { - [self writeByte:(UInt8)size]; - } else if (size <= 0xffff) { - [self writeByte:254]; - UInt16 value = (UInt16)size; - [self writeBytes:&value length:2]; - } else { - [self writeByte:255]; - [self writeBytes:&size length:4]; - } + FlutterStandardCodecHelperWriteSize((__bridge CFMutableDataRef)_data, size); } - (void)writeAlignment:(UInt8)alignment { - UInt8 mod = _data.length % alignment; - if (mod) { - for (int i = 0; i < (alignment - mod); i++) { - [self writeByte:0]; - } - } + FlutterStandardCodecHelperWriteAlignment((__bridge CFMutableDataRef)_data, alignment); } - (void)writeUTF8:(NSString*)value { - UInt32 length = [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - [self writeSize:length]; - [self writeBytes:value.UTF8String length:length]; + FlutterStandardCodecHelperWriteUTF8((__bridge CFMutableDataRef)_data, + (__bridge CFStringRef)value); } -- (void)writeValue:(id)value { - if (value == nil || value == [NSNull null]) { - [self writeByte:FlutterStandardFieldNil]; +static FlutterStandardCodecObjcType GetWriteType(id value) { + if (value == nil || (__bridge CFNullRef)value == kCFNull) { + return FlutterStandardCodecObjcTypeNil; } else if ([value isKindOfClass:[NSNumber class]]) { - CFNumberRef number = (__bridge CFNumberRef)value; - BOOL success = NO; - if (CFGetTypeID(number) == CFBooleanGetTypeID()) { - BOOL b = CFBooleanGetValue((CFBooleanRef)number); - [self writeByte:(b ? FlutterStandardFieldTrue : FlutterStandardFieldFalse)]; - success = YES; - } else if (CFNumberIsFloatType(number)) { - Float64 f; - success = CFNumberGetValue(number, kCFNumberFloat64Type, &f); - if (success) { - [self writeByte:FlutterStandardFieldFloat64]; - [self writeAlignment:8]; - [self writeBytes:(UInt8*)&f length:8]; - } - } else if (CFNumberGetByteSize(number) <= 4) { - SInt32 n; - success = CFNumberGetValue(number, kCFNumberSInt32Type, &n); - if (success) { - [self writeByte:FlutterStandardFieldInt32]; - [self writeBytes:(UInt8*)&n length:4]; - } - } else if (CFNumberGetByteSize(number) <= 8) { - SInt64 n; - success = CFNumberGetValue(number, kCFNumberSInt64Type, &n); - if (success) { - [self writeByte:FlutterStandardFieldInt64]; - [self writeBytes:(UInt8*)&n length:8]; - } - } - if (!success) { - NSLog(@"Unsupported value: %@ of number type %ld", value, CFNumberGetType(number)); - NSAssert(NO, @"Unsupported value for standard codec"); - } + return FlutterStandardCodecObjcTypeNSNumber; } else if ([value isKindOfClass:[NSString class]]) { - NSString* string = value; - [self writeByte:FlutterStandardFieldString]; - [self writeUTF8:string]; + return FlutterStandardCodecObjcTypeNSString; } else if ([value isKindOfClass:[FlutterStandardTypedData class]]) { - FlutterStandardTypedData* typedData = value; - [self writeByte:FlutterStandardFieldForDataType(typedData.type)]; - [self writeSize:typedData.elementCount]; - [self writeAlignment:typedData.elementSize]; - [self writeData:typedData.data]; + return FlutterStandardCodecObjcTypeFlutterStandardTypedData; } else if ([value isKindOfClass:[NSData class]]) { - [self writeValue:[FlutterStandardTypedData typedDataWithBytes:value]]; + return FlutterStandardCodecObjcTypeNSData; } else if ([value isKindOfClass:[NSArray class]]) { - NSArray* array = value; - [self writeByte:FlutterStandardFieldList]; - [self writeSize:array.count]; - for (id object in array) { - [self writeValue:object]; - } + return FlutterStandardCodecObjcTypeNSArray; } else if ([value isKindOfClass:[NSDictionary class]]) { - NSDictionary* dict = value; - [self writeByte:FlutterStandardFieldMap]; - [self writeSize:dict.count]; - for (id key in dict) { - [self writeValue:key]; - [self writeValue:[dict objectForKey:key]]; - } + return FlutterStandardCodecObjcTypeNSDictionary; } else { - NSLog(@"Unsupported value: %@ of type %@", value, [value class]); - NSAssert(NO, @"Unsupported value for standard codec"); + return FlutterStandardCodecObjcTypeUnknown; } } + +struct WriteKeyValuesInfo { + CFTypeRef writer; + CFMutableDataRef data; +}; + +static void WriteKeyValues(CFTypeRef key, CFTypeRef value, void* context) { + WriteKeyValuesInfo* info = (WriteKeyValuesInfo*)context; + FastWriteValueOfType(info->writer, info->data, key); + FastWriteValueOfType(info->writer, info->data, value); +} + +// Recurses into WriteValueOfType directly if it is writing a known type, +// otherwise recurses with objc_msgSend. +static void FastWriteValueOfType(CFTypeRef writer, CFMutableDataRef data, CFTypeRef value) { + FlutterStandardCodecObjcType type = GetWriteType((__bridge id)value); + if (type != FlutterStandardCodecObjcTypeUnknown) { + WriteValueOfType(writer, data, type, value); + } else { + [(__bridge FlutterStandardWriter*)writer writeValue:(__bridge id)value]; + } +} + +static void WriteValueOfType(CFTypeRef writer, + CFMutableDataRef data, + FlutterStandardCodecObjcType type, + CFTypeRef value) { + switch (type) { + case FlutterStandardCodecObjcTypeNil: + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldNil); + break; + case FlutterStandardCodecObjcTypeNSNumber: { + CFNumberRef number = (CFNumberRef)value; + BOOL success = FlutterStandardCodecHelperWriteNumber(data, number); + if (!success) { + NSLog(@"Unsupported value: %@ of number type %ld", value, CFNumberGetType(number)); + NSCAssert(NO, @"Unsupported value for standard codec"); + } + break; + } + case FlutterStandardCodecObjcTypeNSString: { + CFStringRef string = (CFStringRef)value; + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldString); + FlutterStandardCodecHelperWriteUTF8(data, string); + break; + } + case FlutterStandardCodecObjcTypeFlutterStandardTypedData: { + FlutterStandardTypedData* typedData = (__bridge FlutterStandardTypedData*)value; + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldForDataType(typedData.type)); + FlutterStandardCodecHelperWriteSize(data, typedData.elementCount); + FlutterStandardCodecHelperWriteAlignment(data, typedData.elementSize); + FlutterStandardCodecHelperWriteData(data, (__bridge CFDataRef)typedData.data); + break; + } + case FlutterStandardCodecObjcTypeNSData: + WriteValueOfType(writer, data, FlutterStandardCodecObjcTypeFlutterStandardTypedData, + (__bridge CFTypeRef) + [FlutterStandardTypedData typedDataWithBytes:(__bridge NSData*)value]); + break; + case FlutterStandardCodecObjcTypeNSArray: { + CFArrayRef array = (CFArrayRef)value; + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldList); + CFIndex count = CFArrayGetCount(array); + FlutterStandardCodecHelperWriteSize(data, count); + for (CFIndex i = 0; i < count; ++i) { + FastWriteValueOfType(writer, data, CFArrayGetValueAtIndex(array, i)); + } + break; + } + case FlutterStandardCodecObjcTypeNSDictionary: { + CFDictionaryRef dict = (CFDictionaryRef)value; + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldMap); + CFIndex count = CFDictionaryGetCount(dict); + FlutterStandardCodecHelperWriteSize(data, count); + WriteKeyValuesInfo info = { + .writer = writer, + .data = data, + }; + CFDictionaryApplyFunction(dict, WriteKeyValues, (void*)&info); + break; + } + case FlutterStandardCodecObjcTypeUnknown: { + id objc_value = (__bridge id)value; + NSLog(@"Unsupported value: %@ of type %@", objc_value, [objc_value class]); + NSCAssert(NO, @"Unsupported value for standard codec"); + break; + } + } +} + +- (void)writeValue:(id)value { + FlutterStandardCodecObjcType type = GetWriteType(value); + WriteValueOfType((__bridge CFTypeRef)self, (__bridge CFMutableDataRef)self->_data, type, + (__bridge CFTypeRef)value); +} @end @implementation FlutterStandardReader { diff --git a/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c b/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c index 2fbe4277648b0..d3cb78c9a1ebc 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c +++ b/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.c @@ -164,3 +164,98 @@ CFTypeRef FlutterStandardCodecHelperReadValueOfType( assert(false); } } + +void FlutterStandardCodecHelperWriteByte(CFMutableDataRef data, uint8_t value) { + CFDataAppendBytes(data, &value, 1); +} + +void FlutterStandardCodecHelperWriteBytes(CFMutableDataRef data, + const void* bytes, + unsigned long length) { + CFDataAppendBytes(data, bytes, length); +} + +void FlutterStandardCodecHelperWriteSize(CFMutableDataRef data, uint32_t size) { + if (size < 254) { + FlutterStandardCodecHelperWriteByte(data, size); + } else if (size <= 0xffff) { + FlutterStandardCodecHelperWriteByte(data, 254); + UInt16 value = (UInt16)size; + FlutterStandardCodecHelperWriteBytes(data, &value, 2); + } else { + FlutterStandardCodecHelperWriteByte(data, 255); + FlutterStandardCodecHelperWriteBytes(data, &size, 4); + } +} + +void FlutterStandardCodecHelperWriteAlignment(CFMutableDataRef data, + uint8_t alignment) { + uint8_t mod = CFDataGetLength(data) % alignment; + if (mod) { + for (int i = 0; i < (alignment - mod); i++) { + FlutterStandardCodecHelperWriteByte(data, 0); + } + } +} + +void FlutterStandardCodecHelperWriteUTF8(CFMutableDataRef data, + CFStringRef value) { + const char* utf8 = CFStringGetCStringPtr(value, kCFStringEncodingUTF8); + if (utf8) { + size_t length = strlen(utf8); + FlutterStandardCodecHelperWriteSize(data, length); + FlutterStandardCodecHelperWriteBytes(data, utf8, length); + } else { + CFIndex length = CFStringGetLength(value); + CFIndex used_length = 0; + // UTF16 length times 3 will fit all UTF8. + CFIndex buffer_length = length * 3; + UInt8* buffer = (UInt8*)malloc(buffer_length * sizeof(UInt8)); + CFStringGetBytes(value, CFRangeMake(0, length), kCFStringEncodingUTF8, 0, + false, buffer, buffer_length, &used_length); + FlutterStandardCodecHelperWriteSize(data, used_length); + FlutterStandardCodecHelperWriteBytes(data, buffer, used_length); + free(buffer); + } +} + +void FlutterStandardCodecHelperWriteData(CFMutableDataRef data, + CFDataRef value) { + const UInt8* bytes = CFDataGetBytePtr(value); + CFIndex length = CFDataGetLength(value); + FlutterStandardCodecHelperWriteBytes(data, bytes, length); +} + +bool FlutterStandardCodecHelperWriteNumber(CFMutableDataRef data, + CFNumberRef number) { + bool success = false; + if (CFGetTypeID(number) == CFBooleanGetTypeID()) { + bool b = CFBooleanGetValue((CFBooleanRef)number); + FlutterStandardCodecHelperWriteByte( + data, (b ? FlutterStandardFieldTrue : FlutterStandardFieldFalse)); + success = true; + } else if (CFNumberIsFloatType(number)) { + Float64 f; + success = CFNumberGetValue(number, kCFNumberFloat64Type, &f); + if (success) { + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldFloat64); + FlutterStandardCodecHelperWriteAlignment(data, 8); + FlutterStandardCodecHelperWriteBytes(data, &f, 8); + } + } else if (CFNumberGetByteSize(number) <= 4) { + SInt32 n; + success = CFNumberGetValue(number, kCFNumberSInt32Type, &n); + if (success) { + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldInt32); + FlutterStandardCodecHelperWriteBytes(data, &n, 4); + } + } else if (CFNumberGetByteSize(number) <= 8) { + SInt64 n; + success = CFNumberGetValue(number, kCFNumberSInt64Type, &n); + if (success) { + FlutterStandardCodecHelperWriteByte(data, FlutterStandardFieldInt64); + FlutterStandardCodecHelperWriteBytes(data, &n, 8); + } + } + return success; +} diff --git a/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h b/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h index 5c06d6091145b..62faa46816b88 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h +++ b/shell/platform/darwin/common/framework/Source/FlutterStandardCodecHelper.h @@ -37,6 +37,21 @@ static inline bool FlutterStandardFieldIsStandardType(uint8_t field) { field >= FlutterStandardFieldNil; } +typedef enum { + FlutterStandardCodecObjcTypeNil, + FlutterStandardCodecObjcTypeNSNumber, + FlutterStandardCodecObjcTypeNSString, + FlutterStandardCodecObjcTypeFlutterStandardTypedData, + FlutterStandardCodecObjcTypeNSData, + FlutterStandardCodecObjcTypeNSArray, + FlutterStandardCodecObjcTypeNSDictionary, + FlutterStandardCodecObjcTypeUnknown, +} FlutterStandardCodecObjcType; + +/////////////////////////////////////////////////////////////////////////////// +///\name Reader Helpers +///@{ + void FlutterStandardCodecHelperReadAlignment(unsigned long* location, uint8_t alignment); @@ -62,6 +77,34 @@ CFTypeRef FlutterStandardCodecHelperReadValueOfType( CFTypeRef (*ReadTypedDataOfType)(FlutterStandardField, CFTypeRef), CFTypeRef user_data); +///@} + +/////////////////////////////////////////////////////////////////////////////// +///\name Writer Helpers +///@{ + +void FlutterStandardCodecHelperWriteByte(CFMutableDataRef data, uint8_t value); + +void FlutterStandardCodecHelperWriteBytes(CFMutableDataRef data, + const void* bytes, + unsigned long length); + +void FlutterStandardCodecHelperWriteSize(CFMutableDataRef data, uint32_t size); + +void FlutterStandardCodecHelperWriteAlignment(CFMutableDataRef data, + uint8_t alignment); + +void FlutterStandardCodecHelperWriteUTF8(CFMutableDataRef data, + CFStringRef value); + +void FlutterStandardCodecHelperWriteData(CFMutableDataRef data, + CFDataRef value); + +bool FlutterStandardCodecHelperWriteNumber(CFMutableDataRef data, + CFNumberRef number); + +///@} + #if defined(__cplusplus) } #endif