diff --git a/src/structures/oc-payload.cc b/src/structures/oc-payload.cc index 4114414c..5031fa9e 100644 --- a/src/structures/oc-payload.cc +++ b/src/structures/oc-payload.cc @@ -355,18 +355,16 @@ static bool jsTypeToOCRepPayloadPropTypeValidForArray( // Validate a multidimensional array of items of a given type // // If the array passed in contains arrays, their validity is checked -// recursively, and it is -// asserted that they all have the same length as the first one encountered. -// Recursions are limited -// to MAX_REP_ARRAY_DEPTH. +// recursively, and it is asserted that they all have the same length as the +// first one encountered. Recursions are limited to MAX_REP_ARRAY_DEPTH. // // If the array passed in contains primitive types, the type of the array is -// established from the -// first item, and it is asserted that subsequent items have the same type. +// established from the first item, and it is asserted that subsequent items +// have the same type. // // If the array is valid, p_typeEstablished, p_arrayType, and dimensions are set -// and the function -// returns true. Otherwise, it throws an exception and returns false. +// and the function returns true. Otherwise, it throws an exception and returns +// false. static bool validateRepPayloadArray(Local array, bool *p_typeEstablished, OCRepPayloadPropType *p_arrayType, size_t dimensions[MAX_REP_ARRAY_DEPTH], @@ -389,7 +387,7 @@ static bool validateRepPayloadArray(Local array, bool *p_typeEstablished, return false; } - bool child_established; + bool child_established = false; OCRepPayloadPropType child_type; Local child_array = Local::Cast(member); if (child_array->Length() != child_length) { @@ -459,60 +457,61 @@ static bool fillArray(void *flatArray, int *p_index, Local array, for (size_t localIndex = 0; localIndex < length; localIndex++) { Local member = Nan::Get(array, localIndex).ToLocalChecked(); if (member->IsArray()) { - if (!fillArray(flatArray, p_index, array, arrayType)) { + if (!fillArray(flatArray, p_index, Local::Cast(member), + arrayType)) { return false; - } else { - switch (arrayType) { - case OCREP_PROP_INT: - ((uint64_t *)flatArray)[(*p_index)++] = - (uint64_t)member->Uint32Value(); - break; - - case OCREP_PROP_DOUBLE: - ((double *)flatArray)[(*p_index)++] = member->NumberValue(); - break; - - case OCREP_PROP_BOOL: - ((bool *)flatArray)[(*p_index)++] = member->BooleanValue(); - break; - - case OCREP_PROP_STRING: { - char *theString; - - if (c_StringNew(member->ToString(), &theString)) { - ((char **)flatArray)[(*p_index)++] = theString; - } else { - // If we fail to copy the string, we free all strings allocated so - // far and we quit. - for (int freeIndex = 0; freeIndex < (*p_index); freeIndex++) { - free(((char **)flatArray)[freeIndex]); - } - return false; + } + } else { + switch (arrayType) { + case OCREP_PROP_INT: + ((uint64_t *)flatArray)[(*p_index)++] = + (uint64_t)member->Uint32Value(); + break; + + case OCREP_PROP_DOUBLE: + ((double *)flatArray)[(*p_index)++] = member->NumberValue(); + break; + + case OCREP_PROP_BOOL: + ((bool *)flatArray)[(*p_index)++] = member->BooleanValue(); + break; + + case OCREP_PROP_STRING: { + char *theString; + + if (c_StringNew(member->ToString(), &theString)) { + ((char **)flatArray)[(*p_index)++] = theString; + } else { + // If we fail to copy the string, we free all strings allocated so + // far and we quit. + for (int freeIndex = 0; freeIndex < (*p_index); freeIndex++) { + free(((char **)flatArray)[freeIndex]); } - break; + return false; } + break; + } - case OCREP_PROP_OBJECT: { - OCRepPayload *theObject = 0; - if (c_OCRepPayload(member->ToObject(), &theObject)) { - ((OCRepPayload **)flatArray)[(*p_index)++] = theObject; - } else { - // If we fail to create the object, we free all objects allocated - // so far and we quit. - for (int freeIndex = 0; freeIndex < (*p_index); freeIndex++) { - OCRepPayloadDestroy(((OCRepPayload **)flatArray)[freeIndex]); - } - return false; + case OCREP_PROP_OBJECT: { + OCRepPayload *theObject = 0; + if (c_OCRepPayload(member->ToObject(), &theObject)) { + ((OCRepPayload **)flatArray)[(*p_index)++] = theObject; + } else { + // If we fail to create the object, we free all objects allocated + // so far and we quit. + for (int freeIndex = 0; freeIndex < (*p_index); freeIndex++) { + OCRepPayloadDestroy(((OCRepPayload **)flatArray)[freeIndex]); } - break; + return false; } - - // The validation should prevent these from occurring - case OCREP_PROP_NULL: - case OCREP_PROP_ARRAY: - default: - break; + break; } + + // The validation should prevent these from occurring + case OCREP_PROP_NULL: + case OCREP_PROP_ARRAY: + default: + break; } } } @@ -575,7 +574,9 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { Nan::Get(jsPayload, Nan::New("uri").ToLocalChecked()).ToLocalChecked(); VALIDATE_VALUE_TYPE_OR_FREE(uri, IsString, "reppayload.uri", false, payload, OCRepPayloadDestroy); - OCRepPayloadSetUri(payload, (const char *)*String::Utf8Value(uri)); + if (!OCRepPayloadSetUri(payload, (const char *)*String::Utf8Value(uri))) { + goto fail; + } } // reppayload.types @@ -590,8 +591,10 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { Local singleType = Nan::Get(typesArray, index).ToLocalChecked(); VALIDATE_VALUE_TYPE_OR_FREE(singleType, IsString, "reppayload.types item", false, payload, OCRepPayloadDestroy); - OCRepPayloadAddResourceType(payload, - (const char *)*String::Utf8Value(singleType)); + if (!OCRepPayloadAddResourceType( + payload, (const char *)*String::Utf8Value(singleType))) { + goto fail; + } } } @@ -610,8 +613,10 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { VALIDATE_VALUE_TYPE_OR_FREE(singleInterface, IsString, "reppayload.interfaces item", false, payload, OCRepPayloadDestroy); - OCRepPayloadAddInterface( - payload, (const char *)*String::Utf8Value(singleInterface)); + if (!OCRepPayloadAddInterface( + payload, (const char *)*String::Utf8Value(singleInterface))) { + goto fail; + } } } @@ -634,32 +639,28 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); if (!OCRepPayloadSetNull(payload, (const char *)*name)) { Nan::ThrowError("reppayload: Failed to set null property"); - OCRepPayloadDestroy(payload); - return false; + goto fail; } } else if (value->IsUint32()) { String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); if (!OCRepPayloadSetPropInt(payload, (const char *)*name, (int64_t)value->Uint32Value())) { - OCRepPayloadDestroy(payload); Nan::ThrowError("reppayload: Failed to set integer property"); - return false; + goto fail; } } else if (value->IsNumber()) { String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); if (!OCRepPayloadSetPropDouble(payload, (const char *)*name, value->NumberValue())) { - OCRepPayloadDestroy(payload); Nan::ThrowError("reppayload: Failed to set floating point property"); - return false; + goto fail; } } else if (value->IsBoolean()) { String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); if (!OCRepPayloadSetPropBool(payload, (const char *)*name, value->BooleanValue())) { Nan::ThrowError("reppayload: Failed to set boolean property"); - OCRepPayloadDestroy(payload); - return false; + goto fail; } } else if (value->IsString()) { String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); @@ -667,23 +668,7 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { if (!OCRepPayloadSetPropString(payload, (const char *)*name, (const char *)*stringValue)) { Nan::ThrowError("reppayload: Failed to set string property"); - OCRepPayloadDestroy(payload); - return false; - } - } else if (value->IsObject()) { - OCRepPayload *child_payload = 0; - - if (c_OCRepPayload(value->ToObject(), &child_payload)) { - String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); - if (!OCRepPayloadSetPropObjectAsOwner(payload, (const char *)*name, - child_payload)) { - Nan::ThrowError("reppayload: Failed to set object property"); - OCRepPayloadDestroy(payload); - return false; - } - } else { - OCRepPayloadDestroy(payload); - return false; + goto fail; } } else if (value->IsArray()) { size_t dimensions[MAX_REP_ARRAY_DEPTH] = {0}; @@ -692,18 +677,85 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { Local array = Local::Cast(value); if (!validateRepPayloadArray(array, &typeEstablished, &arrayType, dimensions, 0)) { - OCRepPayloadDestroy(payload); - return false; + goto fail; } if (dimensions[0] > 0) { void *flatArray; if (!flattenArray(array, &flatArray, dimensions, arrayType)) { - OCRepPayloadDestroy(payload); - return false; + goto fail; + } + + String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); + switch (arrayType) { + case OCREP_PROP_INT: + if (!OCRepPayloadSetIntArray(payload, (const char *)*name, + (const int64_t *)flatArray, + dimensions)) { + Nan::ThrowError( + "reppayload: Failed to set integer array property"); + goto fail; + } + break; + + case OCREP_PROP_DOUBLE: + if (!OCRepPayloadSetDoubleArray(payload, (const char *)*name, + (const double *)flatArray, + dimensions)) { + Nan::ThrowError( + "reppayload: Failed to set double array property"); + goto fail; + } + break; + + case OCREP_PROP_BOOL: + if (!OCRepPayloadSetBoolArray(payload, (const char *)*name, + (const bool *)flatArray, + dimensions)) { + Nan::ThrowError( + "reppayload: Failed to set boolean array property"); + goto fail; + } + break; + + case OCREP_PROP_STRING: + if (!OCRepPayloadSetStringArray(payload, (const char *)*name, + (const char **)flatArray, + dimensions)) { + Nan::ThrowError( + "reppayload: Failed to set string array property"); + goto fail; + } + break; + + case OCREP_PROP_OBJECT: + if (!OCRepPayloadSetPropObjectArray( + payload, (const char *)*name, + (const OCRepPayload **)flatArray, dimensions)) { + Nan::ThrowError( + "reppayload: Failed to set object array property"); + goto fail; + } + break; + + default: + break; } } + } else if (value->IsObject()) { + OCRepPayload *child_payload = 0; + + if (c_OCRepPayload(value->ToObject(), &child_payload)) { + String::Utf8Value name(Nan::Get(keys, index).ToLocalChecked()); + if (!OCRepPayloadSetPropObjectAsOwner(payload, (const char *)*name, + child_payload)) { + Nan::ThrowError("reppayload: Failed to set object property"); + goto fail; + } + } else { + goto fail; + } } } } @@ -715,14 +767,17 @@ static bool c_OCRepPayload(Local jsPayload, OCRepPayload **p_payload) { payload, OCRepPayloadDestroy); OCRepPayload *next_payload = 0; if (!c_OCRepPayload(next->ToObject(), &next_payload)) { - OCRepPayloadDestroy(payload); - return false; + goto fail; } OCRepPayloadAppend(payload, next_payload); } (*p_payload) = payload; return true; + +fail: + OCRepPayloadDestroy(payload); + return false; } bool c_OCPayload(Local jsPayload, OCPayload **p_payload) { diff --git a/tests/tests/Complex Payload/client.js b/tests/tests/Complex Payload/client.js new file mode 100644 index 00000000..65eaf6c3 --- /dev/null +++ b/tests/tests/Complex Payload/client.js @@ -0,0 +1,149 @@ +var result, testServerAddress, + async = require( "async" ), + uuid = process.argv[ 2 ], + processCallCount = 0, + processLoop = null, + iotivity = require( "../../../lowlevel" ), + testUtils = require( "../../utils" )( iotivity ); + +console.log( JSON.stringify( { assertionCount: 5 } ) ); + +// Initialize +result = iotivity.OCInit( null, 0, iotivity.OCMode.OC_CLIENT ); +testUtils.stackOKOrDie( "Client", "OCInit", result ); + +// Set up OCProcess loop +processLoop = setInterval( function() { + var processResult = iotivity.OCProcess(); + + if ( processResult === iotivity.OCStackResult.OC_STACK_OK ) { + processCallCount++; + } else { + testUtils.stackOKOrDie( + "Client", + "OCProcess(after " + processCallCount + " successful calls)", + processResult ); + } +}, 100 ); + +async.series( [ + + // Discover + function( callback ) { + var discoverHandleReceptacle = {}; + + result = iotivity.OCDoResource( + discoverHandleReceptacle, + iotivity.OCMethod.OC_REST_DISCOVER, + iotivity.OC_MULTICAST_DISCOVERY_URI, + null, + null, + iotivity.OCConnectivityType.CT_DEFAULT, + iotivity.OCQualityOfService.OC_HIGH_QOS, + function( handle, response ) { + + // We retain the discovery callback until we've found the resource with the uuid. + var returnValue = iotivity.OCStackApplicationResult.OC_STACK_KEEP_TRANSACTION; + + if ( testUtils.findResource( response, uuid ) ) { + + // We've successfully found the resource so let's issue a GET request on it. + testUtils.assert( "ok", true, "Client: Resource found" ); + + testServerAddress = response.addr; + callback(); + returnValue = iotivity.OCStackApplicationResult.OC_STACK_DELETE_TRANSACTION; + } + + return returnValue; + }, + null ); + if ( result !== iotivity.OCStackResult.OC_STACK_OK ) { + callback( "OCDoResource(discovery): " + result ); + } + }, + + // GET + function( callback ) { + var getHandleReceptacle = {}; + + result = iotivity.OCDoResource( + getHandleReceptacle, + iotivity.OCMethod.OC_REST_GET, + "/a/" + uuid, + testServerAddress, + null, + iotivity.OCConnectivityType.CT_DEFAULT, + iotivity.OCQualityOfService.OC_HIGH_QOS, + function( handle, response ) { + testUtils.assert( "deepEqual", response.payload, { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + primitiveValue: 42, + objectValue: { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + childValue: 91 + } + }, + arrayValue: [ 19, 23, 7 ] + } + }, "Client: received payload is correct" ); + callback(); + return iotivity.OCStackApplicationResult.OC_STACK_DELETE_TRANSACTION; + }, + null, 0 ); + if ( result !== iotivity.OCStackResult.OC_STACK_OK ) { + callback( "OCDoResource(get): " + result ); + } + }, + + // PUT + function( callback ) { + var putHandleReceptacle = {}; + + result = iotivity.OCDoResource( + putHandleReceptacle, + iotivity.OCMethod.OC_REST_PUT, + "/a/" + uuid, + testServerAddress, { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + putValue: "A string", + anotherPutValue: 23.7, + childValues: { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + putChildValue: false, + putChildArray: [ [ 2, 3, 5 ], [ 9, 11, 17 ] ] + } + } + } + }, + iotivity.OCConnectivityType.CT_DEFAULT, + iotivity.OCQualityOfService.OC_HIGH_QOS, + function( handle, response ) { + return iotivity.OCStackApplicationResult.OC_STACK_DELETE_TRANSACTION; + }, + null, 0 ); + if ( result !== iotivity.OCStackResult.OC_STACK_OK ) { + callback( "OCDoResource(get): " + result ); + } + } +], function( error ) { + testUtils.die( "Client: " + error ); +} ); + +process.on( "SIGINT", function() { + if ( processLoop ) { + clearInterval( processLoop ); + processLoop = null; + } + + testUtils.assert( "ok", true, "Client: OCProcess succeeded " + processCallCount + " times" ); + + result = iotivity.OCStop(); + if ( testUtils.stackOKOrDie( "Client", "OCStop", result ) ) { + process.exit( 0 ); + } +} ); diff --git a/tests/tests/Complex Payload/server.js b/tests/tests/Complex Payload/server.js new file mode 100644 index 00000000..5f84c4cd --- /dev/null +++ b/tests/tests/Complex Payload/server.js @@ -0,0 +1,128 @@ +var result, + uuid = process.argv[ 2 ], + processCallCount = 0, + processLoop = null, + resourceHandleReceptacle = {}, + iotivity = require( "../../../lowlevel" ), + testUtils = require( "../../utils" )( iotivity ); + +console.log( JSON.stringify( { assertionCount: 8 } ) ); + +// Initialize +result = iotivity.OCInit( null, 0, iotivity.OCMode.OC_SERVER ); +testUtils.stackOKOrDie( "Server", "OCInit", result ); + +// Set up process loop +processLoop = setInterval( function() { + var processResult = iotivity.OCProcess(); + + if ( processResult === iotivity.OCStackResult.OC_STACK_OK ) { + processCallCount++; + } else { + testUtils.stackOKOrDie( + "Server", + "OCProcess(after " + processCallCount + " successful calls)", + processResult ); + } +}, 100 ); + +var requestSequenceNumber = 0, + requestSequence = [ + function( flag, request ) { + if ( request.method !== iotivity.OCMethod.OC_REST_GET ) { + testUtils.die( "Server: First method was not GET" ); + } + result = iotivity.OCDoResponse( { + requestHandle: request.requestHandle, + resourceHandle: request.resource, + resourceUri: "/a/" + uuid, + ehResult: iotivity.OCEntityHandlerResult.OC_EH_OK, + payload: { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + primitiveValue: 42, + objectValue: { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + childValue: 91 + } + }, + arrayValue: [ 19, 23, 7 ] + } + }, + sendVendorSpecificHeaderOptions: [] + } ); + + testUtils.stackOKOrDie( "Server", "OCDoResponse(get)", result ); + + return iotivity.OCEntityHandlerResult.OC_EH_OK; + }, + function( flag, request ) { + if ( request.method !== iotivity.OCMethod.OC_REST_PUT ) { + testUtils.die( "Server: Second method was not PUT" ); + } + testUtils.assert( "deepEqual", request.payload, { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + putValue: "A string", + anotherPutValue: 23.7, + childValues: { + type: iotivity.OCPayloadType.PAYLOAD_TYPE_REPRESENTATION, + values: { + putChildValue: false, + putChildArray: [ [ 2, 3, 5 ], [ 9, 11, 17 ] ] + } + } + } + }, "Server: received payload is correct" ); + result = iotivity.OCDoResponse( { + requestHandle: request.requestHandle, + resourceHandle: request.resource, + resourceUri: "/a/" + uuid, + ehResult: iotivity.OCEntityHandlerResult.OC_EH_OK, + payload: null, + sendVendorSpecificHeaderOptions: [] + } ); + + testUtils.stackOKOrDie( "Server", "OCDoResponse(put)", result ); + + cleanup(); + + return iotivity.OCEntityHandlerResult.OC_EH_OK; + } + ]; + +// Create resource +result = iotivity.OCCreateResource( + resourceHandleReceptacle, + "core.fan", + iotivity.OC_RSRVD_INTERFACE_DEFAULT, + "/a/" + uuid, + function( flag, request ) { + return requestSequence[ requestSequenceNumber++ ]( flag, request ); + }, + iotivity.OCResourceProperty.OC_DISCOVERABLE ); +testUtils.stackOKOrDie( "Server", "OCCreateResource", result ); + +// Report that the server has successfully created its resource(s). +console.log( JSON.stringify( { ready: true } ) ); + +function cleanup() { + var cleanupResult; + + if ( processLoop ) { + clearInterval( processLoop ); + processLoop = null; + } + + testUtils.assert( "ok", true, "Server: OCProcess succeeded " + processCallCount + " times" ); + + cleanupResult = iotivity.OCDeleteResource( resourceHandleReceptacle.handle ); + testUtils.stackOKOrDie( "Server", "OCDeleteResource", result ); + + cleanupResult = iotivity.OCStop(); + if ( testUtils.stackOKOrDie( "Server", "OCStop", result ) ) { + console.log( JSON.stringify( { killPeer: true } ) ); + process.exit( 0 ); + } +}