From 9d60220ede468998d9840106e4ef0194f98beb16 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Sun, 22 Sep 2019 10:31:23 +0200 Subject: [PATCH 1/4] Make methodchannel.c more readable, Restructuring --- src/methodchannel.c | 510 +++++++++++++++++++------------------------- src/methodchannel.h | 36 ++-- 2 files changed, 242 insertions(+), 304 deletions(-) diff --git a/src/methodchannel.c b/src/methodchannel.c index 4073b073..f1db7c37 100644 --- a/src/methodchannel.c +++ b/src/methodchannel.c @@ -8,135 +8,103 @@ #include "methodchannel.h" #include "flutter-pi.h" -#define _NEXTN(buffer,n) ((buffer) = &((buffer)[n])); -#define _NEXTN_REMAINING(buffer,remaining,n) do {_NEXTN(buffer,n) (remaining)-=(n);} while (false); -#define _NEXT(buffer) _NEXTN(buffer, 1) -#define _NEXT_REMAINING(buffer,remaining) _NEXTN_REMAINING(buffer,remaining,1) +// only 32bit support for now. +#define __ALIGN4_REMAINING(value, remaining) __align((uint32_t*) (value), 4, remaining) +#define __ALIGN8_REMAINING(value, remaining) __align((uint32_t*) (value), 8, remaining) +#define align4(...) _ALIGN4_REMAINING(__VA_ARGS__, NULL) +#define align8(...) _ALIGN8_REMAINING(__VA_ARGS__, NULL) -#define _GET_MACRO(_1,_2,_3,NAME,...) NAME -#define NEXT(...) _GET_MACRO(__VA_ARGS__, _NEXT_REMAINING, _NEXT_REMAINING, _NEXT)(__VA_ARGS__) -#define NEXTN(...) _GET_MACRO(__VA_ARGS__, _NEXTN_REMAINING, _NEXTN)(__VA_ARGS__) +#define alignmentDiff(value, alignment) __alignmentDiff((uint64_t) value, alignment) -#define ALIGNMENT_DIFF(buffer, n) (((uint64_t) (buffer) - (((uint64_t) (buffer) + (n)-1) | (n)-1) - (n)-1)) -#define _ALIGN(buffer, n) do {(buffer) = (uint8_t*) ((((uint64_t) (buffer) + (n)-1) | (n)-1) - (n-1));} while (false); -#define _ALIGN_REMAINING(buffer, remaining, n) do {(buffer) = (uint8_t*) ((((uint64_t) (buffer) + (n)-1) | (n)-1) - (n-1));} while (false); -#define ALIGN(...) _GET_MACRO(__VA_ARGS__, _ALIGN_REMAINING, _ALIGN)(__VA_ARGS__) +#define __ADVANCE_REMAINING(value, n, remaining) __advance((uint32_t*) (value), n, remaining) +#define advance(...) _ADVANCE_REMAINING(__VA_ARGS__, NULL) -#define ASSERT_RETURN_BOOL(cond, err) if (!(cond)) {fprintf(stderr, "%s\n", err); return false;} +#define ASSERT_RETURN_FALSE(cond, err) if (!(cond)) {fprintf(stderr, "%s\n", err); return false;} -bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, size_t* p_buffer_size) { - MessageValueDiscriminator type = value->type; - - // Type Byte - *p_buffer_size += 1; - - size_t size; - switch (type) { - case kNull: - case kTrue: - case kFalse: - break; - case kTypeInt: - *p_buffer_size += 4; - break; - case kTypeLong: - *p_buffer_size += 8; - break; - case kTypeDouble: - *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned - *p_buffer_size += 8; - - break; - case kTypeString: - size = strlen(value->string_value); - - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size - *p_buffer_size += size; - - break; - case kTypeByteArray: - size = value->bytearray_value.size; - - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size - *p_buffer_size += size; - - break; - case kTypeIntArray: - size = value->intarray_value.size; - - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size - *p_buffer_size = (((*p_buffer_size) + 3) | 3) - 3; // 4-byte aligned - *p_buffer_size += size*4; - - break; - case kTypeLongArray: - size = value->longarray_value.size; - - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size - *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned - *p_buffer_size += size*8; +inline int __alignmentDiff(uint64_t value, int alignment) { + alignment--; + return value - (((((uint64_t) value) + alignment) | alignment) - alignment); +} +inline void __align(uint32_t *value, int alignment, size_t *remaining) { + if (remaining != NULL) + remaining -= alignmentDiff((uint64_t) *value, alignment); + alignment--; - break; - case kTypeDoubleArray: - size = value->doublearray_value.size; - - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size - *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned - *p_buffer_size += size*8; + *value = (uint32_t) ((((*value + alignment) | alignment) - alignment); +} +inline void __advance(uint32_t *value, int n_bytes) { + *value += n_bytes; +} - break; - case kTypeList: - size = value->list_value.size; +inline void write8(uint8_t **pbuffer, uint8_t value) { + *(uint8_t*) *pbuffer = value; +} +inline uint8_t read8(uint8_t **pbuffer) { + return *(uint8_t *) *pbuffer; +} +inline void write16(uint8_t **pbuffer, uint16_t value) { + *(uint16_t*) *pbuffer = value; +} +inline uint16_t read16(uint8_t **pbuffer) { + return *(uint16_t *) *pbuffer; +} +inline void write32(uint8_t **pbuffer, uint32_t value) { + *(uint32_t*) *pbuffer = value; +} +inline uint32_t read32(uint8_t **pbuffer) { + return *(int32_t *) *pbuffer; +} +inline void write64(uint8_t **pbuffer, uint64_t value) { + *(uint64_t*) *pbuffer = value; +} +inline uint64_t read64(uint8_t **pbuffer) { + return *(int64_t *) *pbuffer; +} - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write list size - for (int i = 0; ilist_value.list[i]), p_buffer_size)) return false; - - break; - case kTypeMap: - size = value->map_value.size; +inline int nSizeBytes(int size) { + return (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; +} +inline void writeSize(uint8_t **pbuffer, int size) { + if (size < 254) { + write8(pbuffer, (uint8_t) size); + advance(pbuffer, 1); + } else if (size <= 0xFFFF) { + write8(pbuffer, 0xFE); + advance(pbuffer, 1); - *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write map size - for (int i = 0; ilist_value.list[i*2 ]), p_buffer_size)) return false; - if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2+1]), p_buffer_size)) return false; - } + write16(pbuffer, (uint16_t) size); + advance(pbuffer, 2); + } else { + write8(pbuffer, 0xFF); + advance(pbuffer, 1); - break; - default: - fprintf(stderr, "Error calculating Message Codec Value size: Unsupported Value type: %d\n", type); - return false; + write32(pbuffer, (uint32_t) size); + advance(pbuffer, 4); } - - return true; } +inline bool readSize(uint8_t** pbuffer, size_t* remaining, uint32_t* size) { + ASSERT_RETURN_FALSE(*remaining >= 1, "Error decoding platform message: while decoding size: message ended too soon") + *size = read8(pbuffer); + advance(pbuffer, 1, remaining); -bool MethodChannel_writeSizeValueToBuffer(size_t size, uint8_t** p_buffer) { - if (size < 254) { - **p_buffer = size; - NEXT(*p_buffer) - } else if (size <= 0xFFFF) { - **p_buffer = 254; - NEXT(*p_buffer) - *(uint16_t*) *p_buffer = size; - NEXTN(*p_buffer, 2) - } else { - **p_buffer = 255; - NEXT(*p_buffer) - *(uint32_t*) *p_buffer = size; - NEXTN(*p_buffer, 4) + if (*size == 254) { + ASSERT_RETURN_FALSE(*remaining >= 2, "Error decoding platform message: while decoding size: message ended too soon") + + *size = read16(pbuffer); + advance(pbuffer, 2, remaining); + } else if (*size == 255) { + ASSERT_RETURN_BOOL(*remaining >= 4, "Error decoding platform message: while decoding size: message ended too soon") + + *size = read32(pbuffer); + advance(pbuffer, 4, remaining); } return true; } -bool MethodChannel_alignBuffer(unsigned int alignment, uint8_t** p_buffer) { - alignment--; - *p_buffer = (uint8_t*) ((((uint64_t) *p_buffer + alignment) | alignment) - alignment); - return true; -} -bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t** p_buffer) { - **p_buffer = (uint8_t) value->type; - NEXT(*p_buffer) + +bool MessageChannel_writeValueToBuffer(struct MessageChannelValue* value, uint8_t** pbuffer) { + write8(pbuffer, value->type); + advance(pbuffer, 1); size_t size; uint8_t* byteArray; switch (value->type) { @@ -144,90 +112,84 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* case kTrue: case kFalse: break; - case kTypeInt: - *(int32_t*) *p_buffer = value->int_value; - NEXTN(*p_buffer, 4) + case kInt32: + write32(pbuffer, value->int_value); + advance(pbuffer, 4); break; - case kTypeLong: - *(int64_t*) *p_buffer = value->long_value; - NEXTN(*p_buffer, 8) + case kInt64: + write64(pbuffer, value->long_value); + advance(pbuffer, 8); break; - case kTypeDouble: - MethodChannel_alignBuffer(8, p_buffer); - - *(double*) *p_buffer = value->double_value; - NEXTN(*p_buffer, 8) + case kFloat64: + align8(pbuffer); + write64(pbuffer, (uint64_t) value->double_value); + advance(pbuffer, 8); break; - case kTypeBigInt: - case kTypeString: - case kTypeByteArray: - if (value->type == kTypeBigInt) { - size = strlen(value->bigint_value); - byteArray = (uint8_t*) value->bigint_value; - } else if (value->type == kTypeString) { + case kLargeInt: + case kString: + case kUInt8Array: + if ((value->type == kLargeInt) || (value->type == kString)) { size = strlen(value->string_value); byteArray = (uint8_t*) value->string_value; - } else if (value->type == kTypeByteArray) { + } else if (value->type == kUInt8Array) { size = value->bytearray_value.size; byteArray = (uint8_t*) value->bytearray_value.array; } - MethodChannel_writeSizeValueToBuffer(size, p_buffer); + writeSize(size, pbuffer); for (int i=0; iintarray_value.size; - MethodChannel_writeSizeValueToBuffer(size, p_buffer); - MethodChannel_alignBuffer(4, p_buffer); + writeSize(pbuffer, size); + align(pbuffer, 4); for (int i=0; iintarray_value.array[i]; - NEXTN(*p_buffer, 4) + write32(pbuffer, value->intarray_value.array[i]); + advance(pbuffer, 4); } break; - case kTypeLongArray: + case kInt64Array: + case kFloat64Array: size = value->longarray_value.size; - MethodChannel_writeSizeValueToBuffer(size, p_buffer); - MethodChannel_alignBuffer(8, p_buffer); - + writeSize(pbuffer, size); + align(pbuffer, 8); for (int i=0; ilongarray_value.array[i]; - NEXTN(*p_buffer, 8) + write64(pbuffer, value->longarray_value.array[i]); + advance(pbuffer, 8); } break; - case kTypeDoubleArray: + /*case kFloat64Array: size = value->doublearray_value.size; - MethodChannel_writeSizeValueToBuffer(size, p_buffer); - MethodChannel_alignBuffer(8, p_buffer); + writeSize(pbuffer, size); + align(pbuffer, 8); for (int i=0; idoublearray_value.array[i]; - NEXTN(*p_buffer, 8) + write64(pbuffer, value->doublearray_value.array[i]); + advance(pbuffer, 8) } - break; - case kTypeList: + break;*/ + case kList: size = value->list_value.size; - MethodChannel_writeSizeValueToBuffer(size, p_buffer); - + writeSize(pbuffer, size); for (int i=0; ilist_value.list[i]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->list_value.list[i]), pbuffer)) return false; break; - case kTypeMap: + case kMap: size = value->map_value.size; - MethodChannel_writeSizeValueToBuffer(size, p_buffer); - + writeSize(pbuffer, size); for (int i=0; imap_value.map[i*2 ]), p_buffer)) return false; - if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2+1]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2 ]), pbuffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2+1]), pbuffer)) return false; } break; default: @@ -239,27 +201,27 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* } -bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* argument) { +bool MethodChannel_call(char* channel, char* method, struct MessageChannelValue* argument) { uint8_t *buffer, *buffer_cursor; size_t buffer_size = 0; // the method name is encoded as a String value and is the first value written to the buffer. - struct MethodChannelValue method_name_value = { + struct MessageChannelValue method_name_value = { .type = kTypeString, .string_value = method }; // calculate buffer size - if (!MethodChannel_calculateValueSizeInBuffer(&method_name_value, &buffer_size)) return false; - if (!MethodChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; + if (!MessageChannel_calculateValueSizeInBuffer(&method_name_value, &buffer_size)) return false; + if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; // allocate buffer buffer = (uint8_t*) malloc(buffer_size); buffer_cursor = buffer; // write buffer - if (!MethodChannel_writeValueToBuffer(&method_name_value, &buffer_cursor)) return false; - if (!MethodChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; + if (!MessageChannel_writeValueToBuffer(&method_name_value, &buffer_cursor)) return false; + if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; // send message buffer to flutter engine FlutterEngineResult result = FlutterEngineSendPlatformMessage( @@ -275,7 +237,7 @@ bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* free(buffer); return result == kSuccess; } -bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MethodChannelValue* response_value) { +bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MessageChannelValue* response_value) { uint8_t *buffer, *buffer_cursor; size_t buffer_size; @@ -295,28 +257,10 @@ bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle return result == kSuccess; } - -bool MethodChannel_decodeSize(uint8_t** p_buffer, size_t* buffer_remaining, size_t* size) { - ASSERT_RETURN_BOOL(*buffer_remaining >= 1, "Error decoding platform message: while decoding size: message ended too soon") - *size = *(uint8_t*) *p_buffer; - NEXT(*p_buffer, *buffer_remaining) - - if (*size == 254) { - ASSERT_RETURN_BOOL(*buffer_remaining >= 2, "Error decoding platform message: while decoding size: message ended too soon") - *size = *(uint16_t*) *p_buffer; - NEXTN(*p_buffer, *buffer_remaining, 2) - } else if (*size == 255) { - ASSERT_RETURN_BOOL(*buffer_remaining >= 4, "Error decoding platform message: while decoding size: message ended too soon") - *size = *(uint32_t*) *p_buffer; - NEXTN(*p_buffer, *buffer_remaining, 4) - } - - return true; -} -bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, struct MethodChannelValue* value) { - ASSERT_RETURN_BOOL(*buffer_remaining >= 1, "Error decoding platform message: while decoding value type: message ended to soon") - MessageValueDiscriminator type = **p_buffer; - NEXT(*p_buffer, *buffer_remaining) +bool MessageChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value) { + ASSERT_RETURN_FALSE(*buffer_remaining >= 1, "Error decoding platform message: while decoding value type: message ended to soon") + MessageValueDiscriminator type = **pbuffer; + advance(pbuffer, 1, buffer_remaining); value->type = type; @@ -326,147 +270,118 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str case kTrue: case kFalse: break; - case kTypeInt: - ASSERT_RETURN_BOOL(*buffer_remaining >= 4, "Error decoding platform message: while decoding kTypeInt: message ended to soon") + case kInt32: + ASSERT_RETURN_FALSE(*buffer_remaining >= 4, "Error decoding platform message: while decoding kTypeInt: message ended to soon") - value->int_value = *(int32_t*) *p_buffer; - NEXTN(*p_buffer, *buffer_remaining, 4) + value->int_value = (int32_t) read32(pbuffer); + advance(pbuffer, 4, buffer_remaining); break; - case kTypeLong: - ASSERT_RETURN_BOOL(*buffer_remaining >= 8, "Error decoding platform message: while decoding kTypeLong: message ended too soon") + case kInt64: + ASSERT_RETURN_FALSE(*buffer_remaining >= 8, "Error decoding platform message: while decoding kTypeLong: message ended too soon") - value->long_value = *(int64_t*) *p_buffer; - NEXTN(*p_buffer, *buffer_remaining, 8) + value->long_value = (int64_t) read64(pbuffer); + advance(pbuffer, 8, buffer_remaining); break; - case kTypeDouble: - ASSERT_RETURN_BOOL(*buffer_remaining >= 8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeDouble: message ended too soon") - ALIGN(*p_buffer, *buffer_remaining, 8) - - value->double_value = *(double*) *p_buffer; - NEXTN(*p_buffer, *buffer_remaining, 8) + case kFloat64: + ASSERT_RETURN_FALSE(*buffer_remaining >= (8 + alignmentDiff(*pbuffer, 8)), "Error decoding platform message: while decoding kTypeDouble: message ended too soon") + + align(pbuffer, 8, buffer_remaining); + value->double_value = (double) read64(pbuffer); + advance(pbuffer, 8, buffer_remaining); break; - case kTypeString: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kLargeInt: + case kString: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; - ASSERT_RETURN_BOOL(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeString: message ended too soon") + ASSERT_RETURN_FALSE(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeString: message ended too soon") char* c_string = calloc(size+1, sizeof(char)); for (int i = 0; i < size; i++) { - c_string[i] = **p_buffer; - NEXT(*p_buffer, *buffer_remaining) + c_string[i] = read8(pbuffer); + advance(pbuffer, 1, buffer_remaining); } value->string_value = c_string; break; - case kTypeByteArray: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kUInt8Array: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; - ASSERT_RETURN_BOOL(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeByteArray: message ended too soon") + ASSERT_RETURN_FALSE(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeByteArray: message ended too soon") value->bytearray_value.size = size; - value->bytearray_value.array = *p_buffer; - - NEXTN(*p_buffer, *buffer_remaining, size); + value->bytearray_value.array = *pbuffer; + align(pbuffer, size, buffer_remaining); break; - case kTypeIntArray: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kInt32Array: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; - ASSERT_RETURN_BOOL(*buffer_remaining >= size*4 + ALIGNMENT_DIFF(*p_buffer, 4), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") - ALIGN(*p_buffer, *buffer_remaining, 4) + ASSERT_RETURN_FALSE(*buffer_remaining >= size*4 + alignmentDiff(*pbuffer, 4), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") + align(pbuffer, 4, buffer_remaining); value->intarray_value.size = size; - value->intarray_value.array = (int32_t*) *p_buffer; + value->intarray_value.array = (int32_t*) *pbuffer; - NEXTN(*p_buffer, *buffer_remaining, size*4) + advance(pbuffer, size*4, buffer_remaining); break; - case kTypeLongArray: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kInt64Array: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; - ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeLongArray: message ended too soon") - ALIGN(*p_buffer, *buffer_remaining, 8) + ASSERT_RETURN_FALSE(*buffer_remaining >= size*8 + alignmentDiff(*pbuffer, 8), "Error decoding platform message: while decoding kTypeLongArray: message ended too soon") + align(pbuffer, 8, buffer_remaining); value->longarray_value.size = size; - value->longarray_value.array = (int64_t*) *p_buffer; + value->longarray_value.array = (int64_t*) *pbuffer; - NEXTN(*p_buffer, *buffer_remaining, size*8) + advance(pbuffer, size*8, buffer_remaining); break; - case kTypeDoubleArray: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kFloat64Array: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; - ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") - ALIGN(*p_buffer, *buffer_remaining, 8) + ASSERT_RETURN_FALSE(*buffer_remaining >= size*8 + alignmentDiff(*pbuffer, 8), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") + align(pbuffer, 8, buffer_remaining); value->doublearray_value.size = size; - value->doublearray_value.array = (double*) *p_buffer; + value->doublearray_value.array = (double*) *pbuffer; - NEXTN(*p_buffer, *buffer_remaining, size*8) + advance(pbuffer, size*8, buffer_remaining); break; - case kTypeList: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kList: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; value->list_value.size = size; - value->list_value.list = calloc(size, sizeof(struct MethodChannelValue)); + value->list_value.list = calloc(size, sizeof(struct MessageChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i]))) return false; + if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i]))) return false; } break; - case kTypeMap: - if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; + case kMap: + if (!readSize(pbuffer, buffer_remaining, &size)) return false; value->map_value.size = size; - value->map_value.map = calloc(size*2, sizeof(struct MethodChannelValue)); + value->map_value.map = calloc(size*2, sizeof(struct MessageChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2 ]))) return false; - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2+1]))) return false; + if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i*2 ]))) return false; + if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i*2+1]))) return false; } break; default: - fprintf(stderr, "Error decoding platform message: unknown value type: %d\n", type); + fprintf(stderr, "Error decoding platform message: unknown value type: 0x%02X\n", type); return false; } return true; } -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { - *presult = malloc(sizeof(struct MethodCall)); - struct MethodCall* result = *presult; - - uint8_t* buffer_cursor = buffer; - size_t buffer_remaining = buffer_size; - - if (*buffer == (char) 123) { - result->protocol = kJSONProtocol; - fprintf(stderr, "Error decoding Method Call: JSON Protocol not supported yet.\n"); - return false; - } else { - result->protocol = kStandardProtocol; - } - - struct MethodChannelValue method_name; - if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &method_name)) return false; - if (method_name.type != kTypeString) { - fprintf(stderr, "Error decoding Method Call: expected type of first value in buffer to be string (i.e. method name), got %d\n", method_name.type); - return false; - } - result->method = method_name.string_value; - - if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &(result->argument))) return false; - - return true; -} - - -bool MethodChannel_freeValue(struct MethodChannelValue* p_value) { +bool MessageChannel_freeValue(struct MessageChannelValue* p_value) { switch (p_value->type) { case kTypeString: free(p_value->string_value); @@ -490,11 +405,39 @@ bool MethodChannel_freeValue(struct MethodChannelValue* p_value) { return true; } + +bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { + *presult = malloc(sizeof(struct MethodCall)); + struct MethodCall* result = *presult; + + uint8_t* buffer_cursor = buffer; + size_t buffer_remaining = buffer_size; + + if (*buffer == (char) 123) { + result->protocol = kJSONProtocol; + fprintf(stderr, "Error decoding Method Call: JSON Protocol not supported yet.\n"); + return false; + } else { + result->protocol = kStandardProtocol; + } + + struct MessageChannelValue method_name; + if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, &method_name)) return false; + if (method_name.type != kString) { + fprintf(stderr, "Error decoding Method Call: expected type of first value in buffer to be string (i.e. method name), got %d\n", method_name.type); + return false; + } + result->method = method_name.string_value; + + if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, &(result->argument))) return false; + + return true; +} bool MethodChannel_freeMethodCall(struct MethodCall **pmethodcall) { struct MethodCall* methodcall = *pmethodcall; free(methodcall->method); - if (!MethodChannel_freeValue(&(methodcall->argument))) return false; + if (!MessageChannel_freeValue(&(methodcall->argument))) return false; free(methodcall); *pmethodcall = NULL; @@ -503,14 +446,11 @@ bool MethodChannel_freeMethodCall(struct MethodCall **pmethodcall) { } -#undef _NEXTN -#undef _NEXTN_REMAINING -#undef _NEXT -#undef _NEXT_REMAINING -#undef _GET_MACRO -#undef NEXT -#undef NEXTN -#undef ALIGNMENT_DIFF -#undef _ALIGN -#undef _ALIGN_REMAINING -#undef ALIGN \ No newline at end of file +#undef __ALIGN4_REMAINING +#undef __ALIGN8_REMAINING +#undef align4 +#undef align8 +#undef alignmentDiff +#undef __ADVANCE_REMAINING +#undef advance +#undef ASSERT_RETURN_FALSE \ No newline at end of file diff --git a/src/methodchannel.h b/src/methodchannel.h index 2e84f84d..b57bb889 100644 --- a/src/methodchannel.h +++ b/src/methodchannel.h @@ -8,27 +8,25 @@ typedef enum { kNull = 0, kTrue, kFalse, - kTypeInt, - kTypeLong, - kTypeBigInt, - kTypeDouble, - kTypeString, - kTypeByteArray, - kTypeIntArray, - kTypeLongArray, - kTypeDoubleArray, - kTypeList, - kTypeMap, - kNoValue = 0xFFFF + kInt32, + kInt64, + kLargeInt, // treat as kString + kFloat64, + kString, + kUInt8Array, + kInt32Array, + kInt64Array, + kFloat64Array, + kList, + kMap } MessageValueDiscriminator; -struct MethodChannelValue { +struct MessageChannelValue { MessageValueDiscriminator type; union { bool bool_value; int32_t int_value; int64_t long_value; - char* bigint_value; double double_value; char* string_value; struct { @@ -49,11 +47,11 @@ struct MethodChannelValue { } doublearray_value; struct { size_t size; - struct MethodChannelValue* list; + struct MessageChannelValue* list; } list_value; struct { size_t size; - struct MethodChannelValue* map; + struct MessageChannelValue* map; } map_value; }; }; @@ -64,11 +62,11 @@ struct MethodCall { kJSONProtocol } protocol; char* method; - struct MethodChannelValue argument; + struct MessageChannelValue argument; }; -bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* argument); -bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MethodChannelValue* response_value); +bool MethodChannel_call(char* channel, char* method, struct MessageChannelValue* argument); +bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MessageChannelValue* response_value); bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); bool MethodChannel_freeMethodCall(struct MethodCall** pmethodcall); From dd41a7e021d3840b55e74b6a7eca027797e78ae6 Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Mon, 23 Sep 2019 16:40:31 +0200 Subject: [PATCH 2/4] begin plugin registry implementation --- src/flutter-pi.c | 40 ++- src/{methodchannel.c => platformchannel.c} | 270 +++++++++++++++------ src/{methodchannel.h => platformchannel.h} | 28 ++- src/pluginregistry.c | 153 ++++++++++++ src/pluginregistry.h | 31 +++ 5 files changed, 425 insertions(+), 97 deletions(-) rename src/{methodchannel.c => platformchannel.c} (77%) rename src/{methodchannel.h => platformchannel.h} (50%) create mode 100644 src/pluginregistry.c create mode 100644 src/pluginregistry.h diff --git a/src/flutter-pi.c b/src/flutter-pi.c index cb013796..ca6b3468 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -28,7 +28,8 @@ #include #include "flutter-pi.h" -#include "methodchannel.h" +#include "platformchannel.h" +#include "pluginregistry.h" char* usage ="\ @@ -96,6 +97,7 @@ struct { int engine_argc; const char* const *engine_argv; intptr_t next_vblank_baton; + struct FlutterPiPluginRegistry *registry; } flutter = {0}; struct { @@ -381,7 +383,9 @@ void* proc_resolver(void* userdata, const char* name) { } void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { struct MethodCall* methodcall; - + int ok; + + if (!MethodChannel_decode(message->message_size, (uint8_t*) (message->message), &methodcall)) { fprintf(stderr, "Decoding method call failed\n"); return; @@ -389,8 +393,9 @@ void on_platform_message(const FlutterPlatformMessage* message, void* user printf("MethodCall: method name: %s argument type: %d\n", methodcall->method, methodcall->argument.type); - if (strcmp(methodcall->method, "counter") == 0) { - printf("method \"counter\" was called with argument %d\n", methodcall->argument.int_value); + int ok = PluginRegistry_onMethodCall(flutter->registry, message->channel, methodcall); + if (ok != 0) { + fprintf(stderr, "PluginRegistry_onMethodCall failed: %s\n", strerror(ok)); } MethodChannel_freeMethodCall(&methodcall); @@ -828,6 +833,12 @@ void destroy_display(void) { } bool init_application(void) { + int ok = PluginRegistry_init(&(flutter->registry)); + if (ok != 0) { + fprintf(stderr, "Could not initialize plugin registry: %s\n", strerror(ok)); + return false; + } + // configure flutter rendering flutter.renderer_config.type = kOpenGL; flutter.renderer_config.open_gl.struct_size = sizeof(flutter.renderer_config.open_gl); @@ -891,16 +902,20 @@ bool init_application(void) { return true; } void destroy_application(void) { - if (engine == NULL) return; - - FlutterEngineResult _result = FlutterEngineShutdown(engine); - - if (_result != kSuccess) { - fprintf(stderr, "Could not shutdown the flutter engine.\n"); - } -} + if (engine != NULL) { + FlutterEngineResult _result = FlutterEngineShutdown(engine); + engine = NULL; + if (_result != kSuccess) { + fprintf(stderr, "Could not shutdown the flutter engine.\n"); + } + } + int ok = PluginRegistry_deinit(&flutter->registry); + if (ok != 0) { + fprintf(stderr, "Could not deinitialize plugin registry: %s\n", strerror(ok)); + } +} /**************** * Input-Output * @@ -1101,7 +1116,6 @@ bool run_io_thread(void) { } - bool parse_cmd_args(int argc, char **argv) { int opt; int index = 0; diff --git a/src/methodchannel.c b/src/platformchannel.c similarity index 77% rename from src/methodchannel.c rename to src/platformchannel.c index f1db7c37..2c05c4ec 100644 --- a/src/methodchannel.c +++ b/src/platformchannel.c @@ -5,9 +5,11 @@ #include #include #include -#include "methodchannel.h" + +#include "platformchannel.h" #include "flutter-pi.h" + // only 32bit support for now. #define __ALIGN4_REMAINING(value, remaining) __align((uint32_t*) (value), 4, remaining) #define __ALIGN8_REMAINING(value, remaining) __align((uint32_t*) (value), 8, remaining) @@ -21,6 +23,8 @@ #define ASSERT_RETURN_FALSE(cond, err) if (!(cond)) {fprintf(stderr, "%s\n", err); return false;} + + inline int __alignmentDiff(uint64_t value, int alignment) { alignment--; return value - (((((uint64_t) value) + alignment) | alignment) - alignment); @@ -39,24 +43,25 @@ inline void __advance(uint32_t *value, int n_bytes) { inline void write8(uint8_t **pbuffer, uint8_t value) { *(uint8_t*) *pbuffer = value; } -inline uint8_t read8(uint8_t **pbuffer) { - return *(uint8_t *) *pbuffer; -} inline void write16(uint8_t **pbuffer, uint16_t value) { *(uint16_t*) *pbuffer = value; } -inline uint16_t read16(uint8_t **pbuffer) { - return *(uint16_t *) *pbuffer; -} inline void write32(uint8_t **pbuffer, uint32_t value) { *(uint32_t*) *pbuffer = value; } -inline uint32_t read32(uint8_t **pbuffer) { - return *(int32_t *) *pbuffer; -} inline void write64(uint8_t **pbuffer, uint64_t value) { *(uint64_t*) *pbuffer = value; } + +inline uint8_t read8(uint8_t **pbuffer) { + return *(uint8_t *) *pbuffer; +} +inline uint16_t read16(uint8_t **pbuffer) { + return *(uint16_t *) *pbuffer; +} +inline uint32_t read32(uint8_t **pbuffer) { + return *(int32_t *) *pbuffer; +} inline uint64_t read64(uint8_t **pbuffer) { return *(int64_t *) *pbuffer; } @@ -102,7 +107,88 @@ inline bool readSize(uint8_t** pbuffer, size_t* remaining, uint32_t* size) { return true; } -bool MessageChannel_writeValueToBuffer(struct MessageChannelValue* value, uint8_t** pbuffer) { + +bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, size_t* psize) { + MessageValueDiscriminator type = value->type; + + // Type Byte + advance(psize, 1); + + size_t size; + switch (type) { + case kNull: + case kTrue: + case kFalse: + break; + case kInt32: + advance(psize, 4); + break; + case kInt64: + advance(psize, 8); + break; + case kFloat64: + align (psize, 8); + advance(psize, 8); + break; + case kString: + case kLargeInt: + size = strlen(value->string_value); + advance(psize, size + nSizeBytes(size)); + break; + case kUInt8Array: + size = value->bytearray_value.size; + advance(psize, size + nSizeBytes(size)); + break; + case kInt32Array: + size = value->intarray_value.size; + + advance(psize, nSizeBytes(size)); + align (psize, 4); + advance(psize, size*4); + + break; + case kInt64Array: + size = value->longarray_value.size; + + advance(psize, nSizeBytes(size)); + align (psize, 8); + advance(psize, size*8); + + break; + case kFloat64Array: + size = value->doublearray_value.size; + + advance(psize, nSizeBytes(size)); + align (psize, 8); + advance(psize, size*8); + + break; + case kTypeList: + size = value->list_value.size; + + advance(psize, nSizeBytes(size)); + for (int i = 0; ilist_value.list[i]), psize)) return false; + + break; + case kTypeMap: + size = value->map_value.size; + + advance(psize, nSizeBytes(size)); + for (int i = 0; ilist_value.list[i*2 ]), psize)) return false; + if (!PlatformChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2+1]), psize)) return false; + } + + break; + default: + fprintf(stderr, "Error calculating Message Codec Value size: Unsupported Value type: %d\n", type); + return false; + } + + return true; +} +bool PlatformChannel_writeValueToBuffer(struct MessageChannelValue *value, uint8_t **pbuffer) { write8(pbuffer, value->type); advance(pbuffer, 1); @@ -199,65 +285,7 @@ bool MessageChannel_writeValueToBuffer(struct MessageChannelValue* value, uint8_ return true; } - - -bool MethodChannel_call(char* channel, char* method, struct MessageChannelValue* argument) { - uint8_t *buffer, *buffer_cursor; - size_t buffer_size = 0; - - // the method name is encoded as a String value and is the first value written to the buffer. - struct MessageChannelValue method_name_value = { - .type = kTypeString, - .string_value = method - }; - - // calculate buffer size - if (!MessageChannel_calculateValueSizeInBuffer(&method_name_value, &buffer_size)) return false; - if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; - - // allocate buffer - buffer = (uint8_t*) malloc(buffer_size); - buffer_cursor = buffer; - - // write buffer - if (!MessageChannel_writeValueToBuffer(&method_name_value, &buffer_cursor)) return false; - if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; - - // send message buffer to flutter engine - FlutterEngineResult result = FlutterEngineSendPlatformMessage( - engine, - & (const FlutterPlatformMessage) { - .struct_size = sizeof(FlutterPlatformMessage), - .channel = (const char*) channel, - .message = (const uint8_t*) buffer, - .message_size = (const size_t) buffer_size - } - ); - - free(buffer); - return result == kSuccess; -} -bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MessageChannelValue* response_value) { - uint8_t *buffer, *buffer_cursor; - size_t buffer_size; - - // calculate buffer size - if (!MethodChannel_calculateValueSizeInBuffer(response_value, &buffer_size)) return false; - - // allocate buffer - buffer_cursor = buffer = (uint8_t*) malloc(buffer_size); - - // write buffer - if (!MethodChannel_writeValueToBuffer(response_value, &buffer_cursor)) return false; - - // send message buffer to flutter engine - FlutterEngineResult result = FlutterEngineSendPlatformMessageResponse(engine, response_handle, buffer, buffer_size); - - free(buffer); - return result == kSuccess; -} - -bool MessageChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value) { +bool PlatformChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value) { ASSERT_RETURN_FALSE(*buffer_remaining >= 1, "Error decoding platform message: while decoding value type: message ended to soon") MessageValueDiscriminator type = **pbuffer; advance(pbuffer, 1, buffer_remaining); @@ -381,7 +409,7 @@ bool MessageChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, str return true; } -bool MessageChannel_freeValue(struct MessageChannelValue* p_value) { +bool PlatformChannel_freeValue(struct MessageChannelValue* p_value) { switch (p_value->type) { case kTypeString: free(p_value->string_value); @@ -406,7 +434,103 @@ bool MessageChannel_freeValue(struct MessageChannelValue* p_value) { return true; } -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { +bool PlatformChannel_sendMessage(char *channel, struct MessageChannelValue *argument) { + uint8_t *buffer, *buffer_cursor; + size_t buffer_size = 0; + + if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; + + buffer (uint8_t*) malloc(buffer_size); + buffer_cursor = buffer; + + if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; + + FlutterEngineResult result = FlutterEngineSendPlatformMessage( + engine, + & (const FlutterPlatformMessage) { + .struct_size = sizeof(FlutterPlatformMessage), + .channel = (const char*) channel, + .message = (const uint8_t*) buffer, + .message_size = (const size_t) buffer_size + } + ); + + free(buffer); + return result == kSuccess; +} +bool PlatformChannel_decodeMessage(size_t buffer_size, uint8_t *buffer, struct MessageChannelValue **presult) { + *presult = malloc(sizeof(struct MessageChannelValue)); + if (!*presult) { + errno = ENOMEM; + return false; + } + + struct MessageChannelValue *result = *presult; + + uint8_t *buffer_cursor = buffer; + size_t buffer_remaining = buffer_size; + + if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, result)) return false; + + return true; +} +bool PlatformChannel_respond(FlutterPlatformMessageResponseHandle *response_handle, struct MessageChannelValue *response) { + uint8_t *buffer, *buffer_cursor; + size_t buffer_size; + + // calculate buffer size + if (!MethodChannel_calculateValueSizeInBuffer(response_value, &buffer_size)) return false; + + // allocate buffer + buffer_cursor = buffer = (uint8_t*) malloc(buffer_size); + + // write buffer + if (!MethodChannel_writeValueToBuffer(response_value, &buffer_cursor)) return false; + + // send message buffer to flutter engine + FlutterEngineResult result = FlutterEngineSendPlatformMessageResponse(engine, response_handle, buffer, buffer_size); + + free(buffer); + return result == kSuccess; +} + +bool PlatformChannel_call(char *channel, char *method, struct MessageChannelValue *argument) { + uint8_t *buffer, *buffer_cursor; + size_t buffer_size = 0; + + // the method name is encoded as a String value and is the first value written to the buffer. + struct MessageChannelValue method_name_value = { + .type = kTypeString, + .string_value = method + }; + + // calculate buffer size + if (!MessageChannel_calculateValueSizeInBuffer(&method_name_value, &buffer_size)) return false; + if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; + + // allocate buffer + buffer = (uint8_t*) malloc(buffer_size); + buffer_cursor = buffer; + + // write buffer + if (!MessageChannel_writeValueToBuffer(&method_name_value, &buffer_cursor)) return false; + if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; + + // send message buffer to flutter engine + FlutterEngineResult result = FlutterEngineSendPlatformMessage( + engine, + & (const FlutterPlatformMessage) { + .struct_size = sizeof(FlutterPlatformMessage), + .channel = (const char*) channel, + .message = (const uint8_t*) buffer, + .message_size = (const size_t) buffer_size + } + ); + + free(buffer); + return result == kSuccess; +} +bool PlatformChannel_decodeMethodCall(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { *presult = malloc(sizeof(struct MethodCall)); struct MethodCall* result = *presult; @@ -433,7 +557,7 @@ bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall return true; } -bool MethodChannel_freeMethodCall(struct MethodCall **pmethodcall) { +bool PlatformChannel_freeMethodCall(struct MethodCall **pmethodcall) { struct MethodCall* methodcall = *pmethodcall; free(methodcall->method); diff --git a/src/methodchannel.h b/src/platformchannel.h similarity index 50% rename from src/methodchannel.h rename to src/platformchannel.h index b57bb889..40ca2810 100644 --- a/src/methodchannel.h +++ b/src/platformchannel.h @@ -25,26 +25,26 @@ struct MessageChannelValue { MessageValueDiscriminator type; union { bool bool_value; - int32_t int_value; - int64_t long_value; - double double_value; + int32_t int32_value; + int64_t int64_value; + double float64_value; char* string_value; struct { size_t size; uint8_t* array; - } bytearray_value; + } uint8array_value; struct { size_t size; int32_t* array; - } intarray_value; + } int32array_value; struct { size_t size; int64_t* array; - } longarray_value; + } int64array_value; struct { size_t size; double* array; - } doublearray_value; + } float64array_value; struct { size_t size; struct MessageChannelValue* list; @@ -65,9 +65,15 @@ struct MethodCall { struct MessageChannelValue argument; }; -bool MethodChannel_call(char* channel, char* method, struct MessageChannelValue* argument); -bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MessageChannelValue* response_value); -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); -bool MethodChannel_freeMethodCall(struct MethodCall** pmethodcall); +bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, size_t* psize); +bool PlatformChannel_writeValueToBuffer(struct MessageChannelValue *value, uint8_t **pbuffer); +bool PlatformChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value); +bool PlatformChannel_freeValue(struct MessageChannelValue* p_value); +bool PlatformChannel_sendMessage(char *channel, struct MessageChannelValue *argument); +bool PlatformChannel_decodeMessage(size_t buffer_size, uint8_t *buffer, struct MethodCall **presult); +bool PlatformChannel_respond(FlutterPlatformMessageResponseHandle *response_handle, struct MessageChannelValue *response); +bool PlatformChannel_call(char *channel, char *method, struct MessageChannelValue *argument); +bool PlatformChannel_decodeMethodCall(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); +bool PlatformChannel_freeMethodCall(struct MethodCall **pmethodcall); #endif \ No newline at end of file diff --git a/src/pluginregistry.c b/src/pluginregistry.c new file mode 100644 index 00000000..d3e960ba --- /dev/null +++ b/src/pluginregistry.c @@ -0,0 +1,153 @@ +#include +#include + +#include "platformchannel.h" +#include "pluginregistry.h" + +struct FlutterPiPluginRegistry { + struct FlutterPiPlugin *plugins; + size_t plugin_count; + struct FlutterPiPluginRegistryCallbackListElement *callbacks; +}; + +struct FlutterPiPluginRegistryCallbackListElement { // hopefully that full type name is not used very often. + struct FlutterPiPluginRegistryCallbackListElement *next; + char *channel; + void *userdata; + bool is_methodcall; + union { + FlutterPiMethodCallCallback methodcall_callback; + FlutterPiPlatformMessageCallback message_callback; + }; +}; + + +int PluginRegistry_init(struct FlutterPiPluginRegistry **pregistry) { + int ok; + + *pregistry = NULL; + *pregistry = malloc(sizeof(struct FlutterPiPluginRegistry)); + if (!(*pregistry)) return ENOMEM; + + struct FlutterPiPluginRegistry *registry = *pregistry; + registry->plugins = hardcoded_plugins; + registry->plugin_count = sizeof(hardcoded_plugins) / sizeof(struct FlutterPiPlugin); + + // load dynamic plugins + // nothing for now + + + for (int i = 0; i < registry->plugin_count; i++) { + ok = registry->plugins[i].init(registry, &(registry->plugins[i].userdata)); + if (ok != 0) return ok; + } + + return 0; +} +int PluginRegistry_onPlatformMessage(struct FlutterPiPluginRegistry *registry, FlutterPlatformMessage *message) { + struct FlutterPiPluginRegistryCallbackListElement *element = registry->callbacks; + + for (element = registry->callbacks; element != NULL; element = element->next) + if (strcmp(element->channel, message->channel) == 0) break; + + if (element != NULL) { + if (element->is_methodcall) { + // try decode platform message as method call + struct MethodCall *methodcall = NULL; + bool ok = PlatformChannel_decodeMethodCall(message->message_size, message->message, &methodcall); + if (!ok) return EBADMSG; + + element->methodcall_callback(element->userdata, message->channel, methodcall); + + PlatformChannel_freeMethodCall(&methodcall); + } else { + element->message_callback(element->userdata, message); + } + } + + return 0; +} +int PluginRegistry_setPlatformMessageHandler(struct FlutterPiPluginRegistry *registry, char *channel, + FlutterPiPlatformMessageCallback callback, void *userdata) { + + struct FlutterPiPluginRegistryCallbackListElement *element; + for (element = registry->callbacks; element != NULL; element = element->next) + if (strcmp(element->channel, channel) == 0) break; + + if (element != NULL) { + // change the behaviour of the existing handler + + element->is_methodcall = false; + element->message_callback = callback; + element->userdata = userdata; + return 0; + } else { + // new handler + + element = malloc(sizeof(struct FlutterPiPluginRegistryCallbackListElement)); + if (!element) return ENOMEM; + + element->channel = calloc(strlen(channel) +1, sizeof(char)); + if (!element->channel) return ENOMEM; + + strcpy(element->channel, channel); + + element->is_methodcall = false; + element->message_callback = callback; + element->userdata = userdata; + element->next = registry->callbacks; + + registry->callbacks = element; + } + + return 0; +} +int PluginRegistry_setMethodCallHandler(struct FlutterPiPluginRegistry *registry, char *channel, + FlutterPiMethodCallCallback callback, void *userdata) { + struct FlutterPiPluginRegistryCallbackListElement *element; + + for (element = registry->callbacks; element != NULL; element = element->next) + if (strcmp(element->channel, channel) == 0) break; + + if (element != NULL) { + // change the behaviour of the existing handler + + element->is_methodcall = true; + element->methodcall_callback = callback; + element->userdata = userdata; + return 0; + } else { + // new handler + + element = malloc(sizeof(struct FlutterPiPluginRegistryCallbackListElement)); + if (!element) return ENOMEM; + + element->channel = calloc(strlen(channel) +1, sizeof(char)); + if (!element->channel) return ENOMEM; + + strcpy(element->channel, channel); + + element->is_methodcall = true; + element->methodcall_callback = callback; + element->userdata = userdata; + element->next = registry->callbacks; + + registry->callbacks = element; + } + + return 0; +} +int PluginRegistry_deinit(struct FlutterPiPluginRegistry **pregistry) { + struct FlutterPiPluginRegistryCallbackListElement *element = (*pregistry)->callbacks, *t = NULL; + + while (element != NULL) { + free(element->channel); + + t = element; + element = element->next; + free(t); + } + + free(*pregistry); + *pregistry = NULL; +} \ No newline at end of file diff --git a/src/pluginregistry.h b/src/pluginregistry.h new file mode 100644 index 00000000..fd3a13d2 --- /dev/null +++ b/src/pluginregistry.h @@ -0,0 +1,31 @@ +#include +#include + +#include "platformchannel.h" + +#ifndef FLUTTER_PI_REGISTRY_H_ +#define FLUTTER_PI_REGISTRY_H_ + +typedef bool (*FlutterPiPluginRegistryCallback)(struct FlutterPiPluginRegistry *registry, void **userdata); +typedef bool (*FlutterPiMethodCallCallback)(void *userdata, char *channel, struct MethodCall *methodcall); +typedef bool (*FlutterPiPlatformMessageCallback)(void *userdata, FlutterPlatformMessage *message); +struct FlutterPiPluginRegistry; + +struct FlutterPiPlugin { + const char const* name; + FlutterPiPluginRegistryCallback init; + FlutterPiPluginRegistryCallback deinit; + void *userdata; +}; + +const struct FlutterPiPlugin hardcoded_plugins[] = { + {.name="connectivity", .init=NULL, .deinit=NULL, .userdata=NULL} +}; + +extern int PluginRegistry_init(struct FlutterPiPluginRegistry **registry); +extern int PluginRegistry_onMethodCall(struct FlutterPiPluginRegistry *registry, char *channel, struct MethodCall *methodcall); +extern int PluginRegistry_setMethodCallHandler(struct FlutterPiPluginRegistry *registry, char *methodchannel, + FlutterPiMethodCallCallback callback, void *userdata); +extern int PluginRegistry_deinit(struct FlutterPiPluginRegistry **registry); + +#endif \ No newline at end of file From 873fd37164834156a39d3c2b2c642517da5830c8 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Fri, 27 Sep 2019 20:14:29 +0200 Subject: [PATCH 3/4] pluginregistry and reworked platform channels - completely reworked platform channels - added support for JSON Method Calls - added support for JSON & Standard Method Call responses - implemented basic plugin registry with hardcoded plugin support - started working on services plugin - added test plugin for testing JSON/Standard method calls & responses - added a notImplemented response - support for JSON/Standard Method call envelopes --- src/flutter-pi.c | 49 +- src/jsmn.h | 471 ++++++++++++++++++ src/platformchannel.c | 1053 +++++++++++++++++++++++++++++++---------- src/platformchannel.h | 245 ++++++++-- src/pluginregistry.c | 221 +++++---- src/pluginregistry.h | 32 +- src/services-plugin.c | 162 +++++++ src/services-plugin.h | 10 + src/testplugin.c | 299 ++++++++++++ src/testplugin.h | 10 + 10 files changed, 2083 insertions(+), 469 deletions(-) create mode 100644 src/jsmn.h create mode 100644 src/services-plugin.c create mode 100644 src/services-plugin.h create mode 100644 src/testplugin.c create mode 100644 src/testplugin.h diff --git a/src/flutter-pi.c b/src/flutter-pi.c index ca6b3468..5f885114 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -286,7 +286,7 @@ const GLubyte* hacked_glGetString(GLenum name) { static GLubyte* extensions; if (extensions == NULL) { - GLubyte* orig_extensions = glGetString(GL_EXTENSIONS); + GLubyte* orig_extensions = (GLubyte *) glGetString(GL_EXTENSIONS); size_t len_orig_extensions = strlen(orig_extensions); extensions = malloc(len_orig_extensions+1); @@ -382,23 +382,9 @@ void* proc_resolver(void* userdata, const char* name) { return NULL; } void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { - struct MethodCall* methodcall; int ok; - - - if (!MethodChannel_decode(message->message_size, (uint8_t*) (message->message), &methodcall)) { - fprintf(stderr, "Decoding method call failed\n"); - return; - } - - printf("MethodCall: method name: %s argument type: %d\n", methodcall->method, methodcall->argument.type); - - int ok = PluginRegistry_onMethodCall(flutter->registry, message->channel, methodcall); - if (ok != 0) { - fprintf(stderr, "PluginRegistry_onMethodCall failed: %s\n", strerror(ok)); - } - - MethodChannel_freeMethodCall(&methodcall); + if ((ok = PluginRegistry_onPlatformMessage((FlutterPlatformMessage *)message)) != 0) + fprintf(stderr, "PluginRegistry_onPlatformMessage failed: %s\n", strerror(ok)); } void vsync_callback(void* userdata, intptr_t baton) { // not yet implemented @@ -833,7 +819,10 @@ void destroy_display(void) { } bool init_application(void) { - int ok = PluginRegistry_init(&(flutter->registry)); + int ok; + + printf("Initializing Plugin Registry...\n"); + ok = PluginRegistry_init(); if (ok != 0) { fprintf(stderr, "Could not initialize plugin registry: %s\n", strerror(ok)); return false; @@ -848,9 +837,6 @@ bool init_application(void) { flutter.renderer_config.open_gl.fbo_callback = fbo_callback; flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; - for (int i=0; iregistry); - if (ok != 0) { + if ((ok = PluginRegistry_deinit()) != 0) { fprintf(stderr, "Could not deinitialize plugin registry: %s\n", strerror(ok)); } } @@ -1160,7 +1141,7 @@ bool parse_cmd_args(int argc, char **argv) { argv[optind] = argv[0]; flutter.engine_argc = argc-optind; - flutter.engine_argv = &(argv[optind]); + flutter.engine_argv = (const char* const*) &(argv[optind]); for (int i=0; i + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string. + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +JSMN_API void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing + * a single JSON object. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + +#ifndef JSMN_HEADER +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + /* to quiet a warning from gcc*/ + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; + i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +JSMN_API void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +#endif /* JSMN_HEADER */ + +#ifdef __cplusplus +} +#endif + +#endif /* JSMN_H */ \ No newline at end of file diff --git a/src/platformchannel.c b/src/platformchannel.c index 2c05c4ec..6533feb8 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -6,37 +8,45 @@ #include #include -#include "platformchannel.h" #include "flutter-pi.h" +#include "platformchannel.h" +#include "jsmn.h" // only 32bit support for now. -#define __ALIGN4_REMAINING(value, remaining) __align((uint32_t*) (value), 4, remaining) -#define __ALIGN8_REMAINING(value, remaining) __align((uint32_t*) (value), 8, remaining) -#define align4(...) _ALIGN4_REMAINING(__VA_ARGS__, NULL) -#define align8(...) _ALIGN8_REMAINING(__VA_ARGS__, NULL) +#define __ALIGN4_REMAINING(value, remaining, ...) __align((uint32_t*) (value), 4, remaining) +#define __ALIGN8_REMAINING(value, remaining, ...) __align((uint32_t*) (value), 8, remaining) +#define align4(...) __ALIGN4_REMAINING(__VA_ARGS__, NULL) +#define align8(...) __ALIGN8_REMAINING(__VA_ARGS__, NULL) -#define alignmentDiff(value, alignment) __alignmentDiff((uint64_t) value, alignment) +#define alignmentDiff(value, alignment) __alignmentDiff((uint32_t) value, alignment) -#define __ADVANCE_REMAINING(value, n, remaining) __advance((uint32_t*) (value), n, remaining) -#define advance(...) _ADVANCE_REMAINING(__VA_ARGS__, NULL) +#define __ADVANCE_REMAINING(value, n, remaining, ...) __advance((uint32_t*) (value), n, remaining) +#define advance(...) __ADVANCE_REMAINING(__VA_ARGS__, NULL) -#define ASSERT_RETURN_FALSE(cond, err) if (!(cond)) {fprintf(stderr, "%s\n", err); return false;} +struct ResponseHandlerData { + enum ChannelCodec codec; + PlatformMessageResponseCallback on_response; + void *userdata; +}; -inline int __alignmentDiff(uint64_t value, int alignment) { +inline int __alignmentDiff(uint32_t value, int alignment) { alignment--; - return value - (((((uint64_t) value) + alignment) | alignment) - alignment); + return value - (((((uint32_t) value) + alignment) | alignment) - alignment); } inline void __align(uint32_t *value, int alignment, size_t *remaining) { if (remaining != NULL) - remaining -= alignmentDiff((uint64_t) *value, alignment); + *remaining -= alignmentDiff((uint32_t) *value, alignment); alignment--; - *value = (uint32_t) ((((*value + alignment) | alignment) - alignment); + *value = (uint32_t) (((*value + alignment) | alignment) - alignment); } -inline void __advance(uint32_t *value, int n_bytes) { +inline void __advance(uint32_t *value, int n_bytes, size_t *remaining) { + if (remaining != NULL) + *remaining -= n_bytes; + *value += n_bytes; } @@ -87,34 +97,118 @@ inline void writeSize(uint8_t **pbuffer, int size) { advance(pbuffer, 4); } } -inline bool readSize(uint8_t** pbuffer, size_t* remaining, uint32_t* size) { - ASSERT_RETURN_FALSE(*remaining >= 1, "Error decoding platform message: while decoding size: message ended too soon") - *size = read8(pbuffer); - advance(pbuffer, 1, remaining); +inline int readSize(uint8_t **pbuffer, size_t *premaining, uint32_t *psize) { + if (*premaining < 1) return EBADMSG; + + *psize = read8(pbuffer); + advance(pbuffer, 1, premaining); - if (*size == 254) { - ASSERT_RETURN_FALSE(*remaining >= 2, "Error decoding platform message: while decoding size: message ended too soon") + if (*psize == 254) { + if (*premaining < 2) return EBADMSG; - *size = read16(pbuffer); - advance(pbuffer, 2, remaining); - } else if (*size == 255) { - ASSERT_RETURN_BOOL(*remaining >= 4, "Error decoding platform message: while decoding size: message ended too soon") + *psize = read16(pbuffer); + advance(pbuffer, 2, premaining); + } else if (*psize == 255) { + if (*premaining < 4) return EBADMSG; - *size = read32(pbuffer); - advance(pbuffer, 4, remaining); + *psize = read32(pbuffer); + advance(pbuffer, 4, premaining); } - return true; + return 0; } -bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, size_t* psize) { - MessageValueDiscriminator type = value->type; +int PlatformChannel_freeStdMsgCodecValue(struct StdMsgCodecValue *value) { + int ok; - // Type Byte - advance(psize, 1); + switch (value->type) { + case kString: + free(value->string_value); + break; + case kList: + for (int i=0; i < value->size; i++) { + ok = PlatformChannel_freeStdMsgCodecValue(&(value->list[i])); + if (ok != 0) return ok; + } + free(value->list); + break; + case kMap: + for (int i=0; i < value->size; i++) { + ok = PlatformChannel_freeStdMsgCodecValue(&(value->keys[i])); + if (ok != 0) return ok; + ok = PlatformChannel_freeStdMsgCodecValue(&(value->values[i])); + if (ok != 0) return ok; + } + free(value->keys); + break; + default: + break; + } + + return 0; +} +int PlatformChannel_freeJSONMsgCodecValue(struct JSONMsgCodecValue *value, bool shallow) { + int ok; + + switch (value->type) { + case kJSArray: + if (!shallow) { + for (int i = 0; i < value->size; i++) { + ok = PlatformChannel_freeJSONMsgCodecValue(&(value->array[i]), false); + if (ok != 0) return ok; + } + } + free(value->array); + break; + case kJSObject: + if (!shallow) { + for (int i = 0; i < value->size; i++) { + ok = PlatformChannel_freeJSONMsgCodecValue(&(value->values[i]), false); + if (ok != 0) return ok; + } + } + + free(value->keys); + break; + default: + break; + } + + return 0; +} +int PlatformChannel_free(struct ChannelObject *object) { + switch (object->codec) { + case kStringCodec: + free(object->string_value); + break; + case kBinaryCodec: + break; + case kJSONMessageCodec: + PlatformChannel_freeJSONMsgCodecValue(&(object->jsonmsgcodec_value), false); + break; + case kStandardMessageCodec: + PlatformChannel_freeStdMsgCodecValue(&(object->stdmsgcodec_value)); + break; + case kStandardMethodCall: + free(object->method); + PlatformChannel_freeStdMsgCodecValue(&(object->stdarg)); + break; + case kJSONMethodCall: + PlatformChannel_freeJSONMsgCodecValue(&(object->jsarg), false); + } + + return 0; +} + +int PlatformChannel_calculateStdMsgCodecValueSize(struct StdMsgCodecValue* value, size_t* psize) { + enum StdMsgCodecValueType type = value->type; size_t size; + int ok; + + // Type Byte + advance(psize, 1); switch (type) { case kNull: case kTrue: @@ -127,7 +221,7 @@ bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value advance(psize, 8); break; case kFloat64: - align (psize, 8); + align8 (psize); advance(psize, 8); break; case kString: @@ -136,79 +230,81 @@ bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value advance(psize, size + nSizeBytes(size)); break; case kUInt8Array: - size = value->bytearray_value.size; + size = value->size; advance(psize, size + nSizeBytes(size)); break; case kInt32Array: - size = value->intarray_value.size; + size = value->size; advance(psize, nSizeBytes(size)); - align (psize, 4); + align4 (psize); advance(psize, size*4); break; case kInt64Array: - size = value->longarray_value.size; + size = value->size; advance(psize, nSizeBytes(size)); - align (psize, 8); + align8 (psize); advance(psize, size*8); break; case kFloat64Array: - size = value->doublearray_value.size; + size = value->size; advance(psize, nSizeBytes(size)); - align (psize, 8); + align8 (psize); advance(psize, size*8); break; - case kTypeList: - size = value->list_value.size; + case kList: + size = value->size; advance(psize, nSizeBytes(size)); for (int i = 0; ilist_value.list[i]), psize)) return false; + if ((ok = PlatformChannel_calculateStdMsgCodecValueSize(&(value->list[i]), psize)) != 0) return ok; break; - case kTypeMap: - size = value->map_value.size; + case kMap: + size = value->size; advance(psize, nSizeBytes(size)); for (int i = 0; ilist_value.list[i*2 ]), psize)) return false; - if (!PlatformChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2+1]), psize)) return false; + if ((ok = PlatformChannel_calculateStdMsgCodecValueSize(&(value->keys[i]), psize)) != 0) return ok; + if ((ok = PlatformChannel_calculateStdMsgCodecValueSize(&(value->values[i]), psize)) != 0) return ok; } break; default: - fprintf(stderr, "Error calculating Message Codec Value size: Unsupported Value type: %d\n", type); - return false; + return EINVAL; } - return true; + return 0; } -bool PlatformChannel_writeValueToBuffer(struct MessageChannelValue *value, uint8_t **pbuffer) { +int PlatformChannel_writeStdMsgCodecValueToBuffer(struct StdMsgCodecValue* value, uint8_t **pbuffer) { + uint8_t* byteArray; + size_t size; + int ok; + write8(pbuffer, value->type); advance(pbuffer, 1); - size_t size; uint8_t* byteArray; switch (value->type) { case kNull: case kTrue: case kFalse: break; case kInt32: - write32(pbuffer, value->int_value); + write32(pbuffer, value->int32_value); advance(pbuffer, 4); break; case kInt64: - write64(pbuffer, value->long_value); + write64(pbuffer, value->int64_value); advance(pbuffer, 8); break; case kFloat64: align8(pbuffer); - write64(pbuffer, (uint64_t) value->double_value); + write64(pbuffer, (uint64_t) value->float64_value); advance(pbuffer, 8); break; case kLargeInt: @@ -218,363 +314,800 @@ bool PlatformChannel_writeValueToBuffer(struct MessageChannelValue *value, uint8 size = strlen(value->string_value); byteArray = (uint8_t*) value->string_value; } else if (value->type == kUInt8Array) { - size = value->bytearray_value.size; - byteArray = (uint8_t*) value->bytearray_value.array; + size = value->size; + byteArray = value->uint8array; } - writeSize(size, pbuffer); + writeSize(pbuffer, size); for (int i=0; iintarray_value.size; + size = value->size; writeSize(pbuffer, size); - align(pbuffer, 4); + align4(pbuffer); for (int i=0; iintarray_value.array[i]); + write32(pbuffer, value->int32array[i]); advance(pbuffer, 4); } break; case kInt64Array: - case kFloat64Array: - size = value->longarray_value.size; + size = value->size; writeSize(pbuffer, size); - align(pbuffer, 8); + align8(pbuffer); for (int i=0; ilongarray_value.array[i]); + write64(pbuffer, value->int64array[i]); advance(pbuffer, 8); } break; - /*case kFloat64Array: - size = value->doublearray_value.size; + case kFloat64Array: + size = value->size; writeSize(pbuffer, size); - align(pbuffer, 8); + align8(pbuffer); for (int i=0; idoublearray_value.array[i]); - advance(pbuffer, 8) + write64(pbuffer, value->float64array[i]); + advance(pbuffer, 8); } - break;*/ + break; case kList: - size = value->list_value.size; + size = value->size; writeSize(pbuffer, size); for (int i=0; ilist_value.list[i]), pbuffer)) return false; + if ((ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(value->list[i]), pbuffer)) != 0) return ok; break; case kMap: - size = value->map_value.size; + size = value->size; writeSize(pbuffer, size); for (int i=0; imap_value.map[i*2 ]), pbuffer)) return false; - if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2+1]), pbuffer)) return false; + if ((ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(value->keys[i]), pbuffer)) != 0) return ok; + if ((ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(value->values[i]), pbuffer)) != 0) return ok; } break; default: - fprintf(stderr, "Error encoding Message Codec Value: Unsupported Value type: %d\n", value->type); - return false; + return EINVAL; } - return true; + return 0; } -bool PlatformChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value) { - ASSERT_RETURN_FALSE(*buffer_remaining >= 1, "Error decoding platform message: while decoding value type: message ended to soon") - MessageValueDiscriminator type = **pbuffer; - advance(pbuffer, 1, buffer_remaining); +size_t PlatformChannel_calculateJSONMsgCodecValueSize(struct JSONMsgCodecValue *value) { + size_t size = 0; - value->type = type; + switch (value->type) { + case kJSNull: + case kJSTrue: + return 4; + case kJSFalse: + return 5; + case kJSNumber: ; + char numBuffer[32]; + return sprintf(numBuffer, "%lf", value->number_value); + case kJSString: + return strlen(value->string_value) +2; + case kJSArray: + size += 2; + for (int i=0; i < value->size; i++) { + size += PlatformChannel_calculateJSONMsgCodecValueSize(&(value->array[i])); + if (i+1 != value->size) size += 1; + } + return size; + case kJSObject: + size += 2; + for (int i=0; i < value->size; i++) { + size += strlen(value->keys[i]) + 3 + PlatformChannel_calculateJSONMsgCodecValueSize(&(value->values[i])); + if (i+1 != value->size) size += 1; + } + return size; + default: + return EINVAL; + } - size_t size = 0; char* c_string = 0; uint8_t* byteArray = 0; int32_t* intArray = 0; int64_t* longArray = 0; + return 0; +} +int PlatformChannel_writeJSONMsgCodecValueToBuffer(struct JSONMsgCodecValue* value, uint8_t **pbuffer) { + switch (value->type) { + case kJSNull: + *pbuffer += sprintf((char*) *pbuffer, "null"); + break; + case kJSTrue: + *pbuffer += sprintf((char*) *pbuffer, "true"); + break; + case kJSFalse: + *pbuffer += sprintf((char*) *pbuffer, "false"); + break; + case kJSNumber: + *pbuffer += sprintf((char*) *pbuffer, "%lf", value->number_value); + break; + case kJSString: + *pbuffer += sprintf((char*) *pbuffer, "\"%s\"", value->string_value); + break; + case kJSArray: + *pbuffer += sprintf((char*) *pbuffer, "["); + for (int i=0; i < value->size; i++) { + PlatformChannel_writeJSONMsgCodecValueToBuffer(&(value->array[i]), pbuffer); + if (i+1 != value->size) *pbuffer += sprintf((char*) *pbuffer, ","); + } + *pbuffer += sprintf((char*) *pbuffer, "]"); + break; + case kJSObject: + *pbuffer += sprintf((char*) *pbuffer, "{"); + for (int i=0; i < value->size; i++) { + *pbuffer += sprintf((char*) *pbuffer, "\"%s\":", value->keys[i]); + PlatformChannel_writeJSONMsgCodecValueToBuffer(&(value->values[i]), pbuffer); + if (i+1 != value->size) *pbuffer += sprintf((char*) *pbuffer, ","); + } + *pbuffer += sprintf((char*) *pbuffer, "}"); + break; + default: + return EINVAL; + } + + return 0; +} +int PlatformChannel_decodeStdMsgCodecValue(uint8_t **pbuffer, size_t *premaining, struct StdMsgCodecValue *value_out) { + int64_t *longArray = 0; + int32_t *intArray = 0; + uint8_t *byteArray = 0; + char *c_string = 0; + size_t size = 0; + int ok; + + enum StdMsgCodecValueType type = read8(pbuffer); + advance(pbuffer, 1, premaining); + + value_out->type = type; switch (type) { case kNull: case kTrue: case kFalse: break; case kInt32: - ASSERT_RETURN_FALSE(*buffer_remaining >= 4, "Error decoding platform message: while decoding kTypeInt: message ended to soon") + if (*premaining < 4) return EBADMSG; - value->int_value = (int32_t) read32(pbuffer); - advance(pbuffer, 4, buffer_remaining); + value_out->int32_value = (int32_t) read32(pbuffer); + advance(pbuffer, 4, premaining); break; case kInt64: - ASSERT_RETURN_FALSE(*buffer_remaining >= 8, "Error decoding platform message: while decoding kTypeLong: message ended too soon") + if (*premaining < 8) return EBADMSG; - value->long_value = (int64_t) read64(pbuffer); - advance(pbuffer, 8, buffer_remaining); + value_out->int64_value = (int64_t) read64(pbuffer); + advance(pbuffer, 8, premaining); break; case kFloat64: - ASSERT_RETURN_FALSE(*buffer_remaining >= (8 + alignmentDiff(*pbuffer, 8)), "Error decoding platform message: while decoding kTypeDouble: message ended too soon") - - align(pbuffer, 8, buffer_remaining); - value->double_value = (double) read64(pbuffer); - advance(pbuffer, 8, buffer_remaining); + if (*premaining < (8 + alignmentDiff(*pbuffer, 8))) return EBADMSG; + + align8(pbuffer, premaining); + uint64_t temp = read64(pbuffer); + value_out->float64_value = *((double*) (&temp)); + advance(pbuffer, 8, premaining); break; case kLargeInt: case kString: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; - - ASSERT_RETURN_FALSE(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeString: message ended too soon") - char* c_string = calloc(size+1, sizeof(char)); + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; + if (*premaining < size) return EBADMSG; - for (int i = 0; i < size; i++) { - c_string[i] = read8(pbuffer); - advance(pbuffer, 1, buffer_remaining); - } - value->string_value = c_string; + value_out->string_value = calloc(size+1, sizeof(char)); + if (!value_out->string_value) return ENOMEM; + memcpy(value_out->string_value, *pbuffer, size); + advance(pbuffer, size, premaining); break; case kUInt8Array: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; + if (*premaining < size) return EBADMSG; - ASSERT_RETURN_FALSE(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeByteArray: message ended too soon") - value->bytearray_value.size = size; - value->bytearray_value.array = *pbuffer; - align(pbuffer, size, buffer_remaining); + value_out->size = size; + value_out->uint8array = *pbuffer; + advance(pbuffer, size, premaining); break; case kInt32Array: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; - - ASSERT_RETURN_FALSE(*buffer_remaining >= size*4 + alignmentDiff(*pbuffer, 4), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") - align(pbuffer, 4, buffer_remaining); + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; + if (*premaining < (size*4 + alignmentDiff(*pbuffer, 4))) return EBADMSG; - value->intarray_value.size = size; - value->intarray_value.array = (int32_t*) *pbuffer; - - advance(pbuffer, size*4, buffer_remaining); + align4(pbuffer, premaining); + value_out->size = size; + value_out->int32array = (int32_t*) *pbuffer; + advance(pbuffer, size*4, premaining); break; case kInt64Array: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; - - ASSERT_RETURN_FALSE(*buffer_remaining >= size*8 + alignmentDiff(*pbuffer, 8), "Error decoding platform message: while decoding kTypeLongArray: message ended too soon") - align(pbuffer, 8, buffer_remaining); - - value->longarray_value.size = size; - value->longarray_value.array = (int64_t*) *pbuffer; + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; + if (*premaining < (size*8 + alignmentDiff(*pbuffer, 8))) return EBADMSG; - advance(pbuffer, size*8, buffer_remaining); + align8(pbuffer, premaining); + value_out->size = size; + value_out->int64array = (int64_t*) *pbuffer; + advance(pbuffer, size*8, premaining); break; case kFloat64Array: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; + if (*premaining < (size*8 + alignmentDiff(*pbuffer, 8))) return EBADMSG; - ASSERT_RETURN_FALSE(*buffer_remaining >= size*8 + alignmentDiff(*pbuffer, 8), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") - align(pbuffer, 8, buffer_remaining); - - value->doublearray_value.size = size; - value->doublearray_value.array = (double*) *pbuffer; - - advance(pbuffer, size*8, buffer_remaining); + align8(pbuffer, premaining); + value_out->size = size; + value_out->float64array = (double*) *pbuffer; + advance(pbuffer, size*8, premaining); break; case kList: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; - value->list_value.size = size; - value->list_value.list = calloc(size, sizeof(struct MessageChannelValue)); + value_out->size = size; + value_out->list = calloc(size, sizeof(struct StdMsgCodecValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i]))) return false; + ok = PlatformChannel_decodeStdMsgCodecValue(pbuffer, premaining, &(value_out->list[i])); + if (ok != 0) return ok; } break; case kMap: - if (!readSize(pbuffer, buffer_remaining, &size)) return false; + if ((ok = readSize(pbuffer, premaining, &size)) != 0) return ok; - value->map_value.size = size; - value->map_value.map = calloc(size*2, sizeof(struct MessageChannelValue)); + value_out->size = size; + value_out->keys = calloc(size*2, sizeof(struct StdMsgCodecValue)); + if (!value_out->keys) return ENOMEM; + value_out->values = &(value_out->keys[size]); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i*2 ]))) return false; - if (!MethodChannel_decodeValue(pbuffer, buffer_remaining, &(value->list_value.list[i*2+1]))) return false; + ok = PlatformChannel_decodeStdMsgCodecValue(pbuffer, premaining, &(value_out->keys[i])); + if (ok != 0) return ok; + + ok = PlatformChannel_decodeStdMsgCodecValue(pbuffer, premaining, &(value_out->values[i])); + if (ok != 0) return ok; } break; default: - fprintf(stderr, "Error decoding platform message: unknown value type: 0x%02X\n", type); - return false; + return EBADMSG; + } + + return 0; +} +int PlatformChannel_decodeJSONMsgCodecValue(char *message, size_t size, jsmntok_t **pptoken, size_t *ptokensremaining, struct JSONMsgCodecValue *value_out) { + jsmntok_t *ptoken; + int result, ok; + + if (!pptoken) { + // if we have no token list yet, parse the message & create one. + + jsmntok_t tokens[JSON_DECODE_TOKENLIST_SIZE]; + jsmn_parser parser; + size_t tokensremaining; + + memset(tokens, sizeof(tokens), 0); + + jsmn_init(&parser); + result = jsmn_parse(&parser, (const char *) message, (const size_t) size, tokens, JSON_DECODE_TOKENLIST_SIZE); + if (result < 0) return EBADMSG; + + tokensremaining = (size_t) result; + ptoken = tokens; + + ok = PlatformChannel_decodeJSONMsgCodecValue(message, size, &ptoken, &tokensremaining, value_out); + if (ok != 0) return ok; + } else { + // message is already tokenized + + ptoken = *pptoken; + + (*pptoken) += 1; + *ptokensremaining -= 1; + + switch (ptoken->type) { + case JSMN_UNDEFINED: + return EBADMSG; + case JSMN_PRIMITIVE: + if (message[ptoken->start] == 'n') { + value_out->type = kJSNull; + } else if (message[ptoken->start] == 't') { + value_out->type = kJSTrue; + } else if (message[ptoken->start] == 'f') { + value_out->type = kJSFalse; + } else { + value_out->type = kJSNumber; + + // hacky, but should work in normal circumstances. If the platform message solely consists + // of this number and nothing else, this could fail. + char old = message[ptoken->end]; + message[ptoken->end] = '\0'; + value_out->number_value = strtod(message + ptoken->start, NULL); + message[ptoken->end] = old; + } + + break; + case JSMN_STRING: ; + // use zero-copy approach. + + message[ptoken->end] = '\0'; + char *string = message + ptoken->start; + + value_out->type = kJSString; + value_out->string_value = string; + + break; + case JSMN_ARRAY: ; + struct JSONMsgCodecValue *array = calloc(ptoken->size, sizeof(struct JSONMsgCodecValue)); + if (!array) return ENOMEM; + + for (int i=0; i < ptoken->size; i++) { + ok = PlatformChannel_decodeJSONMsgCodecValue(message, size, pptoken, ptokensremaining, &array[i]); + if (ok != 0) return ok; + } + + value_out->type = kJSArray; + value_out->size = ptoken->size; + value_out->array = array; + + break; + case JSMN_OBJECT: ; + struct JSONMsgCodecValue key; + char **keys = calloc(ptoken->size, sizeof(char *)); + struct JSONMsgCodecValue *values = calloc(ptoken->size, sizeof(struct JSONMsgCodecValue)); + if ((!keys) || (!values)) return ENOMEM; + + for (int i=0; i < ptoken->size; i++) { + ok = PlatformChannel_decodeJSONMsgCodecValue(message, size, pptoken, ptokensremaining, &key); + if (ok != 0) return ok; + + if (key.type != kJSString) return EBADMSG; + keys[i] = key.string_value; + + ok = PlatformChannel_decodeJSONMsgCodecValue(message, size, pptoken, ptokensremaining, &values[i]); + if (ok != 0) return ok; + } + + value_out->type = kJSObject; + value_out->size = ptoken->size; + value_out->keys = keys; + value_out->values = values; + + break; + default: + return EBADMSG; + } } - return true; + return 0; } -bool PlatformChannel_freeValue(struct MessageChannelValue* p_value) { - switch (p_value->type) { - case kTypeString: - free(p_value->string_value); + +int PlatformChannel_decode(uint8_t *buffer, size_t size, enum ChannelCodec codec, struct ChannelObject *object_out) { + struct JSONMsgCodecValue root_jsvalue; + uint8_t *buffer_cursor = buffer; + size_t remaining = size; + int ok; + + + object_out->codec = codec; + switch (codec) { + case kStringCodec: ; + /// buffer is a non-null-terminated, UTF8-encoded string. + /// it's really sad we have to allocate a new memory block for this, but we have to since string codec buffers are not null-terminated. + + char *string; + if (!(string = malloc(size +1))) return ENOMEM; + memcpy(string, buffer, size); + string[size] = '\0'; + + object_out->string_value = string; + break; - case kTypeList: - for (int i=0; i < p_value->list_value.size; i++) - if (!MethodChannel_freeValue(&(p_value->list_value.list[i]))) return false; + case kBinaryCodec: + object_out->binarydata = buffer; + object_out->binarydata_size = size; + + break; + case kJSONMessageCodec: + ok = PlatformChannel_decodeJSONMsgCodecValue((char *) buffer, size, NULL, NULL, &(object_out->jsonmsgcodec_value)); + if (ok != 0) return ok; + + break; + case kJSONMethodCall: ; + ok = PlatformChannel_decodeJSONMsgCodecValue((char *) buffer, size, NULL, NULL, &root_jsvalue); + if (ok != 0) return ok; + + if (root_jsvalue.type != kJSObject) return EBADMSG; + + for (int i=0; i < root_jsvalue.size; i++) { + if ((strcmp(root_jsvalue.keys[i], "method") == 0) && (root_jsvalue.values[i].type == kJSString)) { + object_out->method = root_jsvalue.values[i].string_value; + } else if (strcmp(root_jsvalue.keys[i], "args") == 0) { + object_out->jsarg = root_jsvalue.values[i]; + } else return EBADMSG; + } + + PlatformChannel_freeJSONMsgCodecValue(&root_jsvalue, true); + + break; + case kJSONMethodCallResponse: ; + ok = PlatformChannel_decodeJSONMsgCodecValue((char *) buffer, size, NULL, NULL, &root_jsvalue); + if (ok != 0) return ok; + if (root_jsvalue.type != kJSArray) return EBADMSG; - free(p_value->list_value.list); + if (root_jsvalue.size == 1) { + object_out->success = true; + object_out->jsresult = root_jsvalue.array[0]; + return PlatformChannel_freeJSONMsgCodecValue(&root_jsvalue, true); + } else if ((root_jsvalue.size == 3) && + (root_jsvalue.array[0].type == kJSString) && + ((root_jsvalue.array[1].type == kJSString) || (root_jsvalue.array[1].type == kJSNull))) { + + + object_out->success = false; + object_out->errorcode = root_jsvalue.array[0].string_value; + object_out->errormessage = root_jsvalue.array[1].string_value; + object_out->jserrordetails = root_jsvalue.array[2]; + return PlatformChannel_freeJSONMsgCodecValue(&root_jsvalue, true); + } else return EBADMSG; + + break; + case kStandardMessageCodec: + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &(object_out->stdmsgcodec_value)); + if (ok != 0) return ok; break; - case kTypeMap: - for (int i=0; i< p_value->map_value.size; i++) { - if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2 ]))) return false; - if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2+1]))) return false; + case kStandardMethodCall: ; + struct StdMsgCodecValue methodname; + + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &methodname); + if (ok != 0) return ok; + if (methodname.type != kString) { + PlatformChannel_freeStdMsgCodecValue(&methodname); + return EPROTO; } + object_out->method = methodname.string_value; - free(p_value->map_value.map); - default: + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &(object_out->stdarg)); + if (ok != 0) return ok; + + break; + case kStandardMethodCallResponse: ; + object_out->success = read8(&buffer_cursor) == 0; + advance(&buffer_cursor, 1, &remaining); + + if (object_out->success) { + struct StdMsgCodecValue result; + + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &(object_out->stdresult)); + if (ok != 0) return ok; + } else { + struct StdMsgCodecValue errorcode, errormessage; + + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &errorcode); + if (ok != 0) return ok; + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &errormessage); + if (ok != 0) return ok; + ok = PlatformChannel_decodeStdMsgCodecValue(&buffer_cursor, &remaining, &(object_out->stderrordetails)); + if (ok != 0) return ok; + + if ((errorcode.type == kString) && ((errormessage.type == kString) || (errormessage.type == kNull))) { + object_out->errorcode = errorcode.string_value; + object_out->errormessage = (errormessage.type == kString) ? errormessage.string_value : NULL; + } else { + return EBADMSG; + } + } break; + default: + return EINVAL; } - return true; + return 0; } - -bool PlatformChannel_sendMessage(char *channel, struct MessageChannelValue *argument) { +int PlatformChannel_encode(struct ChannelObject *object, uint8_t **buffer_out, size_t *size_out) { + struct StdMsgCodecValue stdmethod, stderrcode, stderrmessage; + struct JSONMsgCodecValue jsmethod, jserrcode, jserrmessage, jsroot; uint8_t *buffer, *buffer_cursor; - size_t buffer_size = 0; + size_t size = 0; + int ok = 0; - if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; + *size_out = 0; + *buffer_out = NULL; - buffer (uint8_t*) malloc(buffer_size); - buffer_cursor = buffer; + switch (object->codec) { + case kStringCodec: + size = strlen(object->string_value); + break; + case kBinaryCodec: + *buffer_out = object->binarydata; + *size_out = object->binarydata_size; + return 0; + case kJSONMessageCodec: + size = PlatformChannel_calculateJSONMsgCodecValueSize(&(object->jsonmsgcodec_value)); + size += 1; // JSONMsgCodec uses sprintf, which null-terminates strings, + // so lets allocate one more byte for the last null-terminator. + // this is decremented again in the second switch-case, so flutter + // doesn't complain about a malformed message. + break; + case kStandardMessageCodec: + ok = PlatformChannel_calculateStdMsgCodecValueSize(&(object->stdmsgcodec_value), &size); + if (ok != 0) return ok; + break; + case kStandardMethodCall: + stdmethod.type = kString; + stdmethod.string_value = object->method; + + ok = PlatformChannel_calculateStdMsgCodecValueSize(&stdmethod, &size); + if (ok != 0) return ok; - if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; + ok = PlatformChannel_calculateStdMsgCodecValueSize(&(object->stdarg), &size); + if (ok != 0) return ok; - FlutterEngineResult result = FlutterEngineSendPlatformMessage( - engine, - & (const FlutterPlatformMessage) { - .struct_size = sizeof(FlutterPlatformMessage), - .channel = (const char*) channel, - .message = (const uint8_t*) buffer, - .message_size = (const size_t) buffer_size - } - ); + break; + case kStandardMethodCallResponse: + size += 1; + + if (object->success) { + ok = PlatformChannel_calculateStdMsgCodecValueSize(&(object->stdresult), &size); + if (ok != 0) return ok; + } else { + stderrcode = (struct StdMsgCodecValue) { + .type = kString, + .string_value = object->errorcode + }; + stderrmessage = (struct StdMsgCodecValue) { + .type = kString, + .string_value = object->errormessage + }; + + ok = PlatformChannel_calculateStdMsgCodecValueSize(&stderrcode, &size); + if (ok != 0) return ok; + ok = PlatformChannel_calculateStdMsgCodecValueSize(&stderrmessage, &size); + if (ok != 0) return ok; + ok = PlatformChannel_calculateStdMsgCodecValueSize(&(object->stderrordetails), &size); + if (ok != 0) return ok; + } + break; + case kJSONMethodCall: + jsroot.type = kJSObject; + jsroot.size = 2; + jsroot.keys = (char*[]) {"method", "args"}; + jsroot.values = (struct JSONMsgCodecValue[]) { + {.type = kJSString, .string_value = object->method}, + object->jsarg + }; + + size = PlatformChannel_calculateJSONMsgCodecValueSize(&jsroot); + size += 1; + break; + case kJSONMethodCallResponse: + jsroot.type = kJSArray; + if (object->success) { + jsroot.size = 1; + jsroot.array = (struct JSONMsgCodecValue[]) { + object->jsresult + }; + } else { + jsroot.size = 3; + jsroot.array = (struct JSONMsgCodecValue[]) { + {.type = kJSString, .string_value = object->errorcode}, + {.type = (object->errormessage != NULL) ? kJSString : kJSNull, .string_value = object->errormessage}, + object->jserrordetails + }; + } - free(buffer); - return result == kSuccess; -} -bool PlatformChannel_decodeMessage(size_t buffer_size, uint8_t *buffer, struct MessageChannelValue **presult) { - *presult = malloc(sizeof(struct MessageChannelValue)); - if (!*presult) { - errno = ENOMEM; - return false; + size = PlatformChannel_calculateJSONMsgCodecValueSize(&jsroot); + size += 1; + break; + default: + return EINVAL; } - struct MessageChannelValue *result = *presult; - - uint8_t *buffer_cursor = buffer; - size_t buffer_remaining = buffer_size; + if (!(buffer = malloc(size))) return ENOMEM; + buffer_cursor = buffer; + + switch (object->codec) { + case kStringCodec: + memcpy(buffer, object->string_value, size); + break; + case kStandardMessageCodec: + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(object->stdmsgcodec_value), &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + break; + case kStandardMethodCall: + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&stdmethod, &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; - if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, result)) return false; + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(object->stdarg), &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; - return true; -} -bool PlatformChannel_respond(FlutterPlatformMessageResponseHandle *response_handle, struct MessageChannelValue *response) { - uint8_t *buffer, *buffer_cursor; - size_t buffer_size; + break; + case kStandardMethodCallResponse: + if (object->success) { + write8(&buffer_cursor, 0x00); + advance(&buffer_cursor, 1); + + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(object->stdresult), &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + } else { + write8(&buffer_cursor, 0x01); + advance(&buffer_cursor, 1); + + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&stderrcode, &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&stderrmessage, &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + ok = PlatformChannel_writeStdMsgCodecValueToBuffer(&(object->stderrordetails), &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + } + + break; + case kJSONMessageCodec: + size -= 1; + ok = PlatformChannel_writeJSONMsgCodecValueToBuffer(&(object->jsonmsgcodec_value), &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + break; + case kJSONMethodCall: ; + size -= 1; + ok = PlatformChannel_writeJSONMsgCodecValueToBuffer(&jsroot, &buffer_cursor); + if (ok != 0) goto free_buffer_and_return_ok; + break; + default: + return EINVAL; + } - // calculate buffer size - if (!MethodChannel_calculateValueSizeInBuffer(response_value, &buffer_size)) return false; - - // allocate buffer - buffer_cursor = buffer = (uint8_t*) malloc(buffer_size); + *buffer_out = buffer; + *size_out = size; + return 0; - // write buffer - if (!MethodChannel_writeValueToBuffer(response_value, &buffer_cursor)) return false; - // send message buffer to flutter engine - FlutterEngineResult result = FlutterEngineSendPlatformMessageResponse(engine, response_handle, buffer, buffer_size); - + free_buffer_and_return_ok: free(buffer); - return result == kSuccess; + return ok; } -bool PlatformChannel_call(char *channel, char *method, struct MessageChannelValue *argument) { - uint8_t *buffer, *buffer_cursor; - size_t buffer_size = 0; +void PlatformChannel_internalOnResponse(const uint8_t *buffer, size_t size, void *userdata) { + struct ResponseHandlerData *handlerdata; + struct ChannelObject object; + int ok; - // the method name is encoded as a String value and is the first value written to the buffer. - struct MessageChannelValue method_name_value = { - .type = kTypeString, - .string_value = method - }; + handlerdata = (struct ResponseHandlerData *) userdata; + ok = PlatformChannel_decode((uint8_t*) buffer, size, handlerdata->codec, &object); + if (ok != 0) return; - // calculate buffer size - if (!MessageChannel_calculateValueSizeInBuffer(&method_name_value, &buffer_size)) return false; - if (!MessageChannel_calculateValueSizeInBuffer(argument, &buffer_size)) return false; + ok = handlerdata->on_response(&object, handlerdata->userdata); + if (ok != 0) return; - // allocate buffer - buffer = (uint8_t*) malloc(buffer_size); - buffer_cursor = buffer; + free(handlerdata); + + ok = PlatformChannel_free(&object); + if (ok != 0) return; +} +int PlatformChannel_send(char *channel, struct ChannelObject *object, enum ChannelCodec response_codec, PlatformMessageResponseCallback on_response, void *userdata) { + struct ResponseHandlerData *handlerdata = NULL; + FlutterPlatformMessageResponseHandle *response_handle = NULL; + FlutterEngineResult result; + uint8_t *buffer; + size_t size; + int ok; + + ok = PlatformChannel_encode(object, &buffer, &size); + if (ok != 0) return ok; + + if (on_response) { + handlerdata = malloc(sizeof(struct ResponseHandlerData)); + if (!handlerdata) return ENOMEM; + + handlerdata->codec = response_codec; + handlerdata->on_response = on_response; + handlerdata->userdata = userdata; - // write buffer - if (!MessageChannel_writeValueToBuffer(&method_name_value, &buffer_cursor)) return false; - if (!MessageChannel_writeValueToBuffer(argument, &buffer_cursor)) return false; + result = FlutterPlatformMessageCreateResponseHandle(engine, PlatformChannel_internalOnResponse, handlerdata, &response_handle); + if (result != kSuccess) return EINVAL; + } - // send message buffer to flutter engine - FlutterEngineResult result = FlutterEngineSendPlatformMessage( + result = FlutterEngineSendPlatformMessage( engine, & (const FlutterPlatformMessage) { .struct_size = sizeof(FlutterPlatformMessage), .channel = (const char*) channel, .message = (const uint8_t*) buffer, - .message_size = (const size_t) buffer_size + .message_size = (const size_t) size, + .response_handle = response_handle } ); - free(buffer); - return result == kSuccess; -} -bool PlatformChannel_decodeMethodCall(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { - *presult = malloc(sizeof(struct MethodCall)); - struct MethodCall* result = *presult; - - uint8_t* buffer_cursor = buffer; - size_t buffer_remaining = buffer_size; - - if (*buffer == (char) 123) { - result->protocol = kJSONProtocol; - fprintf(stderr, "Error decoding Method Call: JSON Protocol not supported yet.\n"); - return false; - } else { - result->protocol = kStandardProtocol; + if (on_response) { + result = FlutterPlatformMessageReleaseResponseHandle(engine, response_handle); + if (result != kSuccess) return EINVAL; } + + if (object->codec != kBinaryCodec) + free(buffer); - struct MessageChannelValue method_name; - if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, &method_name)) return false; - if (method_name.type != kString) { - fprintf(stderr, "Error decoding Method Call: expected type of first value in buffer to be string (i.e. method name), got %d\n", method_name.type); - return false; - } - result->method = method_name.string_value; + return (result == kSuccess) ? 0 : EINVAL; +} +int PlatformChannel_stdcall(char *channel, char *method, struct StdMsgCodecValue *argument, PlatformMessageResponseCallback on_response, void *userdata) { + struct ChannelObject object = { + .codec = kStandardMethodCall, + .method = method, + .stdarg = *argument + }; + + return PlatformChannel_send(channel, &object, kStandardMethodCallResponse, on_response, userdata); +} +int PlatformChannel_jsoncall(char *channel, char *method, struct JSONMsgCodecValue *argument, PlatformMessageResponseCallback on_response, void *userdata) { + return PlatformChannel_send(channel, + &(struct ChannelObject) { + .codec = kJSONMethodCall, + .method = method, + .jsarg = *argument + }, + kJSONMethodCallResponse, + on_response, + userdata); +} +int PlatformChannel_respond(FlutterPlatformMessageResponseHandle *handle, struct ChannelObject *response) { + FlutterEngineResult result; + uint8_t *buffer; + size_t size; + int ok; - if (!MessageChannel_decodeValue(&buffer_cursor, &buffer_remaining, &(result->argument))) return false; + ok = PlatformChannel_encode(response, &buffer, &size); + if (ok != 0) return ok; - return true; + result = FlutterEngineSendPlatformMessageResponse(engine, (const FlutterPlatformMessageResponseHandle*) handle, (const uint8_t*) buffer, size); + + free(buffer); + + return (result == kSuccess) ? 0 : EINVAL; +} +int PlatformChannel_respondNotImplemented(FlutterPlatformMessageResponseHandle *handle) { + return PlatformChannel_respond( + (FlutterPlatformMessageResponseHandle *) handle, + &(struct ChannelObject) { + .codec = kBinaryCodec, + .binarydata = NULL, + .binarydata_size = 0 + }); +} +int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, enum ChannelCodec codec, char *errorcode, char *errormessage, void *errordetails) { + if ((codec == kStandardMessageCodec) || (codec == kStandardMethodCall) || (codec == kStandardMethodCallResponse)) { + return PlatformChannel_respond(handle, &(struct ChannelObject) { + .codec = kStandardMethodCallResponse, + .success = false, + .errorcode = errorcode, + .errormessage = errormessage, + .stderrordetails = *((struct StdMsgCodecValue *) errordetails) + }); + } else if ((codec == kJSONMessageCodec) || (codec == kJSONMethodCall) || (codec == kJSONMethodCallResponse)) { + return PlatformChannel_respond(handle, &(struct ChannelObject) { + .codec = kJSONMethodCallResponse, + .success = false, + .errorcode = errorcode, + .errormessage = errormessage, + .jserrordetails = *((struct JSONMsgCodecValue *) errordetails) + }); + } else return EINVAL; } -bool PlatformChannel_freeMethodCall(struct MethodCall **pmethodcall) { - struct MethodCall* methodcall = *pmethodcall; - free(methodcall->method); - if (!MessageChannel_freeValue(&(methodcall->argument))) return false; - free(methodcall); +struct JSONMsgCodecValue *json_get(struct JSONMsgCodecValue *object, char *key) { + int i; + for (i=0; i < object->size; i++) + if (strcmp(object->keys[i], key) == 0) break; - *pmethodcall = NULL; - return true; + if (i != object->size) return &(object->values[i]); + return NULL; } - #undef __ALIGN4_REMAINING #undef __ALIGN8_REMAINING #undef align4 #undef align8 #undef alignmentDiff #undef __ADVANCE_REMAINING -#undef advance -#undef ASSERT_RETURN_FALSE \ No newline at end of file +#undef advance \ No newline at end of file diff --git a/src/platformchannel.h b/src/platformchannel.h index 40ca2810..03e71131 100644 --- a/src/platformchannel.h +++ b/src/platformchannel.h @@ -4,7 +4,44 @@ #include #include -typedef enum { +#define JSON_DECODE_TOKENLIST_SIZE 128 + +/* + * It may be simpler for plugins if the two message value types were unified. + * But from a performance POV, this doesn't make sense. number arrays in StandardMessageCodec + * are 4 or 8 -byte aligned for faster access. We don't have to copy them, StdMsgCodecValue.int64array (as an example) + * is just a pointer to that portion of the buffer, where the array is located. + * + * However, JSON and thus JSON Message Handlers have no idea what a int64array is, they just know of JSON arrays. + * This means we'd have to implicitly convert the int64array into a JSON array when we want to unify the two message value types, + * and this costs all the performance we (more precisely, the flutter engineers) gained by memory-aligning the arrays in StdMsgCodecValue. + * + * Let's just hope the flutter team doesn't randomly switch codecs of platform channels. Receive Handlers would + * need to be rewritten every time they do. The handlers not needing to be rewritten would probably be the only advantage + * of using a unified message value type. + */ +enum JSONMsgCodecValueType { + kJSNull, kJSTrue, kJSFalse, kJSNumber, kJSString, kJSArray, kJSObject +}; +struct JSONMsgCodecValue { + enum JSONMsgCodecValueType type; + union { + double number_value; + char *string_value; + struct { + size_t size; + union { + struct JSONMsgCodecValue *array; + struct { + char **keys; + struct JSONMsgCodecValue *values; + }; + }; + }; + }; +}; + +enum StdMsgCodecValueType { kNull = 0, kTrue, kFalse, @@ -19,61 +56,179 @@ typedef enum { kFloat64Array, kList, kMap -} MessageValueDiscriminator; - -struct MessageChannelValue { - MessageValueDiscriminator type; +}; +struct StdMsgCodecValue { + enum StdMsgCodecValueType type; union { - bool bool_value; + bool bool_value; int32_t int32_value; int64_t int64_value; - double float64_value; - char* string_value; - struct { - size_t size; - uint8_t* array; - } uint8array_value; - struct { - size_t size; - int32_t* array; - } int32array_value; + double float64_value; + char* string_value; struct { size_t size; - int64_t* array; - } int64array_value; + union { + uint8_t* uint8array; + int32_t* int32array; + int64_t* int64array; + double* float64array; + struct StdMsgCodecValue* list; + struct { + struct StdMsgCodecValue* keys; + struct StdMsgCodecValue* values; + }; + }; + }; + }; +}; + +/// codec of an abstract channel object +/// These tell this API how it should encode ChannelObjects -> platform messages +/// and how to decode platform messages -> ChannelObjects. +enum ChannelCodec { + kStringCodec, + kBinaryCodec, + kJSONMessageCodec, + kStandardMessageCodec, + kStandardMethodCall, + kStandardMethodCallResponse, + kJSONMethodCall, + kJSONMethodCallResponse +}; + +/// Abstract Channel Object. +/// Different properties are "valid" for different codecs: +/// kStringCodec: +/// - string_value is the raw byte data of a platform message, but with an additional null-byte at the end. +/// kBinaryCodec: +/// - binarydata is an array of the raw byte data of a platform message, +/// - binarydata_size is the size of that byte data in uint8_t's. +/// kJSONMessageCodec: +/// - jsonmsgcodec_value +/// kStdMsgCodecValue: +/// - stdmsgcodec_value +/// kStandardMethodCall: +/// - "method" is the method you'd like to call, or the method that was called +/// by flutter. +/// - stdarg contains the argument to that method call. +/// kJSONMethodCall: +/// - "method" is the method you'd like to call, or the method that was called +/// by flutter. +/// - jsarg contains the argument to that method call. +/// kStandardMethodCallResponse or kJSONMethodCallResponse: +/// - "success" is whether the method call (send to flutter or received from flutter) +/// was succesful, i.e. no errors ocurred. +/// if success is false, +/// - errorcode must be set (by you or by flutter) to point to a valid null-terminated string, +/// - errormessage is either pointing to a valid null-terminated string or is set to NULL, +/// - if the codec is kStandardMethodCallResponse, +/// stderrordetails may be set to any StdMsgCodecValue or NULL. +/// - if the codec is kJSONMethodCallResponse, +/// jserrordetails may be set to any JSONMSgCodecValue or NULL. +struct ChannelObject { + enum ChannelCodec codec; + union { + char *string_value; struct { - size_t size; - double* array; - } float64array_value; + size_t binarydata_size; + uint8_t *binarydata; + }; + struct JSONMsgCodecValue jsonmsgcodec_value; + struct StdMsgCodecValue stdmsgcodec_value; struct { - size_t size; - struct MessageChannelValue* list; - } list_value; + char *method; + union { + struct StdMsgCodecValue stdarg; + struct JSONMsgCodecValue jsarg; + }; + }; struct { - size_t size; - struct MessageChannelValue* map; - } map_value; + bool success; + union { + struct StdMsgCodecValue stdresult; + struct JSONMsgCodecValue jsresult; + }; + char *errorcode; + char *errormessage; + union { + struct StdMsgCodecValue stderrordetails; + struct JSONMsgCodecValue jserrordetails; + }; + }; }; }; -struct MethodCall { - enum { - kStandardProtocol, - kJSONProtocol - } protocol; - char* method; - struct MessageChannelValue argument; -}; +/// A Callback that is called when a response to a platform message you send to flutter +/// arrives. "object" is the platform message decoded using the "codec" you gave to PlatformChannel_send, +/// "userdata" is the userdata you gave to PlatformChannel_send. +typedef int (*PlatformMessageResponseCallback)(struct ChannelObject *object, void *userdata); + + +/// decodes a platform message (represented by `buffer` and `size`) as the given codec, +/// and puts the result into object_out. +/// This method will (in some cases) dynamically allocate memory, +/// so you should always call PlatformChannel_free(object_out) when you don't need it anymore. +/// +/// Additionally, PlatformChannel_decode currently "borrows" from the buffer, so if the buffer +/// is freed by flutter, the contents of object_out will in many cases be bogus. +/// If you'd like object_out to be persistent and not depend on the lifetime of the buffer, +/// you'd have to manually deep-copy it. +int PlatformChannel_decode(uint8_t *buffer, size_t size, enum ChannelCodec codec, struct ChannelObject *object_out); + +/// Encodes a generic ChannelObject into a buffer (that is, too, allocated by PlatformChannel_encode) +/// A pointer to the buffer is put into buffer_out and the size of that buffer into size_out. +/// The lifetime of the buffer is independent of the ChannelObject, so contents of the ChannelObject +/// can be freed after the object was encoded. +int PlatformChannel_encode(struct ChannelObject *object, uint8_t **buffer_out, size_t *size_out); + +/// Encodes a generic ChannelObject (anything, string/binary codec or Standard/JSON Method Calls and responses) as a platform message +/// and sends it to flutter on channel `channel` +/// When flutter responds to this message, it is automatically decoded using the codec given in `response_codec`. +/// Then, on_response is called with the decoded ChannelObject and the userdata as an argument. +/// Flutter _should_ always respond to platform messages, so it's okay if not calling your handler would cause a memory leak +/// (since that should never happen) +/// userdata can be NULL. +int PlatformChannel_send(char *channel, struct ChannelObject *object, enum ChannelCodec response_codec, PlatformMessageResponseCallback on_response, void *userdata); + +/// Encodes a StandardMethodCodec method call as a platform message and sends it to flutter +/// on channel `channel`. This is just a wrapper around PlatformChannel_send +/// that builds the ChannelObject for you. +/// The response_codec is kStandardMethodCallResponse. userdata can be NULL. +int PlatformChannel_stdcall(char *channel, char *method, struct StdMsgCodecValue *argument, PlatformMessageResponseCallback on_response, void *userdata); + +/// Encodes a JSONMethodCodec method call as a platform message and sends it to flutter +/// on channel `channel`. This is just a wrapper around PlatformChannel_send +/// that builds the ChannelObject for you. +/// The response_codec is kJSONMethodCallResponse. userdata can be NULL. +int PlatformChannel_jsoncall(char *channel, char *method, struct JSONMsgCodecValue *argument, PlatformMessageResponseCallback on_response, void *userdata); + +/// Responds to a platform message. You can (of course) only respond once to a platform message, +/// i.e. a FlutterPlatformMessageResponseHandle can only be used once. +/// The codec of `response` can be any of the available codecs. +int PlatformChannel_respond(FlutterPlatformMessageResponseHandle *handle, struct ChannelObject *response); + +/// Tells flutter that the platform message that was sent to you was not handled. +/// (for example, there's no plugin that is using this channel, or there is a plugin +/// but it doesn't want to respond.) +/// You should always use this instead of not replying to a platform message, since not replying could cause a memory leak. +/// When flutter receives this response, it will throw a MissingPluginException. +/// For most channel used by the ServicesPlugin, this is not too bad since it +/// specifies many of the channels used as OptionalMethodChannels. (which will silently catch the MissingPluginException) +int PlatformChannel_respondNotImplemented(FlutterPlatformMessageResponseHandle *handle); + +/// Tells flutter that the method that was called caused an error. +/// errorcode MUST be non-null, errormessage can be null. +/// when codec is one of kStandardMethodCall, kStandardMethodCallResponse, kStandardMessageCodec: +/// errordetails must either be NULL or a pointer to a struct StdMsgCodecValue. +/// when codec is one of kJSONMethodCall, kJSONMethodCallResponse or kJSONMessageCodec: +/// errordetails must either be NULL or a pointer to a struct JSONMsgCodecValue. +int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, enum ChannelCodec codec, char *errorcode, char *errormessage, void *errordetails); + +/// frees a ChannelObject that was decoded using PlatformChannel_decode. +/// not freeing ChannelObjects may result in a memory leak. +int PlatformChannel_free(struct ChannelObject *object); -bool PlatformChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, size_t* psize); -bool PlatformChannel_writeValueToBuffer(struct MessageChannelValue *value, uint8_t **pbuffer); -bool PlatformChannel_decodeValue(uint8_t** pbuffer, size_t* buffer_remaining, struct MessageChannelValue* value); -bool PlatformChannel_freeValue(struct MessageChannelValue* p_value); -bool PlatformChannel_sendMessage(char *channel, struct MessageChannelValue *argument); -bool PlatformChannel_decodeMessage(size_t buffer_size, uint8_t *buffer, struct MethodCall **presult); -bool PlatformChannel_respond(FlutterPlatformMessageResponseHandle *response_handle, struct MessageChannelValue *response); -bool PlatformChannel_call(char *channel, char *method, struct MessageChannelValue *argument); -bool PlatformChannel_decodeMethodCall(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); -bool PlatformChannel_freeMethodCall(struct MethodCall **pmethodcall); +int json_findKey(struct JSONMsgCodecValue *object, char *key); +struct JSONMsgCodecValue *json_get(struct JSONMsgCodecValue *object, char *key); #endif \ No newline at end of file diff --git a/src/pluginregistry.c b/src/pluginregistry.c index d3e960ba..89feb831 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -4,150 +4,143 @@ #include "platformchannel.h" #include "pluginregistry.h" +// hardcoded plugin headers +#include "services-plugin.h" +#include "testplugin.h" + + + +struct ChannelObjectReceiverData { + char *channel; + enum ChannelCodec codec; + ChannelObjectReceiveCallback callback; +}; struct FlutterPiPluginRegistry { struct FlutterPiPlugin *plugins; size_t plugin_count; - struct FlutterPiPluginRegistryCallbackListElement *callbacks; + struct ChannelObjectReceiverData *callbacks; + size_t callbacks_size; }; -struct FlutterPiPluginRegistryCallbackListElement { // hopefully that full type name is not used very often. - struct FlutterPiPluginRegistryCallbackListElement *next; - char *channel; - void *userdata; - bool is_methodcall; - union { - FlutterPiMethodCallCallback methodcall_callback; - FlutterPiPlatformMessageCallback message_callback; - }; +struct FlutterPiPlugin hardcoded_plugins[] = { + {.name = "services", .init = Services_init, .deinit = Services_deinit}, + {.name = "testplugin", .init = TestPlugin_init, .deinit = TestPlugin_deinit} }; +struct FlutterPiPluginRegistry *pluginregistry; -int PluginRegistry_init(struct FlutterPiPluginRegistry **pregistry) { +int PluginRegistry_init() { int ok; - *pregistry = NULL; - *pregistry = malloc(sizeof(struct FlutterPiPluginRegistry)); - if (!(*pregistry)) return ENOMEM; - - struct FlutterPiPluginRegistry *registry = *pregistry; - registry->plugins = hardcoded_plugins; - registry->plugin_count = sizeof(hardcoded_plugins) / sizeof(struct FlutterPiPlugin); - - // load dynamic plugins - // nothing for now + pluginregistry = malloc(sizeof(struct FlutterPiPluginRegistry)); + if (!pluginregistry) return ENOMEM; + pluginregistry->callbacks_size = 20; + pluginregistry->callbacks = calloc(pluginregistry->callbacks_size, sizeof(struct ChannelObjectReceiverData)); + + pluginregistry->plugins = hardcoded_plugins; + pluginregistry->plugin_count = sizeof(hardcoded_plugins) / sizeof(struct FlutterPiPlugin); - for (int i = 0; i < registry->plugin_count; i++) { - ok = registry->plugins[i].init(registry, &(registry->plugins[i].userdata)); - if (ok != 0) return ok; - } - - return 0; -} -int PluginRegistry_onPlatformMessage(struct FlutterPiPluginRegistry *registry, FlutterPlatformMessage *message) { - struct FlutterPiPluginRegistryCallbackListElement *element = registry->callbacks; + // insert code for dynamically loading plugins here - for (element = registry->callbacks; element != NULL; element = element->next) - if (strcmp(element->channel, message->channel) == 0) break; - - if (element != NULL) { - if (element->is_methodcall) { - // try decode platform message as method call - struct MethodCall *methodcall = NULL; - bool ok = PlatformChannel_decodeMethodCall(message->message_size, message->message, &methodcall); - if (!ok) return EBADMSG; - - element->methodcall_callback(element->userdata, message->channel, methodcall); - - PlatformChannel_freeMethodCall(&methodcall); - } else { - element->message_callback(element->userdata, message); + // call all the init methods for all plugins + for (int i = 0; i < pluginregistry->plugin_count; i++) { + if (pluginregistry->plugins[i].init) { + ok = pluginregistry->plugins[i].init(); + if (ok != 0) return ok; } } return 0; } -int PluginRegistry_setPlatformMessageHandler(struct FlutterPiPluginRegistry *registry, char *channel, - FlutterPiPlatformMessageCallback callback, void *userdata) { - - struct FlutterPiPluginRegistryCallbackListElement *element; - for (element = registry->callbacks; element != NULL; element = element->next) - if (strcmp(element->channel, channel) == 0) break; - - if (element != NULL) { - // change the behaviour of the existing handler - - element->is_methodcall = false; - element->message_callback = callback; - element->userdata = userdata; - return 0; - } else { - // new handler - - element = malloc(sizeof(struct FlutterPiPluginRegistryCallbackListElement)); - if (!element) return ENOMEM; - - element->channel = calloc(strlen(channel) +1, sizeof(char)); - if (!element->channel) return ENOMEM; +int PluginRegistry_onPlatformMessage(FlutterPlatformMessage *message) { + struct ChannelObject object; + int ok; - strcpy(element->channel, channel); + for (int i = 0; i < pluginregistry->callbacks_size; i++) { + if ((pluginregistry->callbacks[i].callback) && (strcmp(pluginregistry->callbacks[i].channel, message->channel) == 0)) { + ok = PlatformChannel_decode((uint8_t*) message->message, message->message_size, pluginregistry->callbacks[i].codec, &object); + if (ok != 0) return ok; - element->is_methodcall = false; - element->message_callback = callback; - element->userdata = userdata; - element->next = registry->callbacks; + pluginregistry->callbacks[i].callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); - registry->callbacks = element; + PlatformChannel_free(&object); + return 0; + } } - return 0; -} -int PluginRegistry_setMethodCallHandler(struct FlutterPiPluginRegistry *registry, char *channel, - FlutterPiMethodCallCallback callback, void *userdata) { - struct FlutterPiPluginRegistryCallbackListElement *element; + // we didn't find a callback for the specified channel. + // just respond with a null buffer to tell the VM-side + // that the feature is not implemented. - for (element = registry->callbacks; element != NULL; element = element->next) - if (strcmp(element->channel, channel) == 0) break; + return PlatformChannel_respondNotImplemented((FlutterPlatformMessageResponseHandle *) message->response_handle); +} +int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelObjectReceiveCallback callback) { + /// the index in 'callback' of the ChannelObjectReceiverData that will be added / updated. + int index = -1; + + /// find the index with channel name 'channel', or else, the first unoccupied index. + for (int i = 0; i < pluginregistry->callbacks_size; i++) { + if (pluginregistry->callbacks[i].channel == NULL) { + if (index == -1) { + index = i; + } + } else if (strcmp(channel, pluginregistry->callbacks[i].channel) == 0) { + index = i; + break; + } + } - if (element != NULL) { - // change the behaviour of the existing handler - - element->is_methodcall = true; - element->methodcall_callback = callback; - element->userdata = userdata; - return 0; - } else { - // new handler - - element = malloc(sizeof(struct FlutterPiPluginRegistryCallbackListElement)); - if (!element) return ENOMEM; - - element->channel = calloc(strlen(channel) +1, sizeof(char)); - if (!element->channel) return ENOMEM; - - strcpy(element->channel, channel); - - element->is_methodcall = true; - element->methodcall_callback = callback; - element->userdata = userdata; - element->next = registry->callbacks; + /// no matching or unoccupied index found. + if (index == -1) { + if (!callback) return 0; + + /// expand array + size_t currentsize = pluginregistry->callbacks_size * sizeof(struct ChannelObjectReceiverData); + + pluginregistry->callbacks = realloc(pluginregistry->callbacks, 2 * currentsize); + memset(&pluginregistry->callbacks[pluginregistry->callbacks_size], currentsize, 0); + + index = pluginregistry->callbacks_size; + pluginregistry->callbacks_size = 2*pluginregistry->callbacks_size; + } - registry->callbacks = element; + if (callback) { + char *channelCopy = malloc(strlen(channel) +1); + if (!channelCopy) return ENOMEM; + strcpy(channelCopy, channel); + + pluginregistry->callbacks[index].channel = channelCopy; + pluginregistry->callbacks[index].codec = codec; + pluginregistry->callbacks[index].callback = callback; + } else if (pluginregistry->callbacks[index].callback) { + free(pluginregistry->callbacks[index].channel); + pluginregistry->callbacks[index].channel = NULL; + pluginregistry->callbacks[index].callback = NULL; } return 0; + } -int PluginRegistry_deinit(struct FlutterPiPluginRegistry **pregistry) { - struct FlutterPiPluginRegistryCallbackListElement *element = (*pregistry)->callbacks, *t = NULL; - - while (element != NULL) { - free(element->channel); +int PluginRegistry_deinit() { + int i, ok; + + /// call each plugins 'deinit' + for (i = 0; i < pluginregistry->plugin_count; i++) { + if (pluginregistry->plugins[i].deinit) { + ok = pluginregistry->plugins[i].deinit(); + if (ok != 0) return ok; + } + } - t = element; - element = element->next; - free(t); + /// free all the channel names from the callback list. + for (int i=0; i < pluginregistry->callbacks_size; i++) { + if (pluginregistry->callbacks[i].channel) + free(pluginregistry->callbacks[i].channel); } - free(*pregistry); - *pregistry = NULL; -} \ No newline at end of file + /// free the rest + free(pluginregistry->callbacks); + free(pluginregistry); + pluginregistry = NULL; +} diff --git a/src/pluginregistry.h b/src/pluginregistry.h index fd3a13d2..858c0943 100644 --- a/src/pluginregistry.h +++ b/src/pluginregistry.h @@ -1,31 +1,31 @@ +#ifndef FLUTTER_PI_REGISTRY_H_ +#define FLUTTER_PI_REGISTRY_H_ 1 + #include #include +#include #include "platformchannel.h" -#ifndef FLUTTER_PI_REGISTRY_H_ -#define FLUTTER_PI_REGISTRY_H_ +typedef int (*InitDeinitCallback)(void); +typedef int (*ChannelObjectReceiveCallback)(char*, struct ChannelObject*, FlutterPlatformMessageResponseHandle *responsehandle); -typedef bool (*FlutterPiPluginRegistryCallback)(struct FlutterPiPluginRegistry *registry, void **userdata); -typedef bool (*FlutterPiMethodCallCallback)(void *userdata, char *channel, struct MethodCall *methodcall); -typedef bool (*FlutterPiPlatformMessageCallback)(void *userdata, FlutterPlatformMessage *message); struct FlutterPiPluginRegistry; struct FlutterPiPlugin { const char const* name; - FlutterPiPluginRegistryCallback init; - FlutterPiPluginRegistryCallback deinit; - void *userdata; + InitDeinitCallback init; + InitDeinitCallback deinit; }; -const struct FlutterPiPlugin hardcoded_plugins[] = { - {.name="connectivity", .init=NULL, .deinit=NULL, .userdata=NULL} -}; -extern int PluginRegistry_init(struct FlutterPiPluginRegistry **registry); -extern int PluginRegistry_onMethodCall(struct FlutterPiPluginRegistry *registry, char *channel, struct MethodCall *methodcall); -extern int PluginRegistry_setMethodCallHandler(struct FlutterPiPluginRegistry *registry, char *methodchannel, - FlutterPiMethodCallCallback callback, void *userdata); -extern int PluginRegistry_deinit(struct FlutterPiPluginRegistry **registry); +extern int hardcoded_plugins_count; +extern struct FlutterPiPlugin hardcoded_plugins[]; +extern struct FlutterPiPluginRegistry *pluginregistry; + +int PluginRegistry_init(); +int PluginRegistry_onPlatformMessage(FlutterPlatformMessage *message); +int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelObjectReceiveCallback callback); +int PluginRegistry_deinit(); #endif \ No newline at end of file diff --git a/src/services-plugin.c b/src/services-plugin.c new file mode 100644 index 00000000..2d4915e3 --- /dev/null +++ b/src/services-plugin.c @@ -0,0 +1,162 @@ +#include +#include + +#include "pluginregistry.h" +#include "services-plugin.h" + +struct { + char label[256]; + uint32_t primaryColor; // ARGB8888 (blue is the lowest byte) + char isolateId[32]; +} ServicesPlugin = {0}; + + +int Services_onReceiveNavigation(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + return PlatformChannel_respondNotImplemented(responsehandle); +} + +int Services_onReceiveIsolate(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + memset(&(ServicesPlugin.isolateId), sizeof(ServicesPlugin.isolateId), 0); + memcpy(ServicesPlugin.isolateId, object->binarydata, object->binarydata_size); + + return PlatformChannel_respondNotImplemented(responsehandle); +} + +int Services_onReceivePlatform(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + struct JSONMsgCodecValue *value; + struct JSONMsgCodecValue *arg = &(object->jsarg); + int ok; + + if (strcmp(object->method, "Clipboard.setData") == 0) { + /* + * Clipboard.setData(Map data) + * Places the data from the text entry of the argument, + * which must be a Map, onto the system clipboard. + */ + } else if (strcmp(object->method, "Clipboard.getData") == 0) { + /* + * Clipboard.getData(String format) + * Returns the data that has the format specified in the argument + * from the system clipboard. The only currently supported is "text/plain". + * The result is a Map with a single key, "text". + */ + } else if (strcmp(object->method, "HapticFeedback.vibrate") == 0) { + /* + * HapticFeedback.vibrate(void) + * Triggers a system-default haptic response. + */ + } else if (strcmp(object->method, "SystemSound.play") == 0) { + /* + * SystemSound.play(String soundName) + * Triggers a system audio effect. The argument must + * be a String describing the desired effect; currently only "click" is + * supported. + */ + } else if (strcmp(object->method, "SystemChrome.setPreferredOrientations") == 0) { + /* + * SystemChrome.setPreferredOrientations(DeviceOrientation[]) + * Informs the operating system of the desired orientation of the display. The argument is a [List] of + * values which are string representations of values of the [DeviceOrientation] enum. + * + * enum DeviceOrientation { + * portraitUp, landscapeLeft, portraitDown, landscapeRight + * } + */ + } else if (strcmp(object->method, "SystemChrome.setApplicationSwitcherDescription") == 0) { + /* + * SystemChrome.setApplicationSwitcherDescription(Map description) + * Informs the operating system of the desired label and color to be used + * to describe the application in any system-level application lists (e.g application switchers) + * The argument is a Map with two keys, "label" giving a string description, + * and "primaryColor" giving a 32 bit integer value (the lower eight bits being the blue channel, + * the next eight bits being the green channel, the next eight bits being the red channel, + * and the high eight bits being set, as from Color.value for an opaque color). + * The "primaryColor" can also be zero to indicate that the system default should be used. + */ + + /* + value = json_get(arg, "label"); + if (value && (value->type == kJSString)) + snprintf(ServicesPlugin.label, sizeof(ServicesPlugin.label), "%s", value->string_value); + + printf("ServicesPlugin responding with true\n"); + return PlatformChannel_respond(responsehandle, &(struct ChannelObject) { + .codec = kStringCodec, + .string_value = "[true]" + }); + */ + } else if (strcmp(object->method, "SystemChrome.setEnabledSystemUIOverlays") == 0) { + /* + * SystemChrome.setEnabledSystemUIOverlays(List overlays) + * Specifies the set of system overlays to have visible when the application + * is running. The argument is a List of values which are + * string representations of values of the SystemUIOverlay enum. + * + * enum SystemUIOverlay { + * top, bottom + * } + * + */ + } else if (strcmp(object->method, "SystemChrome.restoreSystemUIOverlays") == 0) { + /* + * SystemChrome.restoreSystemUIOverlays(void) + */ + } else if (strcmp(object->method, "SystemChrome.setSystemUIOverlayStyle") == 0) { + /* + * SystemChrome.setSystemUIOverlayStyle(struct SystemUIOverlayStyle) + * + * enum Brightness: + * light, dark + * + * struct SystemUIOverlayStyle: + * systemNavigationBarColor: null / uint32 + * statusBarColor: null / uint32 + * statusBarIconBrightness: null / Brightness + * statusBarBrightness: null / Brightness + * systemNavigationBarIconBrightness: null / Brightness + */ + } else if (strcmp(object->method, "SystemNavigator.pop")) { + printf("flutter requested application exit\n"); + } + + return PlatformChannel_respondNotImplemented(responsehandle); +} + +int Services_onReceiveAccessibility(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + return PlatformChannel_respondNotImplemented(responsehandle); +} + + +int Services_init(void) { + int ok; + + ok = PluginRegistry_setReceiver("flutter/navigation", kJSONMethodCall, Services_onReceiveNavigation); + if (ok != 0) { + printf("Could not set flutter/navigation ChannelObject receiver: %s\n", strerror(ok)); + return ok; + } + + ok = PluginRegistry_setReceiver("flutter/isolate", kBinaryCodec, Services_onReceiveIsolate); + if (ok != 0) { + printf("Could not set flutter/isolate ChannelObject receiver: %s\n", strerror(ok)); + return ok; + } + + ok = PluginRegistry_setReceiver("flutter/platform", kJSONMethodCall, Services_onReceivePlatform); + if (ok != 0) { + printf("Could not set flutter/platform ChannelObject receiver: %s\n", strerror(ok)); + return ok; + } + + ok = PluginRegistry_setReceiver("flutter/accessibility", kBinaryCodec, Services_onReceiveAccessibility); + if (ok != 0) { + printf("Could not set flutter/accessibility ChannelObject receiver: %s\n", strerror(ok)); + return ok; + } + + printf("Initialized Services plugin.\n"); +} + +int Services_deinit(void) { + printf("Deinitialized Services plugin.\n"); +} \ No newline at end of file diff --git a/src/services-plugin.h b/src/services-plugin.h new file mode 100644 index 00000000..0d17111c --- /dev/null +++ b/src/services-plugin.h @@ -0,0 +1,10 @@ +#ifndef _SERVICES_PLUGIN_H +#define _SERVICES_PLUGIN_H + +#include +#include + +int Services_init(void); +int Services_deinit(void); + +#endif \ No newline at end of file diff --git a/src/testplugin.c b/src/testplugin.c new file mode 100644 index 00000000..26dc9ee6 --- /dev/null +++ b/src/testplugin.c @@ -0,0 +1,299 @@ +#include +#include +#include + +#include "pluginregistry.h" + +#define TESTPLUGIN_CHANNEL_JSON "plugins.flutter-pi.io/testjson" +#define TESTPLUGIN_CHANNEL_STD "plugins.flutter-pi.io/teststd" +#define INDENT_STRING " " + +int __printJSON(struct JSONMsgCodecValue *value, int indent) { + switch (value->type) { + case kJSNull: + printf("null"); + break; + case kJSTrue: + printf("true"); + break; + case kJSFalse: + printf("false"); + break; + case kJSNumber: + printf("%f", value->number_value); + break; + case kJSString: + printf("\"%s\"", value->string_value); + break; + case kJSArray: + printf("[\n"); + for (int i = 0; i < value->size; i++) { + printf("%.*s", indent + 2, INDENT_STRING); + __printJSON(&(value->array[i]), indent + 2); + if (i+1 != value->size) printf(",\n", indent + 2, INDENT_STRING); + } + printf("\n%.*s]", indent, INDENT_STRING); + break; + case kJSObject: + printf("{\n"); + for (int i = 0; i < value->size; i++) { + printf("%.*s\"%s\": ", indent + 2, INDENT_STRING, value->keys[i]); + __printJSON(&(value->values[i]), indent + 2); + if (i+1 != value->size) printf(",\n", indent +2, INDENT_STRING); + } + printf("\n%.*s}", indent, INDENT_STRING); + break; + default: break; + } + + return 0; +} +int printJSON(struct JSONMsgCodecValue *value, int indent) { + printf("%.*s", indent, INDENT_STRING); + __printJSON(value, indent); + printf("\n"); +} +int __printStd(struct StdMsgCodecValue *value, int indent) { + switch (value->type) { + case kNull: + printf("null"); + break; + case kTrue: + printf("true"); + break; + case kFalse: + printf("false"); + break; + case kInt32: + printf("%" PRIi32, value->int32_value); + break; + case kInt64: + printf("%" PRIi64, value->int64_value); + break; + case kFloat64: + printf("%lf", value->float64_value); + break; + case kString: + case kLargeInt: + printf("\"%s\"", value->string_value); + break; + case kUInt8Array: + printf("(uint8_t) ["); + for (int i = 0; i < value->size; i++) { + printf("0x%02X", value->uint8array[i]); + if (i + 1 != value->size) printf(", "); + } + printf("]"); + break; + case kInt32Array: + printf("(int32_t) ["); + for (int i = 0; i < value->size; i++) { + printf("%" PRIi32, value->int32array[i]); + if (i + 1 != value->size) printf(", "); + } + printf("]"); + break; + case kInt64Array: + printf("(int64_t) ["); + for (int i = 0; i < value->size; i++) { + printf("%" PRIi64, value->int64array[i]); + if (i + 1 != value->size) printf(", "); + } + printf("]"); + break; + case kFloat64Array: + printf("(double) ["); + for (int i = 0; i < value->size; i++) { + printf("%ld", value->float64array[i]); + if (i + 1 != value->size) printf(", "); + } + printf("]"); + break; + case kList: + printf("[\n"); + for (int i = 0; i < value->size; i++) { + printf("%.*s", indent + 2, INDENT_STRING); + __printStd(&(value->list[i]), indent + 2); + if (i + 1 != value->size) printf(",\n"); + } + printf("\n%.*s]", indent, INDENT_STRING); + break; + case kMap: + printf("{\n"); + for (int i = 0; i < value->size; i++) { + printf("%.*s", indent + 2, INDENT_STRING); + __printStd(&(value->keys[i]), indent + 2); + printf(": "); + __printStd(&(value->values[i]), indent + 2); + if (i + 1 != value->size) printf(",\n"); + } + printf("\n%.*s}", indent, INDENT_STRING); + break; + default: + break; + } +} +int printStd(struct StdMsgCodecValue *value, int indent) { + printf("%.*s", indent, INDENT_STRING); + __printStd(value, indent); + printf("\n"); +} + +#undef INDENT_STRING + + +int TestPlugin_onReceiveResponseJSON(struct ChannelObject *object, void *userdata) { + uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); + free(userdata); + + if (object->success) { + printf("TestPlugin_onReceiveResponseJSON(dt: %lluns)\n" + " success\n" + " result:\n", dt); + printJSON(&object->jsresult, 4); + } else { + printf("TestPlugin_onReceiveResponseJSON(dt: %lluns)\n", dt); + printf(" failure\n" + " error code: %s\n" + " error message: %s\n" + " error details:\n", object->errorcode, (object->errormessage != NULL) ? object->errormessage : "null"); + printJSON(&object->jsresult, 4); + } + + return 0; +} +int TestPlugin_sendJSON() { + uint64_t* time = malloc(sizeof(uint64_t)); + *time = FlutterEngineGetCurrentTime(); + + char *method = "test"; + struct JSONMsgCodecValue argument = { + .type = kJSObject, + .size = 5, + .keys = (char*[]) { + "key1", + "key2", + "key3", + "key4", + "array" + }, + .values = (struct JSONMsgCodecValue[]) { + {.type = kJSString, .string_value = "value1"}, + {.type = kJSTrue}, + {.type = kJSNumber, .number_value = -1000}, + {.type = kJSNumber, .number_value = -5.0005}, + {.type = kJSArray, .size = 2, .array = (struct JSONMsgCodecValue[]) { + {.type = kJSString, .string_value = "array1"}, + {.type = kJSNumber, .number_value = 2} + }} + }, + }; + + int ok = PlatformChannel_jsoncall(TESTPLUGIN_CHANNEL_JSON, method, &argument, TestPlugin_onReceiveResponseJSON, time); + if (ok != 0) { + printf("Could not MethodCall JSON: %s\n", strerror(ok)); + } +} +int TestPlugin_onReceiveResponseStd(struct ChannelObject *object, void *userdata) { + uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); + free(userdata); + + if (object->success) { + printf("TestPlugin_onReceiveResponseStd(dt: %lluns)\n" + " success\n" + " result:\n", dt); + printStd(&object->stdresult, 4); + } else { + printf("TestPlugin_onReceiveResponseStd(dt: %lluns)\n", dt); + printf(" failure\n" + " error code: %s\n" + " error message: %s\n" + " error details:\n", object->errorcode, (object->errormessage != NULL) ? object->errormessage : "null"); + printStd(&object->stdresult, 4); + } + + return 0; +} +int TestPlugin_sendStd() { + uint64_t *time = malloc(sizeof(uint64_t)); + *time = FlutterEngineGetCurrentTime(); + + char *method = "test"; + struct StdMsgCodecValue argument = { + .type = kMap, + .size = 7, + .keys = (struct StdMsgCodecValue[]) { + {.type = kString, .string_value = "key1"}, + {.type = kString, .string_value = "key2"}, + {.type = kString, .string_value = "key3"}, + {.type = kString, .string_value = "key4"}, + {.type = kInt32, .int32_value = 5}, + {.type = kString, .string_value = "timestamp"}, + {.type = kString, .string_value = "array"} + }, + .values = (struct StdMsgCodecValue[]) { + {.type = kString, .string_value = "value1"}, + {.type = kTrue}, + {.type = kInt32, .int32_value = -1000}, + {.type = kFloat64, .float64_value = -5.0005}, + {.type = kUInt8Array, .uint8array = (uint8_t[]) {0x00, 0x01, 0x02, 0x03, 0xFF}, .size = 5}, + {.type = kInt64, .int64_value = *time & 0x7FFFFFFFFFFFFFFF}, + {.type = kList, .size = 2, .list = (struct StdMsgCodecValue[]) { + {.type = kString, .string_value = "array1"}, + {.type = kInt32, .int32_value = 2} + }} + }, + }; + + PlatformChannel_stdcall(TESTPLUGIN_CHANNEL_STD, method, &argument, TestPlugin_onReceiveResponseStd, time); +} + + +int TestPlugin_onReceiveJSON(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + printf("TestPlugin_onReceiveJSON(channel: %s)\n" + " method: %s\n" + " args: \n", channel, object->method); + printJSON(&(object->jsarg), 4); + + TestPlugin_sendJSON(); + + return PlatformChannel_respond(responsehandle, &(struct ChannelObject) { + .codec = kJSONMethodCallResponse, + .success = true, + .jsresult = { + .type = kJSTrue + } + }); +} +int TestPlugin_onReceiveStd(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle) { + printf("TestPlugin_onReceiveStd(channel: %s)\n" + " method: %s\n" + " args: \n", channel, object->method); + + printStd(&(object->stdarg), 4); + + TestPlugin_sendStd(); + + return PlatformChannel_respond( + responsehandle, + &(struct ChannelObject) { + .codec = kStandardMethodCallResponse, + .success = true, + .stdresult = { + .type = kTrue + } + } + ); +} + + +int TestPlugin_init(void) { + printf("Initializing Testplugin\n"); + PluginRegistry_setReceiver(TESTPLUGIN_CHANNEL_JSON, kJSONMethodCall, TestPlugin_onReceiveJSON); + PluginRegistry_setReceiver(TESTPLUGIN_CHANNEL_STD, kStandardMethodCall, TestPlugin_onReceiveStd); + return 0; +} +int TestPlugin_deinit(void) { + printf("Deinitializing Testplugin\n"); + return 0; +} \ No newline at end of file diff --git a/src/testplugin.h b/src/testplugin.h new file mode 100644 index 00000000..fdf3b3d2 --- /dev/null +++ b/src/testplugin.h @@ -0,0 +1,10 @@ +#ifndef _TEST_PLUGIN_H +#define _TEST_PLUGIN_H + +#include +#include + +extern int TestPlugin_init(void); +extern int TestPlugin_deinit(void); + +#endif \ No newline at end of file From 6c5d10f4f940d97748ae82c1088a9dc455a89e41 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Sun, 29 Sep 2019 14:00:44 +0200 Subject: [PATCH 4/4] Documentation, utilities, missingplugin handling - added more code documentation - added jsobject_get, jsvalue_equals, stdmap_get, stdvalue_equals - added kNotImplemented codec for detecting when a flutter-side platform message handler is not present, or for notifying there's no plugin listening on that channel - made testplugin check for kNotImplemented responses --- src/platformchannel.c | 173 ++++++++++++++++++++++++++++++++++++++++-- src/platformchannel.h | 26 ++++++- src/pluginregistry.c | 82 ++++++++++---------- src/pluginregistry.h | 35 +++++++-- src/services-plugin.c | 12 +-- src/testplugin.c | 10 +++ 6 files changed, 276 insertions(+), 62 deletions(-) diff --git a/src/platformchannel.c b/src/platformchannel.c index 6533feb8..e7fefb81 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -690,7 +690,11 @@ int PlatformChannel_decode(uint8_t *buffer, size_t size, enum ChannelCodec codec uint8_t *buffer_cursor = buffer; size_t remaining = size; int ok; - + + if ((size == 0) && (buffer == NULL)) { + object_out->codec = kNotImplemented; + return 0; + } object_out->codec = codec; switch (codec) { @@ -818,6 +822,10 @@ int PlatformChannel_encode(struct ChannelObject *object, uint8_t **buffer_out, s *buffer_out = NULL; switch (object->codec) { + case kNotImplemented: + *size_out = 0; + *buffer_out = NULL; + return 0; case kStringCodec: size = strlen(object->string_value); break; @@ -1061,7 +1069,7 @@ int PlatformChannel_respond(FlutterPlatformMessageResponseHandle *handle, struct result = FlutterEngineSendPlatformMessageResponse(engine, (const FlutterPlatformMessageResponseHandle*) handle, (const uint8_t*) buffer, size); - free(buffer); + if (buffer != NULL) free(buffer); return (result == kSuccess) ? 0 : EINVAL; } @@ -1069,9 +1077,7 @@ int PlatformChannel_respondNotImplemented(FlutterPlatformMessageResponseHandle * return PlatformChannel_respond( (FlutterPlatformMessageResponseHandle *) handle, &(struct ChannelObject) { - .codec = kBinaryCodec, - .binarydata = NULL, - .binarydata_size = 0 + .codec = kNotImplemented }); } int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, enum ChannelCodec codec, char *errorcode, char *errormessage, void *errordetails) { @@ -1094,7 +1100,59 @@ int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, e } else return EINVAL; } -struct JSONMsgCodecValue *json_get(struct JSONMsgCodecValue *object, char *key) { +bool jsvalue_equals(struct JSONMsgCodecValue *a, struct JSONMsgCodecValue *b) { + if (a == b) return true; + if ((a == NULL) ^ (b == NULL)) return false; + if (a->type != b->type) return false; + + switch (a->type) { + case kJSNull: + case kJSTrue: + case kJSFalse: + return true; + case kJSNumber: + return a->number_value == b->number_value; + case kJSString: + return strcmp(a->string_value, b->string_value) == 0; + case kJSArray: + if (a->size != b->size) return false; + if (a->array == b->array) return true; + for (int i = 0; i < a->size; i++) + if (!jsvalue_equals(&a->array[i], &b->array[i])) + return false; + return true; + case kJSObject: + if (a->size != b->size) return false; + if ((a->keys == b->keys) && (a->values == b->values)) return true; + + bool _keyInBAlsoInA[a->size]; + memset(_keyInBAlsoInA, false, a->size * sizeof(bool)); + + for (int i = 0; i < a->size; i++) { + // The key we're searching for in b. + char *key = a->keys[i]; + + int j = 0; + while (j < a->size) { + while (_keyInBAlsoInA[j] && (j < a->size)) j++; // skip all keys with _keyInBAlsoInA set to true. + if (strcmp(key, b->keys[j]) != 0) j++; // if b->keys[j] is not equal to "key", continue searching + else { + _keyInBAlsoInA[j] = true; + + // the values of "key" in a and b must (of course) also be equivalent. + if (!jsvalue_equals(&a->values[i], &b->values[j])) return false; + break; + } + } + + // we did not find a->keys[i] in b. + if (j + 1 >= a->size) return false; + } + + return true; + } +} +struct JSONMsgCodecValue *jsobject_get(struct JSONMsgCodecValue *object, char *key) { int i; for (i=0; i < object->size; i++) if (strcmp(object->keys[i], key) == 0) break; @@ -1103,6 +1161,109 @@ struct JSONMsgCodecValue *json_get(struct JSONMsgCodecValue *object, char *key) if (i != object->size) return &(object->values[i]); return NULL; } +bool stdvalue_equals(struct StdMsgCodecValue *a, struct StdMsgCodecValue *b) { + if (a == b) return true; + if ((a == NULL) ^ (b == NULL)) return false; + if (a->type != b->type) return false; + + switch (a->type) { + case kNull: + case kTrue: + case kFalse: + return true; + case kInt32: + return a->int32_value == b->int32_value; + case kInt64: + return a->int64_value == b->int64_value; + case kLargeInt: + case kString: + return strcmp(a->string_value, b->string_value) == 0; + case kFloat64: + return a->float64_value == b->float64_value; + case kUInt8Array: + if (a->size != b->size) return false; + if (a->uint8array == b->uint8array) return true; + for (int i = 0; i < a->size; i++) + if (a->uint8array[i] != b->uint8array[i]) + return false; + return true; + case kInt32Array: + if (a->size != b->size) return false; + if (a->int32array == b->int32array) return true; + for (int i = 0; i < a->size; i++) + if (a->int32array[i] != b->int32array[i]) + return false; + return true; + case kInt64Array: + if (a->size != b->size) return false; + if (a->int64array == b->int64array) return true; + for (int i = 0; i < a->size; i++) + if (a->int64array[i] != b->int64array[i]) + return false; + return true; + case kFloat64Array: + if (a->size != b->size) return false; + if (a->float64array == b->float64array) return true; + for (int i = 0; i < a->size; i++) + if (a->float64array[i] != b->float64array[i]) + return false; + return true; + case kList: + // the order of list elements is important + if (a->size != b->size) return false; + if (a->list == b->list) return true; + + for (int i = 0; i < a->size; i++) + if (!stdvalue_equals(&(a->list[i]), &(b->list[i]))); + return false; + + return true; + case kMap: { + // the order is not important here, which makes it a bit difficult to compare + if (a->size != b->size) return false; + if ((a->keys == b->keys) && (a->values == b->values)) return true; + + // _keyInBAlsoInA[i] == true means that there's a key in a that matches b->keys[i] + // so if we're searching for a key in b, we can safely ignore / don't need to compare + // keys in b that have they're _keyInBAlsoInA set to true. + bool _keyInBAlsoInA[a->size]; + memset(_keyInBAlsoInA, false, a->size * sizeof(bool)); + + for (int i = 0; i < a->size; i++) { + // The key we're searching for in b. + struct StdMsgCodecValue *key = &(a->keys[i]); + + int j = 0; + while (j < a->size) { + while (_keyInBAlsoInA[j] && (j < a->size)) j++; // skip all keys with _keyInBAlsoInA set to true. + if (!stdvalue_equals(key, &(b->keys[j]))) j++; // if b->keys[j] is not equal to "key", continue searching + else { + _keyInBAlsoInA[j] = true; + + // the values of "key" in a and b must (of course) also be equivalent. + if (!stdvalue_equals(&(a->values[i]), &(b->values[j]))) return false; + break; + } + } + + // we did not find a->keys[i] in b. + if (j + 1 >= a->size) return false; + } + + return true; + } + default: return false; + } + + return false; +} +struct StdMsgCodecValue *stdmap_get(struct StdMsgCodecValue *map, struct StdMsgCodecValue *key) { + for (int i=0; i < map->size; i++) + if (stdvalue_equals(&map->keys[i], key)) + return &map->values[i]; + + return NULL; +} #undef __ALIGN4_REMAINING #undef __ALIGN8_REMAINING diff --git a/src/platformchannel.h b/src/platformchannel.h index 03e71131..254e9812 100644 --- a/src/platformchannel.h +++ b/src/platformchannel.h @@ -86,6 +86,7 @@ struct StdMsgCodecValue { /// These tell this API how it should encode ChannelObjects -> platform messages /// and how to decode platform messages -> ChannelObjects. enum ChannelCodec { + kNotImplemented, kStringCodec, kBinaryCodec, kJSONMessageCodec, @@ -98,6 +99,9 @@ enum ChannelCodec { /// Abstract Channel Object. /// Different properties are "valid" for different codecs: +/// kNotImplemented: +/// no values associated with this "codec". +/// this represents a platform message with no buffer and zero length. (so just an empty response) /// kStringCodec: /// - string_value is the raw byte data of a platform message, but with an additional null-byte at the end. /// kBinaryCodec: @@ -228,7 +232,25 @@ int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, e /// not freeing ChannelObjects may result in a memory leak. int PlatformChannel_free(struct ChannelObject *object); -int json_findKey(struct JSONMsgCodecValue *object, char *key); -struct JSONMsgCodecValue *json_get(struct JSONMsgCodecValue *object, char *key); +/// returns true if values a and b are equal. +/// for JS arrays, the order of the values is relevant +/// (so two arrays are only equal if the same values in appear in exactly same order) +/// for objects, the order of the entries is irrelevant. +bool jsvalue_equals(struct JSONMsgCodecValue *a, struct JSONMsgCodecValue *b); + +/// given a JS object as an argument, it searches for an entry with key "key" +/// and returns the value associated with it. +/// if the key is not found, returns NULL. +struct JSONMsgCodecValue *jsobject_get(struct JSONMsgCodecValue *object, char *key); + +/// StdMsgCodecValue equivalent of jsvalue_equals. +/// again, for lists, the order of values is relevant +/// for maps, it's not. +bool stdvalue_equals(struct StdMsgCodecValue *a, struct StdMsgCodecValue *b); + +/// StdMsgCodecValue equivalent of jsobject_get, just that the key can be +/// any arbitrary StdMsgCodecValue (and must not be a string as for jsobject_get) +struct StdMsgCodecValue *stdmap_get(struct StdMsgCodecValue *map, struct StdMsgCodecValue *key); + #endif \ No newline at end of file diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 89feb831..17284bcc 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -9,44 +9,46 @@ #include "testplugin.h" - struct ChannelObjectReceiverData { char *channel; enum ChannelCodec codec; ChannelObjectReceiveCallback callback; }; -struct FlutterPiPluginRegistry { +struct { struct FlutterPiPlugin *plugins; size_t plugin_count; struct ChannelObjectReceiverData *callbacks; size_t callbacks_size; -}; +} pluginregistry; +/// array of plugins that are statically included in flutter-pi. struct FlutterPiPlugin hardcoded_plugins[] = { {.name = "services", .init = Services_init, .deinit = Services_deinit}, + +#ifdef INCLUDE_TESTPLUGIN {.name = "testplugin", .init = TestPlugin_init, .deinit = TestPlugin_deinit} +#endif }; -struct FlutterPiPluginRegistry *pluginregistry; +size_t hardcoded_plugins_count; int PluginRegistry_init() { int ok; - pluginregistry = malloc(sizeof(struct FlutterPiPluginRegistry)); - if (!pluginregistry) return ENOMEM; + memset(&pluginregistry, 0, sizeof(pluginregistry)); - pluginregistry->callbacks_size = 20; - pluginregistry->callbacks = calloc(pluginregistry->callbacks_size, sizeof(struct ChannelObjectReceiverData)); + pluginregistry.callbacks_size = 20; + pluginregistry.callbacks = calloc(pluginregistry.callbacks_size, sizeof(struct ChannelObjectReceiverData)); - pluginregistry->plugins = hardcoded_plugins; - pluginregistry->plugin_count = sizeof(hardcoded_plugins) / sizeof(struct FlutterPiPlugin); + pluginregistry.plugins = hardcoded_plugins; + pluginregistry.plugin_count = sizeof(hardcoded_plugins) / sizeof(struct FlutterPiPlugin); // insert code for dynamically loading plugins here // call all the init methods for all plugins - for (int i = 0; i < pluginregistry->plugin_count; i++) { - if (pluginregistry->plugins[i].init) { - ok = pluginregistry->plugins[i].init(); + for (int i = 0; i < pluginregistry.plugin_count; i++) { + if (pluginregistry.plugins[i].init) { + ok = pluginregistry.plugins[i].init(); if (ok != 0) return ok; } } @@ -57,12 +59,12 @@ int PluginRegistry_onPlatformMessage(FlutterPlatformMessage *message) { struct ChannelObject object; int ok; - for (int i = 0; i < pluginregistry->callbacks_size; i++) { - if ((pluginregistry->callbacks[i].callback) && (strcmp(pluginregistry->callbacks[i].channel, message->channel) == 0)) { - ok = PlatformChannel_decode((uint8_t*) message->message, message->message_size, pluginregistry->callbacks[i].codec, &object); + for (int i = 0; i < pluginregistry.callbacks_size; i++) { + if ((pluginregistry.callbacks[i].callback) && (strcmp(pluginregistry.callbacks[i].channel, message->channel) == 0)) { + ok = PlatformChannel_decode((uint8_t*) message->message, message->message_size, pluginregistry.callbacks[i].codec, &object); if (ok != 0) return ok; - pluginregistry->callbacks[i].callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); + pluginregistry.callbacks[i].callback((char*) message->channel, &object, (FlutterPlatformMessageResponseHandle*) message->response_handle); PlatformChannel_free(&object); return 0; @@ -80,12 +82,12 @@ int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelOb int index = -1; /// find the index with channel name 'channel', or else, the first unoccupied index. - for (int i = 0; i < pluginregistry->callbacks_size; i++) { - if (pluginregistry->callbacks[i].channel == NULL) { + for (int i = 0; i < pluginregistry.callbacks_size; i++) { + if (pluginregistry.callbacks[i].channel == NULL) { if (index == -1) { index = i; } - } else if (strcmp(channel, pluginregistry->callbacks[i].channel) == 0) { + } else if (strcmp(channel, pluginregistry.callbacks[i].channel) == 0) { index = i; break; } @@ -96,13 +98,13 @@ int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelOb if (!callback) return 0; /// expand array - size_t currentsize = pluginregistry->callbacks_size * sizeof(struct ChannelObjectReceiverData); + size_t currentsize = pluginregistry.callbacks_size * sizeof(struct ChannelObjectReceiverData); - pluginregistry->callbacks = realloc(pluginregistry->callbacks, 2 * currentsize); - memset(&pluginregistry->callbacks[pluginregistry->callbacks_size], currentsize, 0); + pluginregistry.callbacks = realloc(pluginregistry.callbacks, 2 * currentsize); + memset(&pluginregistry.callbacks[pluginregistry.callbacks_size], currentsize, 0); - index = pluginregistry->callbacks_size; - pluginregistry->callbacks_size = 2*pluginregistry->callbacks_size; + index = pluginregistry.callbacks_size; + pluginregistry.callbacks_size = 2*pluginregistry.callbacks_size; } if (callback) { @@ -110,13 +112,13 @@ int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelOb if (!channelCopy) return ENOMEM; strcpy(channelCopy, channel); - pluginregistry->callbacks[index].channel = channelCopy; - pluginregistry->callbacks[index].codec = codec; - pluginregistry->callbacks[index].callback = callback; - } else if (pluginregistry->callbacks[index].callback) { - free(pluginregistry->callbacks[index].channel); - pluginregistry->callbacks[index].channel = NULL; - pluginregistry->callbacks[index].callback = NULL; + pluginregistry.callbacks[index].channel = channelCopy; + pluginregistry.callbacks[index].codec = codec; + pluginregistry.callbacks[index].callback = callback; + } else if (pluginregistry.callbacks[index].callback) { + free(pluginregistry.callbacks[index].channel); + pluginregistry.callbacks[index].channel = NULL; + pluginregistry.callbacks[index].callback = NULL; } return 0; @@ -126,21 +128,19 @@ int PluginRegistry_deinit() { int i, ok; /// call each plugins 'deinit' - for (i = 0; i < pluginregistry->plugin_count; i++) { - if (pluginregistry->plugins[i].deinit) { - ok = pluginregistry->plugins[i].deinit(); + for (i = 0; i < pluginregistry.plugin_count; i++) { + if (pluginregistry.plugins[i].deinit) { + ok = pluginregistry.plugins[i].deinit(); if (ok != 0) return ok; } } /// free all the channel names from the callback list. - for (int i=0; i < pluginregistry->callbacks_size; i++) { - if (pluginregistry->callbacks[i].channel) - free(pluginregistry->callbacks[i].channel); + for (int i=0; i < pluginregistry.callbacks_size; i++) { + if (pluginregistry.callbacks[i].channel) + free(pluginregistry.callbacks[i].channel); } /// free the rest - free(pluginregistry->callbacks); - free(pluginregistry); - pluginregistry = NULL; + free(pluginregistry.callbacks); } diff --git a/src/pluginregistry.h b/src/pluginregistry.h index 858c0943..67c32132 100644 --- a/src/pluginregistry.h +++ b/src/pluginregistry.h @@ -7,11 +7,31 @@ #include "platformchannel.h" +/// Callback for Initialization or Deinitialization. +/// Return value is 0 for success, or anything else for an error +/// (uses the errno error codes) typedef int (*InitDeinitCallback)(void); -typedef int (*ChannelObjectReceiveCallback)(char*, struct ChannelObject*, FlutterPlatformMessageResponseHandle *responsehandle); - -struct FlutterPiPluginRegistry; +/// A Callback that gets called when a platform message +/// arrives on a channel you registered it with. +/// channel is the method channel that received a platform message, +/// object is the object that is the result of automatically decoding +/// the platform message using the codec given to PluginRegistry_setReceiver. +/// BE AWARE that object->type can be kNotImplemented, REGARDLESS of the codec +/// passed to PluginRegistry_setReceiver. +typedef int (*ChannelObjectReceiveCallback)(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *responsehandle); + +/// details of a plugin for flutter-pi. +/// All plugins are initialized (i.e. get their "init" callbacks called) +/// when PluginRegistry_init() is called by flutter-pi. +/// In the init callback, you probably want to do stuff like +/// register callbacks for some method channels your plugin uses, +/// or dynamically allocate memory for your plugin if you need to. +/// PluginRegistry_init() and thus every plugins init is called +/// BEFORE the flutter engine is set up and running. The "engine" +/// global may even be NULL at the time "init" is called. Sending flutter messages +/// will probably cause the application to crash. +/// deinit is also called AFTER the engine is shut down. struct FlutterPiPlugin { const char const* name; InitDeinitCallback init; @@ -19,13 +39,14 @@ struct FlutterPiPlugin { }; -extern int hardcoded_plugins_count; -extern struct FlutterPiPlugin hardcoded_plugins[]; -extern struct FlutterPiPluginRegistry *pluginregistry; - int PluginRegistry_init(); int PluginRegistry_onPlatformMessage(FlutterPlatformMessage *message); + +/// Sets the callback that should be called when a platform message arrives on channel "channel", +/// and the codec used to automatically decode the platform message. +/// Call this method with NULL as the callback parameter to remove the current listener on that channel. int PluginRegistry_setReceiver(char *channel, enum ChannelCodec codec, ChannelObjectReceiveCallback callback); + int PluginRegistry_deinit(); #endif \ No newline at end of file diff --git a/src/services-plugin.c b/src/services-plugin.c index 2d4915e3..57413cc7 100644 --- a/src/services-plugin.c +++ b/src/services-plugin.c @@ -74,17 +74,17 @@ int Services_onReceivePlatform(char *channel, struct ChannelObject *object, Flut * The "primaryColor" can also be zero to indicate that the system default should be used. */ - /* - value = json_get(arg, "label"); + value = jsobject_get(arg, "label"); if (value && (value->type == kJSString)) snprintf(ServicesPlugin.label, sizeof(ServicesPlugin.label), "%s", value->string_value); - printf("ServicesPlugin responding with true\n"); return PlatformChannel_respond(responsehandle, &(struct ChannelObject) { - .codec = kStringCodec, - .string_value = "[true]" + .codec = kJSONMethodCallResponse, + .success = true, + .jsresult = { + .type = kNull + } }); - */ } else if (strcmp(object->method, "SystemChrome.setEnabledSystemUIOverlays") == 0) { /* * SystemChrome.setEnabledSystemUIOverlays(List overlays) diff --git a/src/testplugin.c b/src/testplugin.c index 26dc9ee6..f3ade9eb 100644 --- a/src/testplugin.c +++ b/src/testplugin.c @@ -145,6 +145,11 @@ int printStd(struct StdMsgCodecValue *value, int indent) { int TestPlugin_onReceiveResponseJSON(struct ChannelObject *object, void *userdata) { uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); free(userdata); + + if (object->codec == kNotImplemented) { + printf("channel " TESTPLUGIN_CHANNEL_JSON " not implented on flutter side\n"); + return 0; + } if (object->success) { printf("TestPlugin_onReceiveResponseJSON(dt: %lluns)\n" @@ -198,6 +203,11 @@ int TestPlugin_onReceiveResponseStd(struct ChannelObject *object, void *userdata uint64_t dt = FlutterEngineGetCurrentTime() - *((uint64_t*) userdata); free(userdata); + if (object->codec == kNotImplemented) { + printf("channel " TESTPLUGIN_CHANNEL_STD " not implented on flutter side\n"); + return 0; + } + if (object->success) { printf("TestPlugin_onReceiveResponseStd(dt: %lluns)\n" " success\n"