From 9b493757c2057d4486fe5b473f3918d14704a519 Mon Sep 17 00:00:00 2001 From: Todd Malsbary Date: Tue, 25 Oct 2016 10:30:12 -0700 Subject: [PATCH] [IOT-1116] Support heterogeneous array encoding. OCRepPayloads with values having only sequential integer names are encoded/decoded as CBOR arrays of mixed types. Change-Id: I2cdd87f9fb4b8473e00aefdd5bc5daa74941290f Bug: https://jira.iotivity.org/browse/IOT-1116 Signed-off-by: Todd Malsbary Reviewed-on: https://gerrit.iotivity.org/gerrit/13661 Tested-by: jenkins-iotivity Reviewed-by: Dan Mihai --- resource/csdk/stack/src/ocpayloadconvert.c | 115 +++++++++++++++++++---------- resource/csdk/stack/src/ocpayloadparse.c | 47 +++++++++++- resource/csdk/stack/test/cbortests.cpp | 83 +++++++++++++++++++++ 3 files changed, 205 insertions(+), 40 deletions(-) diff --git a/resource/csdk/stack/src/ocpayloadconvert.c b/resource/csdk/stack/src/ocpayloadconvert.c index af183b7..c8165f4 100644 --- a/resource/csdk/stack/src/ocpayloadconvert.c +++ b/resource/csdk/stack/src/ocpayloadconvert.c @@ -58,6 +58,7 @@ static int64_t OCConvertPresencePayload(OCPresencePayload *payload, uint8_t *out size_t *size); static int64_t OCConvertSecurityPayload(OCSecurityPayload *payload, uint8_t *outPayload, size_t *size); +static int64_t OCConvertSingleRepPayloadValue(CborEncoder *parent, const OCRepPayloadValue *value); static int64_t OCConvertSingleRepPayload(CborEncoder *parent, const OCRepPayload *payload); static int64_t OCConvertArray(CborEncoder *parent, const OCRepPayloadValueArray *valArray); @@ -563,17 +564,86 @@ exit: static int64_t OCConvertRepMap(CborEncoder *map, const OCRepPayload *payload) { int64_t err = CborNoError; - CborEncoder repMap; - err |= cbor_encoder_create_map(map, &repMap, CborIndefiniteLength); - VERIFY_CBOR_SUCCESS(TAG, err, "Failed creating rep map"); - err |= OCConvertSingleRepPayload(&repMap, payload); - VERIFY_CBOR_SUCCESS(TAG, err, "Failed converting single rep payload"); - err |= cbor_encoder_close_container(map, &repMap); - VERIFY_CBOR_SUCCESS(TAG, err, "Failed closing rep map"); + CborEncoder encoder; + OCRepPayloadValue *value = NULL; + size_t arrayLength = 0; + + // Encode payload as an array when value names are consecutive + // non-negative integers. Otherwise encode as a map. + arrayLength = 0; + for (value = payload->values; value; value = value->next) + { + char *endp; + long i = strtol(value->name, &endp, 0); + if (*endp != '\0' || i < 0 || arrayLength != (size_t)i) + { + break; + } + ++arrayLength; + } + + if (value) + { + err |= cbor_encoder_create_map(map, &encoder, CborIndefiniteLength); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed creating rep map"); + err |= OCConvertSingleRepPayload(&encoder, payload); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed converting single rep payload"); + err |= cbor_encoder_close_container(map, &encoder); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed closing rep map"); + } + else + { + err |= cbor_encoder_create_array(map, &encoder, arrayLength); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed creating rep map"); + for (value = payload->values; value; value = value->next) + { + err |= OCConvertSingleRepPayloadValue(&encoder, value); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed converting single rep value"); + } + err |= cbor_encoder_close_container(map, &encoder); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed closing rep map"); + } + exit: return err; } +static int64_t OCConvertSingleRepPayloadValue(CborEncoder *parent, const OCRepPayloadValue *value) +{ + int64_t err = CborNoError; + switch (value->type) + { + case OCREP_PROP_NULL: + err = cbor_encode_null(parent); + break; + case OCREP_PROP_INT: + err = cbor_encode_int(parent, value->i); + break; + case OCREP_PROP_DOUBLE: + err = cbor_encode_double(parent, value->d); + break; + case OCREP_PROP_BOOL: + err = cbor_encode_boolean(parent, value->b); + break; + case OCREP_PROP_STRING: + err = cbor_encode_text_string(parent, value->str, strlen(value->str)); + break; + case OCREP_PROP_BYTE_STRING: + err = cbor_encode_byte_string(parent, value->ocByteStr.bytes, value->ocByteStr.len); + break; + case OCREP_PROP_OBJECT: + err = OCConvertRepMap(parent, value->obj); + break; + case OCREP_PROP_ARRAY: + err = OCConvertArray(parent, &value->arr); + break; + default: + OIC_LOG_V(ERROR, TAG, "Invalid Prop type: %d", value->type); + break; + } + return err; +} + static int64_t OCConvertSingleRepPayload(CborEncoder *repMap, const OCRepPayload *payload) { int64_t err = CborNoError; @@ -583,36 +653,7 @@ static int64_t OCConvertSingleRepPayload(CborEncoder *repMap, const OCRepPayload err |= cbor_encode_text_string(repMap, value->name, strlen(value->name)); VERIFY_CBOR_SUCCESS(TAG, err, "Failed adding tag name"); - switch (value->type) - { - case OCREP_PROP_NULL: - err |= cbor_encode_null(repMap); - break; - case OCREP_PROP_INT: - err |= cbor_encode_int(repMap, value->i); - break; - case OCREP_PROP_DOUBLE: - err |= cbor_encode_double(repMap, value->d); - break; - case OCREP_PROP_BOOL: - err |= cbor_encode_boolean(repMap, value->b); - break; - case OCREP_PROP_STRING: - err |= cbor_encode_text_string(repMap, value->str, strlen(value->str)); - break; - case OCREP_PROP_BYTE_STRING: - err |= cbor_encode_byte_string(repMap, value->ocByteStr.bytes, value->ocByteStr.len); - break; - case OCREP_PROP_OBJECT: - err |= OCConvertRepMap(repMap, value->obj); - break; - case OCREP_PROP_ARRAY: - err |= OCConvertArray(repMap, &value->arr); - break; - default: - OIC_LOG_V(ERROR, TAG, "Invalid Prop type: %d", value->type); - break; - } + err |= OCConvertSingleRepPayloadValue(repMap, value); VERIFY_CBOR_SUCCESS(TAG, err, "Failed adding single rep value"); value = value->next; } diff --git a/resource/csdk/stack/src/ocpayloadparse.c b/resource/csdk/stack/src/ocpayloadparse.c index bb11a5a..758ec53 100644 --- a/resource/csdk/stack/src/ocpayloadparse.c +++ b/resource/csdk/stack/src/ocpayloadparse.c @@ -29,9 +29,9 @@ #include #include +#include "ocpayload.h" #include "oic_string.h" #include "oic_malloc.h" -#include "ocpayload.h" #include "ocpayloadcbor.h" #include "ocstackinternal.h" #include "payload_logging.h" @@ -40,6 +40,11 @@ #define TAG "OIC_RI_PAYLOADPARSE" +/* + * The length of UINT64_MAX as a decimal string. + */ +#define UINT64_MAX_STRLEN 20 + static OCStackResult OCParseDiscoveryPayload(OCPayload **outPayload, CborValue *arrayVal); static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *repParent, bool isRoot); static OCStackResult OCParseRepPayload(OCPayload **outPayload, CborValue *arrayVal); @@ -477,6 +482,7 @@ static OCRepPayloadPropType DecodeCborType(CborType type) return OCREP_PROP_NULL; } } + static CborError OCParseArrayFindDimensionsAndType(const CborValue *parent, size_t dimensions[MAX_REP_ARRAY_DEPTH], OCRepPayloadPropType *type) { @@ -789,7 +795,7 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o VERIFY_PARAM_NON_NULL(TAG, outPayload, "Invalid Parameter outPayload"); VERIFY_PARAM_NON_NULL(TAG, objMap, "Invalid Parameter objMap"); - if (cbor_value_is_map(objMap)) + if (cbor_value_is_map(objMap) || cbor_value_is_array(objMap)) { if (!*outPayload) { @@ -802,6 +808,7 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o OCRepPayload *curPayload = *outPayload; + uint64_t arrayIndex = 0; size_t len = 0; CborValue repMap; err = cbor_value_enter_container(objMap, &repMap); @@ -809,7 +816,7 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o while (!err && cbor_value_is_valid(&repMap)) { - if (cbor_value_is_text_string(&repMap)) + if (cbor_value_is_map(objMap) && cbor_value_is_text_string(&repMap)) { err = cbor_value_dup_text_string(&repMap, &name, &len, NULL); VERIFY_CBOR_SUCCESS(TAG, err, "Failed finding tag name in the map"); @@ -826,6 +833,30 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o continue; } } + else if (cbor_value_is_array(objMap)) + { + name = (char*)OICCalloc(UINT64_MAX_STRLEN + 1, sizeof(char)); + VERIFY_PARAM_NON_NULL(TAG, name, "Failed allocating tag name in the map"); +#ifdef PRIu64 + snprintf(name, UINT64_MAX_STRLEN + 1, "%" PRIu64, arrayIndex); +#else + /* + * Some libc implementations do not support the PRIu64 format + * specifier. Since we don't expect huge heterogeneous arrays, + * we should never hit the error path below in practice. + */ + if (arrayIndex <= UINT32_MAX) + { + snprintf(name, UINT64_MAX_STRLEN + 1, "%" PRIu32, (uint32_t)arrayIndex); + } + else + { + err = CborErrorDataTooLarge; + OICFree(name); + continue; + } +#endif + } CborType type = cbor_value_get_type(&repMap); switch (type) { @@ -883,6 +914,15 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o break; case CborArrayType: err = OCParseArray(curPayload, name, &repMap); + if (err != CborNoError) + { + // OCParseArray will fail if the array contains mixed types, try + // to parse as payload with non-negative integer value names + OCRepPayload *pl = NULL; + err = OCParseSingleRepPayload(&pl, &repMap, false); + VERIFY_CBOR_SUCCESS(TAG, err, "Failed setting parse single rep"); + res = OCRepPayloadSetPropObjectAsOwner(curPayload, name, pl); + } break; default: OIC_LOG_V(ERROR, TAG, "Parsing rep property, unknown type %d", repMap.type); @@ -901,6 +941,7 @@ static CborError OCParseSingleRepPayload(OCRepPayload **outPayload, CborValue *o } OICFree(name); name = NULL; + ++arrayIndex; } if (cbor_value_is_container(objMap)) { diff --git a/resource/csdk/stack/test/cbortests.cpp b/resource/csdk/stack/test/cbortests.cpp index c17afc1..ed270bb 100644 --- a/resource/csdk/stack/test/cbortests.cpp +++ b/resource/csdk/stack/test/cbortests.cpp @@ -49,6 +49,13 @@ extern "C" #include "gtest_helper.h" +//----------------------------------------------------------------------------- +// CBOR_BIN_STRING_DEBUG may be defined when compiling to save the output of +// the OC payload conversion to CBOR to a file for further examination with any +// CBOR related tools. +//----------------------------------------------------------------------------- +//#define CBOR_BIN_STRING_DEBUG + class CborByteStringTest : public ::testing::Test { protected: virtual void SetUp() { @@ -222,3 +229,79 @@ TEST_F(CborByteStringTest, ByteStringArrayConvertParseTest ) OCPayloadDestroy((OCPayload*)payload_out); } + +TEST(CborHeterogeneousArrayTest, ConvertParseTest) +{ + OCRepPayload *arr = OCRepPayloadCreate(); + ASSERT_TRUE(arr != NULL); + EXPECT_TRUE(OCRepPayloadSetPropString(arr, "0", "string")); + EXPECT_TRUE(OCRepPayloadSetPropDouble(arr, "1", 1.0)); + OCRepPayload *obj = OCRepPayloadCreate(); + ASSERT_TRUE(obj != NULL); + EXPECT_TRUE(OCRepPayloadSetPropString(obj, "member", "value")); + EXPECT_TRUE(OCRepPayloadSetPropObjectAsOwner(arr, "2", obj)); + const char *strArray[] = { "string" }; + size_t dim[MAX_REP_ARRAY_DEPTH] = { 1, 0, 0 }; + EXPECT_TRUE(OCRepPayloadSetStringArray(arr, "3", strArray, dim)); + + OCRepPayload* payload_in = OCRepPayloadCreate(); + ASSERT_TRUE(payload_in != NULL); + EXPECT_TRUE(OCRepPayloadSetPropObjectAsOwner(payload_in, "property", arr)); + + // Convert OCPayload to CBOR + uint8_t *payload_cbor = NULL; + size_t payload_cbor_size = 0; + EXPECT_EQ(OC_STACK_OK, OCConvertPayload((OCPayload*) payload_in, &payload_cbor, &payload_cbor_size)); +#ifdef CBOR_BIN_STRING_DEBUG + FILE *fp = fopen("binstringhetarr.cbor", "wb+"); + if (fp) + { + fwrite(payload_cbor, 1, payload_cbor_size, fp); + fclose(fp); + } +#endif //CBOR_BIN_STRING_DEBUG + OCRepPayloadDestroy(payload_in); + + // Compare that array encoding was used + uint8_t binstring[] = { + 0xbf, 0x68, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x84, 0x66, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0xfb, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x66, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x65, 0xff, 0x81, 0x66, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0xff + }; + ASSERT_EQ(sizeof(binstring), payload_cbor_size); + EXPECT_EQ(0, memcmp(binstring, payload_cbor, payload_cbor_size)); + + // Parse CBOR back to OCPayload + OCPayload* payload_out = NULL; + EXPECT_EQ(OC_STACK_OK, OCParsePayload(&payload_out, PAYLOAD_TYPE_REPRESENTATION, + payload_cbor, payload_cbor_size)); + + // Compare values + EXPECT_TRUE(OCRepPayloadGetPropObject((OCRepPayload*) payload_out, "property", &arr)); + char *str; + EXPECT_TRUE(OCRepPayloadGetPropString(arr, "0", &str)); + EXPECT_STREQ("string", str); + OICFree(str); + double d; + EXPECT_TRUE(OCRepPayloadGetPropDouble(arr, "1", &d)); + EXPECT_EQ(1.0, d); + EXPECT_TRUE(OCRepPayloadGetPropObject(arr, "2", &obj)); + OCRepPayloadDestroy(obj); + char **strArr; + EXPECT_TRUE(OCRepPayloadGetStringArray(arr, "3", &strArr, dim)); + EXPECT_EQ(1u, dim[0]); + EXPECT_EQ(0u, dim[1]); + EXPECT_EQ(0u, dim[2]); + EXPECT_STREQ("string", strArr[0]); + for (size_t i = 0; i < dim[0]; ++i) + { + OICFree(strArr[i]); + } + OICFree(strArr); + OCRepPayloadDestroy(arr); + + // Cleanup + OICFree(payload_cbor); + OCPayloadDestroy(payload_out); +} -- 2.7.4