[IOT-1116] Support heterogeneous array encoding. 61/13661/8
authorTodd Malsbary <todd.malsbary@intel.com>
Tue, 25 Oct 2016 17:30:12 +0000 (10:30 -0700)
committerDan Mihai <Daniel.Mihai@microsoft.com>
Fri, 2 Dec 2016 03:18:57 +0000 (03:18 +0000)
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 <todd.malsbary@intel.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/13661
Tested-by: jenkins-iotivity <jenkins-iotivity@opendaylight.org>
Reviewed-by: Dan Mihai <Daniel.Mihai@microsoft.com>
resource/csdk/stack/src/ocpayloadconvert.c
resource/csdk/stack/src/ocpayloadparse.c
resource/csdk/stack/test/cbortests.cpp

index af183b7..c8165f4 100644 (file)
@@ -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;
     }
index bb11a5a..758ec53 100644 (file)
@@ -29,9 +29,9 @@
 
 #include <string.h>
 #include <stdlib.h>
+#include "ocpayload.h"
 #include "oic_string.h"
 #include "oic_malloc.h"
-#include "ocpayload.h"
 #include "ocpayloadcbor.h"
 #include "ocstackinternal.h"
 #include "payload_logging.h"
 
 #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))
         {
index c17afc1..ed270bb 100644 (file)
@@ -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);
+}