From e95e8cd97d4eb9ce34ac420b57cc97187603ca8f Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 5 Oct 2023 14:45:26 +0200 Subject: [PATCH] Add JSON Encoder and Decoder Allow the used encoder and decoder to be configurable. By default the JSON encoder and decoders are disabled in compile time. Use oc_rep_encoder_set_type from oc_rep.h to set the encoder that will be used to encode payloads. --- .github/workflows/android.yml | 4 +- .github/workflows/coverity.yml | 1 + .github/workflows/sonar-cloud-analysis.yml | 10 +- CMakeLists.txt | 29 +- api/cloud/unittest/cloud_test.cpp | 6 +- api/oc_blockwise_internal.h | 1 + api/oc_client_api.c | 13 +- api/oc_core_res.c | 42 +- api/oc_core_res_internal.h | 3 + api/oc_discovery.c | 22 +- api/oc_helpers.c | 3 + api/oc_helpers_internal.h | 6 +- api/oc_rep.c | 604 ++-------- api/oc_rep_decode.c | 566 +++++++++ api/oc_rep_decode_internal.h | 66 + api/oc_rep_decode_json.c | 524 ++++++++ api/oc_rep_decode_json_internal.h | 44 + api/oc_rep_encode.c | 209 +++- api/oc_rep_encode_internal.h | 93 +- api/oc_rep_encode_json.c | 339 ++++++ api/oc_rep_encode_json_internal.h | 45 + api/oc_rep_internal.h | 9 +- api/oc_ri.c | 13 +- api/oc_server_api.c | 7 + api/unittest/discoverytest.cpp | 102 +- api/unittest/repdecodejsontest.cpp | 440 +++++++ api/unittest/repdecodetest.cpp | 60 + api/unittest/repencodejsontest.cpp | 471 ++++++++ api/unittest/repencodetest.cpp | 167 ++- api/unittest/repjsontest.cpp | 1257 ++++++++++++++++++++ api/unittest/reptest.cpp | 494 +++++--- apps/cloud_server.c | 16 + docker/run.sh | 1 + include/oc_rep.h | 78 +- include/oc_ri.h | 6 +- include/oc_swupdate.h | 2 +- messaging/coap/coap.c | 42 +- messaging/coap/coap_options.c | 8 +- messaging/coap/coap_options.h | 6 +- messaging/coap/engine.c | 7 +- messaging/coap/observe.c | 8 + messaging/coap/unittest/optionstest.cpp | 20 +- port/android/Makefile | 15 +- port/esp32/main/CMakeLists.txt | 1 + port/linux/Makefile | 16 +- security/oc_acl.c | 8 +- security/oc_cred.c | 6 + security/oc_oscore_crypto.c | 8 +- security/oc_security.c | 2 +- swig/Makefile | 5 + swig/swig_interfaces/oc_rep.i | 4 + tests/gtest/RepPool.cpp | 5 +- tests/gtest/Utility.h | 31 + util/jsmn/jsmn.c | 316 +++++ util/jsmn/jsmn_internal.h | 123 ++ util/oc_secure_string_internal.h | 6 + util/unittest/jsmntest.cpp | 290 +++++ 57 files changed, 5673 insertions(+), 1007 deletions(-) create mode 100644 api/oc_rep_decode.c create mode 100644 api/oc_rep_decode_internal.h create mode 100644 api/oc_rep_decode_json.c create mode 100644 api/oc_rep_decode_json_internal.h create mode 100644 api/oc_rep_encode_json.c create mode 100644 api/oc_rep_encode_json_internal.h create mode 100644 api/unittest/repdecodejsontest.cpp create mode 100644 api/unittest/repdecodetest.cpp create mode 100644 api/unittest/repencodejsontest.cpp create mode 100644 api/unittest/repjsontest.cpp create mode 100644 util/jsmn/jsmn.c create mode 100644 util/jsmn/jsmn_internal.h create mode 100644 util/unittest/jsmntest.cpp diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 48f7129dfd..fca8f4b91f 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -41,8 +41,8 @@ jobs: include: # default (ip4 on, secure on, pki on, dynamic allocation on, tcp on, cloud on, java on, IDD on) - args: "" - # debug on - - args: "DEBUG=1" + # debug on, json encoder on + - args: "DEBUG=1 JSON_ENCODER=1" # secure off - args: "SECURE=0" # TODO: reenable when dynamic allocation is fixed diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index e4a8ecb9e1..6e47acbf6c 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -28,6 +28,7 @@ jobs: -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON -DPLGD_DEV_TIME_ENABLED=ON -DOC_ETAG_ENABLED=ON + -DOC_JSON_ENCODER_ENABLED=ON -B ${{github.workspace}}/build - uses: vapier/coverity-scan-action@v1 diff --git a/.github/workflows/sonar-cloud-analysis.yml b/.github/workflows/sonar-cloud-analysis.yml index 813c888ee9..fd0185b8b3 100644 --- a/.github/workflows/sonar-cloud-analysis.yml +++ b/.github/workflows/sonar-cloud-analysis.yml @@ -29,12 +29,12 @@ jobs: - build_args: "-DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON" # security off, ipv4 on, collection create on, push on, rfotm on - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_IPV4_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON" - # ipv6 dns on, oscore off, rep realloc on - - build_args: "-DOC_DNS_LOOKUP_IPV6_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DOC_REPRESENTATION_REALLOC_ENCODING_ENABLED=ON" + # ipv6 dns on, oscore off, rep realloc on, json encoder on + - build_args: "-DOC_DNS_LOOKUP_IPV6_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DOC_REPRESENTATION_REALLOC_ENCODING_ENABLED=ON -DOC_JSON_ENCODER_ENABLED=ON" # ipv4 on, tcp on, dynamic allocation off, rfotm on, push off (because it forces dynamic allocation) - build_args: "-DOC_IPV4_ENABLED=ON -DOC_TCP_ENABLED=ON -DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON" - # security off, dynamic allocation off, push off (because it forces dynamic allocation) - - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_DYNAMIC_ALLOCATION_ENABLED=OFF" + # security off, dynamic allocation off, push off (because it forces dynamic allocation), json encoder on + - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_JSON_ENCODER_ENABLED=ON" # security off, cloud (ipv4+tcp), collection create on, introspection IDD off - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_IDD_API_ENABLED=OFF" @@ -104,7 +104,7 @@ jobs: mkdir build && cd build # sonar-scanner currently cannot handle multi configuration configuration (ie. compilation of the same file with different defines), # so we enable as many features as possible so we get max. amount of code analysis - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DOC_DISCOVERY_RESOURCE_OBSERVABLE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON -DPLGD_DEV_TIME_ENABLED=ON -DOC_ETAG_ENABLED=ON -DBUILD_TESTING=ON .. + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DOC_CLOUD_ENABLED=ON -DOC_COLLECTIONS_IF_CREATE_ENABLED=ON -DOC_MNT_ENABLED=ON -DOC_WKCORE_ENABLED=ON -DOC_SOFTWARE_UPDATE_ENABLED=ON -DOC_DISCOVERY_RESOURCE_OBSERVABLE_ENABLED=ON -DOC_PUSH_ENABLED=ON -DOC_RESOURCE_ACCESS_IN_RFOTM_ENABLED=ON -DPLGD_DEV_TIME_ENABLED=ON -DOC_ETAG_ENABLED=ON -DOC_JSON_ENCODER_ENABLED=ON -DBUILD_TESTING=ON .. cd .. # for files defined in multiple cmake targets, sonar-scanner seems to take the configuration from the first compilation of the file, # so we force client-server target to be compiled first so we get analysis of code with both OC_CLIENT and OC_SERVER enabled diff --git a/CMakeLists.txt b/CMakeLists.txt index 141c18e5d2..c025e95e5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(OC_APP_DATA_BUFFER_SIZE "" CACHE STRING "Custom static buffer size for appli set(OC_APP_DATA_BUFFER_POOL "" CACHE STRING "Custom static size of application messages.") set(OC_VERSION_1_1_0_ENABLED OFF CACHE BOOL "Enable OCF version 1.1") set(OC_ETAG_ENABLED OFF CACHE BOOL "Enable Entity Tag (ETag) support.") +set(OC_JSON_ENCODER_ENABLED OFF CACHE BOOL "Enable JSON encoder/decoder support.") set(PLGD_DEV_TIME_ENABLED OFF CACHE BOOL "Enable plgd time feature.") set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -99,8 +100,16 @@ if(UNIX) if(OC_UBSAN_ENABLED) message(STATUS "Undefined behaviour sanitizer enabled") - add_compile_options(${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer) + if(OC_COMPILER_IS_CLANG) + # -fno-sanitize-recover=undefined,local-bounds,nullability + add_compile_options(${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined,local-bounds,nullability -fno-omit-frame-pointer) + else() + # -fno-sanitize-recover=undefined + add_compile_options(${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined -fno-omit-frame-pointer) + endif() add_link_options(-fsanitize=undefined) + # halt_on_error=1 + set(OC_UBSAN_OPTIONS "print_stacktrace=1") endif() if(OC_COMPILER_IS_CLANG) @@ -411,6 +420,10 @@ if(OC_ETAG_ENABLED) list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_ETAG") endif() +if(OC_JSON_ENCODER_ENABLED) + list(APPEND PUBLIC_COMPILE_DEFINITIONS "OC_JSON_ENCODER") +endif() + if(PLGD_DEV_TIME_ENABLED) list(APPEND PUBLIC_COMPILE_DEFINITIONS "PLGD_DEV_TIME") if(BUILD_MBEDTLS) @@ -474,6 +487,13 @@ if(OC_PKI_ENABLED) ) endif() +if(OC_JSON_ENCODER_ENABLED) + file(GLOB JSMN_SRC + ${PROJECT_SOURCE_DIR}/util/jsmn/*.c + ) + list(APPEND COMMON_SRC ${JSMN_SRC}) +endif() + if(PLGD_DEV_TIME_ENABLED) file(GLOB PLGD_SRC ${PROJECT_SOURCE_DIR}/api/plgd/*.c @@ -971,6 +991,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) FILES ${apitest_files} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) file(GLOB TIMESTAMPTEST_SRC api/c-timestamp/unittest/*.cpp) @@ -979,6 +1000,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${TIMESTAMPTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) file(GLOB PLATFORMTEST_SRC port/unittest/*.cpp) @@ -988,6 +1010,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${COMMONTEST_SRC} ${PLATFORMTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) file(GLOB MESSAGINGTEST_SRC messaging/coap/unittest/*.cpp) @@ -996,6 +1019,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${COMMONTEST_SRC} ${MESSAGINGTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) if(OC_SECURITY_ENABLED) @@ -1005,6 +1029,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${COMMONTEST_SRC} ${SECURITYTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) file(COPY ${PROJECT_SOURCE_DIR}/apps/pki_certs @@ -1018,6 +1043,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${COMMONTEST_SRC} ${UTILTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) if(OC_CLOUD_ENABLED) @@ -1028,6 +1054,7 @@ if(BUILD_TESTING AND(UNIX OR MINGW)) SOURCES ${COMMONTEST_SRC} ${CLOUDTEST_SRC} ENVIRONMENT "ASAN_OPTIONS=${OC_ASAN_OPTIONS}" "TSAN_OPTIONS=${OC_TSAN_OPTIONS}" + "UBSAN_OPTIONS=${OC_UBSAN_OPTIONS}" ) endif() diff --git a/api/cloud/unittest/cloud_test.cpp b/api/cloud/unittest/cloud_test.cpp index b315cf936e..119e376f2a 100644 --- a/api/cloud/unittest/cloud_test.cpp +++ b/api/cloud/unittest/cloud_test.cpp @@ -53,10 +53,8 @@ TEST_F(TestCloud, cloud_set_last_error) oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); - int err = 123; - - cloud_set_last_error(ctx, (oc_cloud_error_t)err); - ASSERT_EQ((oc_cloud_error_t)err, ctx->last_error); + cloud_set_last_error(ctx, CLOUD_ERROR_RESPONSE); + ASSERT_EQ(CLOUD_ERROR_RESPONSE, ctx->last_error); } TEST_F(TestCloud, cloud_update_by_resource) diff --git a/api/oc_blockwise_internal.h b/api/oc_blockwise_internal.h index 40b32897e6..2b52d9ca45 100644 --- a/api/oc_blockwise_internal.h +++ b/api/oc_blockwise_internal.h @@ -56,6 +56,7 @@ typedef struct oc_blockwise_state_s oc_string_t href; oc_endpoint_t endpoint; oc_method_t method; + oc_content_format_t content_format; oc_blockwise_role_t role; uint32_t payload_size; uint32_t next_block_offset; diff --git a/api/oc_client_api.c b/api/oc_client_api.c index 14df8c2d4c..9a5a681c95 100644 --- a/api/oc_client_api.c +++ b/api/oc_client_api.c @@ -20,15 +20,16 @@ #ifdef OC_CLIENT +#include "api/client/oc_client_cb_internal.h" #include "api/oc_client_api_internal.h" #include "api/oc_discovery_internal.h" #include "api/oc_helpers_internal.h" -#include "oc_message_internal.h" -#include "api/client/oc_client_cb_internal.h" +#include "api/oc_rep_encode_internal.h" #include "messaging/coap/coap.h" #include "messaging/coap/coap_options.h" #include "messaging/coap/transactions.h" #include "oc_api.h" +#include "oc_message_internal.h" #include "oc_ri_internal.h" #ifdef OC_TCP @@ -106,7 +107,8 @@ dispatch_coap_request(void) } else #endif /* OC_SPEC_VER_OIC */ { - coap_options_set_content_format(g_request, APPLICATION_VND_OCF_CBOR); + coap_options_set_content_format(g_request, + oc_rep_encoder_get_content_format()); } } @@ -210,7 +212,7 @@ prepare_coap_request(oc_client_cb_t *cb, coap_configure_request_fn_t configure, } else #endif /* OC_SPEC_VER_OIC */ { - coap_options_set_accept(g_request, APPLICATION_VND_OCF_CBOR); + coap_options_set_accept(g_request, oc_rep_encoder_get_content_format()); } coap_set_token(g_request, cb->token, cb->token_len); @@ -270,7 +272,8 @@ oc_do_multicast_update(void) (uint32_t)payload_size); if (payload_size > 0) { - coap_options_set_content_format(g_request, APPLICATION_VND_OCF_CBOR); + coap_options_set_content_format(g_request, + oc_rep_encoder_get_content_format()); } g_multicast_update->length = coap_serialize_message( diff --git a/api/oc_core_res.c b/api/oc_core_res.c index ac94957b7a..2fc00340b6 100644 --- a/api/oc_core_res.c +++ b/api/oc_core_res.c @@ -307,6 +307,29 @@ core_update_device_data(uint32_t device_count, oc_add_new_device_t cfg) g_oc_device_info[device_count].data = cfg.add_device_cb_data; } +static void +oc_create_device_resource(size_t device_count, const char *uri, const char *rt) +{ + /* Construct device resource */ + int properties = OC_DISCOVERABLE; +#ifdef OC_CLOUD + properties |= OC_OBSERVABLE; +#endif /* OC_CLOUD */ + if (oc_strnlen(rt, OC_CHAR_ARRAY_LEN(OCF_D_RT) + 1) == + OC_CHAR_ARRAY_LEN(OCF_D_RT) && + strncmp(rt, OCF_D_RT, OC_CHAR_ARRAY_LEN(OCF_D_RT)) == 0) { + oc_core_populate_resource(OCF_D, device_count, uri, + OC_IF_R | OC_IF_BASELINE, OC_IF_R, properties, + oc_core_device_handler, /*put*/ NULL, + /*post*/ NULL, /*delete*/ NULL, 1, rt); + } else { + oc_core_populate_resource(OCF_D, device_count, uri, + OC_IF_R | OC_IF_BASELINE, OC_IF_R, properties, + oc_core_device_handler, /*put*/ NULL, + /*post*/ NULL, /*delete*/ NULL, 2, rt, OCF_D_RT); + } +} + oc_device_info_t * oc_core_add_new_device(oc_add_new_device_t cfg) { @@ -336,20 +359,7 @@ oc_core_add_new_device(oc_add_new_device_t cfg) core_update_device_data(device_count, cfg); - /* Construct device resource */ - int properties = OC_DISCOVERABLE; -#ifdef OC_CLOUD - properties |= OC_OBSERVABLE; -#endif /* OC_CLOUD */ - if (strlen(cfg.rt) == 8 && strncmp(cfg.rt, "oic.wk.d", 8) == 0) { - oc_core_populate_resource(OCF_D, device_count, cfg.uri, - OC_IF_R | OC_IF_BASELINE, OC_IF_R, properties, - oc_core_device_handler, 0, 0, 0, 1, cfg.rt); - } else { - oc_core_populate_resource( - OCF_D, device_count, cfg.uri, OC_IF_R | OC_IF_BASELINE, OC_IF_R, - properties, oc_core_device_handler, 0, 0, 0, 2, cfg.rt, "oic.wk.d"); - } + oc_create_device_resource(device_count, cfg.uri, cfg.rt); if (oc_get_con_res_announced()) { /* Construct oic.wk.con resource for this device. */ @@ -705,8 +715,8 @@ oc_core_get_resource_type_by_uri(const char *uri, size_t uri_len) OC_CHAR_ARRAY_LEN("/oic/p"))) { return OCF_P; } - if (core_is_resource_uri(uri, uri_len, "/oic/d", - OC_CHAR_ARRAY_LEN("/oic/p"))) { + if (core_is_resource_uri(uri, uri_len, OCF_D_URI, + OC_CHAR_ARRAY_LEN(OCF_D_URI))) { return OCF_D; } if (core_is_resource_uri(uri, uri_len, OCF_RES_URI, diff --git a/api/oc_core_res_internal.h b/api/oc_core_res_internal.h index b63c5e8077..cb1aae0bf4 100644 --- a/api/oc_core_res_internal.h +++ b/api/oc_core_res_internal.h @@ -34,6 +34,9 @@ extern "C" { #endif +#define OCF_D_URI "/oic/d" +#define OCF_D_RT "oic.wk.d" + /** * @brief initialize the core functionality */ diff --git a/api/oc_discovery.c b/api/oc_discovery.c index 31380ed9ee..ebd9c1cd94 100644 --- a/api/oc_discovery.c +++ b/api/oc_discovery.c @@ -1201,8 +1201,8 @@ oc_wkcore_discovery_handler(oc_request_t *request, const char *rt_device = NULL; size_t rt_devlen = 0; size_t device = request->resource->device; - oc_resource_t *resource = - oc_core_get_resource_by_uri_v1("oic/d", OC_CHAR_ARRAY_LEN("oic/d"), device); + oc_resource_t *resource = oc_core_get_resource_by_uri_v1( + OCF_D_URI, OC_CHAR_ARRAY_LEN(OCF_D_URI), device); for (size_t i = 0; i < oc_string_array_get_allocated_size(resource->types); ++i) { size_t size = oc_string_array_get_item_size(resource->types, i); @@ -1227,7 +1227,6 @@ oc_wkcore_discovery_handler(oc_request_t *request, // oic.d.sensor";if="oic.if.11 oic.if.baseline" size_t length = clf_add_line_to_buffer("<"); - response_length += length; oc_endpoint_t *eps = oc_connectivity_get_endpoints(request->resource->device); @@ -1240,22 +1239,17 @@ oc_wkcore_discovery_handler(oc_request_t *request, } oc_string64_t ep; if (oc_endpoint_to_string64(eps, &ep)) { - length = clf_add_str_to_buffer(oc_string(ep), oc_string_len(ep)); - response_length += length; + length += clf_add_str_to_buffer(oc_string(ep), oc_string_len(ep)); break; } eps = eps->next; } - length = clf_add_line_to_buffer("/oic/res>;"); - response_length += length; - length = clf_add_line_to_buffer("rt=\"oic.wk.res "); - response_length += length; - length = clf_add_str_to_buffer(rt_device, rt_devlen); - response_length += length; - length = clf_add_line_to_buffer("\";"); - response_length += length; - length = + length += clf_add_line_to_buffer("/oic/res>;"); + length += clf_add_line_to_buffer("rt=\"oic.wk.res "); + length += clf_add_str_to_buffer(rt_device, rt_devlen); + length += clf_add_line_to_buffer("\";"); + length += clf_add_line_to_buffer("if=\"oic.if.ll oic.if.baseline\";ct=10000"); response_length += length; } diff --git a/api/oc_helpers.c b/api/oc_helpers.c index b81904b6c5..4599bb61f6 100644 --- a/api/oc_helpers.c +++ b/api/oc_helpers.c @@ -176,6 +176,9 @@ bool oc_string_is_cstr_equal(const oc_string_t *str1, const char *str2, size_t str2_len) { + if (str1 == NULL || oc_string(*str1) == NULL) { + return str2 == NULL; + } return oc_string_view_is_equal(oc_string_view2(str1), oc_string_view(str2, str2_len)); } diff --git a/api/oc_helpers_internal.h b/api/oc_helpers_internal.h index 5aab0087f6..cd2007ad3b 100644 --- a/api/oc_helpers_internal.h +++ b/api/oc_helpers_internal.h @@ -110,14 +110,14 @@ bool oc_string_is_equal(const oc_string_t *str1, const oc_string_t *str2) /** * @brief Compare an oc_string with a C-string * - * @param str1 oc_string (cannot be NULL) - * @param str2 C-string (cannot be NULL) + * @param str1 oc_string + * @param str2 C-string * @param str2_len length of \p str2 * @return true strings are equal * @return false strings are not equal */ bool oc_string_is_cstr_equal(const oc_string_t *str1, const char *str2, - size_t str2_len) OC_NONNULL(); + size_t str2_len); /** * @brief Fill buffer with random values. diff --git a/api/oc_rep.c b/api/oc_rep.c index df9343c1b2..a2fc9af3e0 100644 --- a/api/oc_rep.c +++ b/api/oc_rep.c @@ -24,9 +24,10 @@ #include "oc_config.h" #include "port/oc_assert.h" #include "port/oc_log_internal.h" +#include "util/oc_features.h" #include "util/oc_macros_internal.h" #include "util/oc_memb.h" -#include "util/oc_features.h" +#include "util/oc_secure_string_internal.h" #include @@ -35,13 +36,6 @@ CborEncoder root_map; CborEncoder links_array; int g_err; -typedef enum oc_rep_error_t { - OC_REP_NO_ERROR = 0, - - OC_REP_ERROR_INTERNAL = -1, - OC_REP_ERROR_OUT_OF_MEMORY = -2, -} oc_rep_error_t; - void oc_rep_set_pool(struct oc_memb *rep_objects_pool) { @@ -52,12 +46,13 @@ void oc_rep_new_v1(uint8_t *payload, size_t size) { g_err = CborNoError; - oc_rep_encoder_init(payload, size); + oc_rep_buffer_init(payload, size); } void oc_rep_new(uint8_t *payload, int size) { + assert(size >= 0); oc_rep_new_v1(payload, (size_t)size); } @@ -67,7 +62,7 @@ void oc_rep_new_realloc_v1(uint8_t **payload, size_t size, size_t max_size) { g_err = CborNoError; - oc_rep_encoder_realloc_init(payload, size, max_size); + oc_rep_buffer_realloc_init(payload, size, max_size); } void @@ -85,27 +80,20 @@ oc_rep_get_cbor_errno(void) return g_err; } -static oc_rep_t * -alloc_rep_internal(void) +oc_rep_t * +oc_alloc_rep(void) { oc_rep_t *rep = (oc_rep_t *)oc_memb_alloc(g_rep_objects); - if (rep != NULL) { - rep->name.size = 0; - } #ifdef OC_DEBUG oc_assert(rep != NULL); #endif + if (rep == NULL) { + return NULL; + } + rep->name.size = 0; return rep; } -#ifdef OC_HAS_FEATURE_PUSH -oc_rep_t * -oc_alloc_rep(void) -{ - return alloc_rep_internal(); -} -#endif - static void free_rep_internal(oc_rep_t *rep_value) { @@ -152,493 +140,26 @@ oc_free_rep(oc_rep_t *rep) free_rep_internal(rep); } -/* - An Object is a collection of key-value pairs. - A value_object value points to the first key-value pair, - and subsequent items are accessed via the next pointer. - - An Object Array is a collection of objects, where each object - is a collection of key-value pairs. - A value_object_array value points to the first object in the - array. This object is then traversed via its value_object pointer. - Subsequent objects in the object array are then accessed through - the next pointer of the first object. -*/ - -static CborError -oc_parse_rep_key(const CborValue *value, oc_rep_t **rep) -{ - oc_rep_t *cur = *rep; - if (!cbor_value_is_text_string(value)) { - return CborErrorIllegalType; - } - size_t len; - CborError err = cbor_value_calculate_string_length(value, &len); - if (err != CborNoError) { - return err; - } - len++; - if (len == 0) { - return CborErrorInternalError; - } - oc_alloc_string(&cur->name, len); - return cbor_value_copy_text_string(value, oc_string(cur->name), &len, NULL); -} - -typedef struct -{ - CborValue value; - oc_rep_value_type_t type; - size_t length; -} oc_parse_array_rep_t; - -static int -cbor_type_to_oc_rep_value_type(CborType type) -{ - switch (type) { - case CborIntegerType: - return OC_REP_INT; - case CborDoubleType: - return OC_REP_DOUBLE; - case CborBooleanType: - return OC_REP_BOOL; - case CborMapType: - return OC_REP_OBJECT; - case CborArrayType: - return OC_REP_ARRAY; - case CborByteStringType: - return OC_REP_BYTE_STRING; - case CborTextStringType: - return OC_REP_STRING; - default: - break; - } - return OC_REP_ERROR_INTERNAL; -} - -static CborError -oc_parse_rep_array_init(const CborValue *value, oc_parse_array_rep_t *rep_array) -{ - CborValue array; - CborError err = cbor_value_enter_container(value, &array); - if (err != CborNoError) { - return err; - } - size_t len = 0; - cbor_value_get_array_length(value, &len); - if (len == 0) { - CborValue t = array; - while (!cbor_value_at_end(&t)) { - len++; - err = cbor_value_advance(&t); - if (err != CborNoError) { - return err; - } - } - } - - if (len == 0) { - rep_array->value = array; - rep_array->type = OC_REP_NIL; - rep_array->length = len; - return CborNoError; - } - - // we support only arrays with a single type - int ret = cbor_type_to_oc_rep_value_type(array.type); - if (ret < 0) { - return CborErrorIllegalType; - } - oc_rep_value_type_t value_type = (oc_rep_value_type_t)ret; - - rep_array->value = array; - rep_array->type = value_type | OC_REP_ARRAY; - rep_array->length = len; - return CborNoError; -} - -static oc_rep_error_t -oc_rep_array_init(oc_rep_t *rep, oc_rep_value_type_t array_type, size_t len) -{ - switch (array_type) { - case OC_REP_INT_ARRAY: - oc_new_int_array(&rep->value.array, len); - break; - case OC_REP_DOUBLE_ARRAY: - oc_new_double_array(&rep->value.array, len); - break; - case OC_REP_BOOL_ARRAY: - oc_new_bool_array(&rep->value.array, len); - break; - case OC_REP_BYTE_STRING_ARRAY: // NOLINT(bugprone-branch-clone) - oc_new_byte_string_array(&rep->value.array, len); - break; - case OC_REP_STRING_ARRAY: - oc_new_string_array(&rep->value.array, len); - break; - case OC_REP_OBJECT_ARRAY: - rep->value.object_array = alloc_rep_internal(); - if (rep->value.object_array == NULL) { - return OC_REP_ERROR_OUT_OF_MEMORY; - } - break; - default: - return OC_REP_ERROR_INTERNAL; - } - - rep->type = array_type; - return OC_REP_NO_ERROR; -} - -static CborError -oc_rep_array_value_type_check(const oc_rep_t *rep, oc_rep_value_type_t type) -{ - if ((rep->type & type) != type) { - return CborErrorIllegalType; - } - return CborNoError; -} - -static CborError oc_parse_rep_object(CborValue *value, oc_rep_t **rep); - -static CborError -oc_parse_rep_object_array(CborValue *array, size_t array_len, oc_rep_t *rep) -{ - oc_rep_t **prev = &rep->value.object_array; - size_t k = 0; - while (!cbor_value_at_end(array)) { - if (array->type != CborMapType) { - return CborErrorIllegalType; - } - if (k > 0) { - if (prev == NULL) { - return CborErrorInternalError; - } - if ((*prev) == NULL) { - return CborErrorOutOfMemory; - } - (*prev)->next = alloc_rep_internal(); - if ((*prev)->next == NULL) { - return CborErrorOutOfMemory; - } - prev = &(*prev)->next; - } - (*prev)->type = OC_REP_OBJECT; - (*prev)->next = NULL; - oc_rep_t **obj = &(*prev)->value.object; - /* Process a series of properties that make up an object of the array */ - CborValue map; - CborError err = cbor_value_enter_container(array, &map); - if (err != CborNoError) { - return err; - } - while (!cbor_value_at_end(&map) && (err == CborNoError)) { - err |= oc_parse_rep_object(&map, obj); - obj = &(*obj)->next; - err |= cbor_value_advance(&map); - } - if (err != CborNoError) { - return err; - } - ++k; - err = cbor_value_advance(array); - if (err != CborNoError) { - return err; - } - } - - if (k != array_len) { - return CborErrorInternalError; - } - return CborNoError; -} - -static CborError -oc_parse_rep_simple_array(CborValue *array, size_t array_len, oc_rep_t *rep) -{ - size_t k = 0; - while (!cbor_value_at_end(array)) { - int ret = cbor_type_to_oc_rep_value_type(array->type); - if (ret < 0) { - return CborErrorIllegalType; - } - oc_rep_value_type_t value_type = (oc_rep_value_type_t)ret; - CborError err = oc_rep_array_value_type_check(rep, value_type); - if (err != CborNoError) { - return err; - } - - switch (rep->type) { - case OC_REP_INT_ARRAY: - err = cbor_value_get_int64(array, oc_int_array(rep->value.array) + k); - break; - case OC_REP_DOUBLE_ARRAY: - err = cbor_value_get_double(array, oc_double_array(rep->value.array) + k); - break; - case OC_REP_BOOL_ARRAY: - err = cbor_value_get_boolean(array, oc_bool_array(rep->value.array) + k); - break; - case OC_REP_BYTE_STRING_ARRAY: { - size_t len = 0; - err |= cbor_value_calculate_string_length(array, &len); - if (len >= STRING_ARRAY_ITEM_MAX_LEN) { - len = STRING_ARRAY_ITEM_MAX_LEN - 1; - } - uint8_t *size = - (uint8_t *)oc_byte_string_array_get_item(rep->value.array, k); - size -= 1; - *size = (uint8_t)len; - err |= cbor_value_copy_byte_string( - array, (uint8_t *)oc_byte_string_array_get_item(rep->value.array, k), - &len, NULL); - } break; - case OC_REP_STRING_ARRAY: { - size_t len = 0; - err |= cbor_value_calculate_string_length(array, &len); - len++; - if (len > STRING_ARRAY_ITEM_MAX_LEN) { - len = STRING_ARRAY_ITEM_MAX_LEN; - } - err |= cbor_value_copy_text_string( - array, oc_string_array_get_item(rep->value.array, k), &len, NULL); - } break; - default: - return CborErrorIllegalType; - } - if (err != CborNoError) { - return err; - } - ++k; - err = cbor_value_advance(array); - if (err != CborNoError) { - return err; - } - } - if (k != array_len) { - return CborErrorInternalError; - } - return CborNoError; -} - -static CborError -oc_parse_rep_array(const CborValue *value, oc_rep_t *rep) -{ - oc_parse_array_rep_t rep_array; - CborError err = oc_parse_rep_array_init(value, &rep_array); - if (err != CborNoError) { - return err; - } - if (rep_array.length == 0) { - return CborNoError; - } - - oc_rep_error_t rep_err = - oc_rep_array_init(rep, rep_array.type, rep_array.length); - if (rep_err != OC_REP_NO_ERROR) { - OC_ERR("initialize rep array error(%d)", rep_err); - return rep_err == OC_REP_ERROR_OUT_OF_MEMORY ? CborErrorOutOfMemory - : CborErrorInternalError; - } - - if (rep->type == OC_REP_OBJECT_ARRAY) { - return oc_parse_rep_object_array(&rep_array.value, rep_array.length, rep); - } - return oc_parse_rep_simple_array(&rep_array.value, rep_array.length, rep); -} - -static CborError -oc_parse_rep_value(CborValue *value, oc_rep_t **rep) -{ - /* skip over CBOR Tags */ - CborError err = cbor_value_skip_tag(value); - if (err != CborNoError) { - return err; - } - - oc_rep_t *cur = *rep; - switch (value->type) { - case CborIntegerType: { - err = cbor_value_get_int64(value, &cur->value.integer); - cur->type = OC_REP_INT; - return err; - } - case CborBooleanType: { - err = cbor_value_get_boolean(value, &cur->value.boolean); - cur->type = OC_REP_BOOL; - return err; - } - case CborDoubleType: { - err = cbor_value_get_double(value, &cur->value.double_p); - cur->type = OC_REP_DOUBLE; - return err; - } - case CborByteStringType: { - size_t len; - err = cbor_value_calculate_string_length(value, &len); - if (err != CborNoError) { - return err; - } - len++; - if (len == 0) { - return CborErrorInternalError; - } - cur->type = OC_REP_BYTE_STRING; - oc_alloc_string(&cur->value.string, len); - err |= cbor_value_copy_byte_string( - value, oc_cast(cur->value.string, uint8_t), &len, NULL); - return err; - } - case CborTextStringType: { - size_t len; - err = cbor_value_calculate_string_length(value, &len); - if (err != CborNoError) { - return err; - } - len++; - if (len == 0) { - return CborErrorInternalError; - } - cur->type = OC_REP_STRING; - oc_alloc_string(&cur->value.string, len); - err |= cbor_value_copy_text_string(value, oc_string(cur->value.string), - &len, NULL); - return err; - } - case CborMapType: { - oc_rep_t **obj = &cur->value.object; - cur->type = OC_REP_OBJECT; - CborValue map; - err = cbor_value_enter_container(value, &map); - while (!cbor_value_at_end(&map)) { - err = oc_parse_rep_object(&map, obj); - if (err != CborNoError) { - return err; - } - (*obj)->next = NULL; - obj = &(*obj)->next; - err |= cbor_value_advance(&map); - } - return err; - } - case CborArrayType: - return oc_parse_rep_array(value, cur); - case CborInvalidType: - return CborErrorIllegalType; - default: - break; - } - - return CborNoError; -} - -/* Parse single property */ -static CborError -oc_parse_rep_object(CborValue *value, oc_rep_t **rep) -{ - oc_rep_t *cur = alloc_rep_internal(); - if (cur == NULL) { - return CborErrorOutOfMemory; - } - cur->next = NULL; - cur->value.object_array = NULL; - - CborError err = oc_parse_rep_key(value, &cur); - if (err != CborNoError) { - OC_ERR("failed to parse rep: cannot parse key(%d)", err); - oc_free_rep(cur); - return err; - } - err = cbor_value_advance(value); - if (err != CborNoError) { - OC_ERR("failed to parse rep: cannot advance iterator(%d)", err); - oc_free_rep(cur); - return err; - } - - err = oc_parse_rep_value(value, &cur); - if (err != CborNoError) { - OC_ERR("failed to parse rep: cannot parse value(%d)", err); - oc_free_rep(cur); - return err; - } - - *rep = cur; - return CborNoError; -} - -int -oc_parse_rep(const uint8_t *in_payload, size_t payload_size, oc_rep_t **out_rep) -{ - if (out_rep == NULL) { - return -1; - } - CborParser parser; - CborValue root_value; - CborError err = - cbor_parser_init(in_payload, payload_size, 0, &parser, &root_value); - if (err != CborNoError) { - return err; - } - *out_rep = NULL; - if (cbor_value_is_map(&root_value)) { - CborValue cur_value; - err = cbor_value_enter_container(&root_value, &cur_value); - oc_rep_t **cur = out_rep; - while (cbor_value_is_valid(&cur_value) && err == CborNoError) { - err |= oc_parse_rep_object(&cur_value, cur); - if (err != CborNoError) { - return err; - } - err |= cbor_value_advance(&cur_value); - assert(*cur != NULL); - cur = &(*cur)->next; - } - return err; - } - if (cbor_value_is_array(&root_value)) { - CborValue map; - err = cbor_value_enter_container(&root_value, &map); - oc_rep_t **cur = out_rep; - while (cbor_value_is_valid(&map)) { - *cur = alloc_rep_internal(); - if (*cur == NULL) { - return CborErrorOutOfMemory; - } - (*cur)->type = OC_REP_OBJECT; - oc_rep_t **kv = &(*cur)->value.object; - CborValue cur_value; - err |= cbor_value_enter_container(&map, &cur_value); - while (cbor_value_is_valid(&cur_value) && err == CborNoError) { - err |= oc_parse_rep_object(&cur_value, kv); - err |= cbor_value_advance(&cur_value); - assert(*kv != NULL); - (*kv)->next = NULL; - kv = &(*kv)->next; - } - (*cur)->next = NULL; - cur = &(*cur)->next; - err |= cbor_value_advance(&map); - if (err != CborNoError) { - return err; - } - } - return err; - } - return CborNoError; -} - static bool oc_rep_get_value(const oc_rep_t *rep, oc_rep_value_type_t type, const char *key, - void **value, size_t *size) + size_t key_len, void **value, size_t *size) { - if (!rep || !key || !value) { - OC_ERR("Error of input parameters"); + if (rep == NULL) { + OC_ERR("Error of input parameters: invalid rep"); + return false; + } + if (value == NULL) { + OC_ERR("Error of input parameters: invalid value"); + return false; + } + if (key_len == 0 || key_len >= OC_MAX_STRING_LENGTH) { + OC_ERR("Error of input parameters: invalid key"); return false; } const oc_rep_t *rep_value = rep; while (rep_value != NULL) { - if ((oc_string_len(rep_value->name) == strlen(key)) && + if ((oc_string_len(rep_value->name) == key_len) && (strncmp(key, oc_string(rep_value->name), oc_string_len(rep_value->name)) == 0) && (rep_value->type == type)) { @@ -698,56 +219,62 @@ oc_rep_get_value(const oc_rep_t *rep, oc_rep_value_type_t type, const char *key, bool oc_rep_is_null(const oc_rep_t *rep, const char *key, bool *is_null) { - if (!is_null) { + if (is_null == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_NIL, key, (void **)&is_null, - (size_t *)NULL); + return oc_rep_get_value(rep, OC_REP_NIL, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&is_null, NULL); } bool oc_rep_get_int(const oc_rep_t *rep, const char *key, int64_t *value) { - if (!value) { + if (value == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_INT, key, (void **)&value, - (size_t *)NULL); + return oc_rep_get_value(rep, OC_REP_INT, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&value, NULL); } bool oc_rep_get_bool(const oc_rep_t *rep, const char *key, bool *value) { - if (!value) { + if (value == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_BOOL, key, (void **)&value, - (size_t *)NULL); + return oc_rep_get_value(rep, OC_REP_BOOL, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&value, NULL); } bool oc_rep_get_double(const oc_rep_t *rep, const char *key, double *value) { - if (!value) { + if (value == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_DOUBLE, key, (void **)&value, - (size_t *)NULL); + return oc_rep_get_value(rep, OC_REP_DOUBLE, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&value, NULL); } bool oc_rep_get_byte_string(const oc_rep_t *rep, const char *key, char **value, size_t *size) { - if (!size) { + if (size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_BYTE_STRING, key, (void **)value, size); + return oc_rep_get_value(rep, OC_REP_BYTE_STRING, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, size); } bool @@ -758,79 +285,94 @@ oc_rep_get_string(const oc_rep_t *rep, const char *key, char **value, // the value parameter should be changed to const since it points to a // value of const oc_rep_t* - if (!size) { + if (size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_STRING, key, (void **)value, size); + return oc_rep_get_value(rep, OC_REP_STRING, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, size); } bool oc_rep_get_int_array(const oc_rep_t *rep, const char *key, int64_t **value, size_t *size) { - if (!size) { + if (size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_INT_ARRAY, key, (void **)value, size); + return oc_rep_get_value(rep, OC_REP_INT_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, size); } bool oc_rep_get_bool_array(const oc_rep_t *rep, const char *key, bool **value, size_t *size) { - if (!size) { + if (size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_BOOL_ARRAY, key, (void **)value, size); + return oc_rep_get_value(rep, OC_REP_BOOL_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, size); } bool oc_rep_get_double_array(const oc_rep_t *rep, const char *key, double **value, size_t *size) { - if (!size) { + if (size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_DOUBLE_ARRAY, key, (void **)value, size); + return oc_rep_get_value(rep, OC_REP_DOUBLE_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, size); } bool oc_rep_get_byte_string_array(const oc_rep_t *rep, const char *key, oc_string_array_t *value, size_t *size) { - if (!value || !size) { + if (value == NULL || size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_BYTE_STRING_ARRAY, key, (void **)&value, - size); + return oc_rep_get_value(rep, OC_REP_BYTE_STRING_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&value, size); } bool oc_rep_get_string_array(const oc_rep_t *rep, const char *key, oc_string_array_t *value, size_t *size) { - if (!value || !size) { + if (value == NULL || size == NULL) { OC_ERR("Error of input parameters"); return false; } - return oc_rep_get_value(rep, OC_REP_STRING_ARRAY, key, (void **)&value, size); + return oc_rep_get_value(rep, OC_REP_STRING_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)&value, size); } bool oc_rep_get_object(const oc_rep_t *rep, const char *key, oc_rep_t **value) { - return oc_rep_get_value(rep, OC_REP_OBJECT, key, (void **)value, NULL); + return oc_rep_get_value(rep, OC_REP_OBJECT, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, NULL); } bool oc_rep_get_object_array(const oc_rep_t *rep, const char *key, oc_rep_t **value) { - return oc_rep_get_value(rep, OC_REP_OBJECT_ARRAY, key, (void **)value, NULL); + return oc_rep_get_value(rep, OC_REP_OBJECT_ARRAY, key, + oc_strnlen_s(key, OC_MAX_STRING_LENGTH), + (void **)value, NULL); } bool diff --git a/api/oc_rep_decode.c b/api/oc_rep_decode.c new file mode 100644 index 0000000000..cc675a6332 --- /dev/null +++ b/api/oc_rep_decode.c @@ -0,0 +1,566 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "api/oc_rep_decode_internal.h" +#include "api/oc_rep_internal.h" +#include "port/oc_log_internal.h" + +#ifdef OC_JSON_ENCODER +#include "api/oc_rep_decode_json_internal.h" +#endif /* OC_JSON_ENCODER */ + +typedef CborError (*oc_rep_parse_payload_t)(const uint8_t *payload, + size_t payload_size, + oc_rep_t **out_rep); + +typedef struct +{ + oc_rep_decoder_type_t type; + oc_rep_parse_payload_t parse; +} oc_rep_decoder_t; + +static int oc_rep_parse_cbor(const uint8_t *payload, size_t payload_size, + oc_rep_t **out_rep); + +static oc_rep_decoder_t g_rep_decoder = { + .type = OC_REP_CBOR_DECODER, + .parse = &oc_rep_parse_cbor, +}; + +static CborError rep_parse_object(CborValue *value, oc_rep_t **rep); + +static CborError +rep_parse_object_array(CborValue *array, size_t array_len, oc_rep_t *rep) +{ + oc_rep_t **prev = &rep->value.object_array; + size_t k = 0; + while (!cbor_value_at_end(array)) { + if (array->type != CborMapType) { + return CborErrorIllegalType; + } + if (k > 0) { + if (prev == NULL) { + return CborErrorInternalError; + } + if ((*prev) == NULL) { + return CborErrorOutOfMemory; + } + (*prev)->next = oc_alloc_rep(); + if ((*prev)->next == NULL) { + return CborErrorOutOfMemory; + } + prev = &(*prev)->next; + } + (*prev)->type = OC_REP_OBJECT; + (*prev)->next = NULL; + oc_rep_t **obj = &(*prev)->value.object; + /* Process a series of properties that make up an object of the array */ + CborValue map; + CborError err = cbor_value_enter_container(array, &map); + if (err != CborNoError) { + return err; + } + while (!cbor_value_at_end(&map) && (err == CborNoError)) { + err |= rep_parse_object(&map, obj); + obj = &(*obj)->next; + err |= cbor_value_advance(&map); + } + if (err != CborNoError) { + return err; + } + ++k; + err = cbor_value_advance(array); + if (err != CborNoError) { + return err; + } + } + + if (k != array_len) { + return CborErrorInternalError; + } + return CborNoError; +} + +static CborError +rep_array_value_type_check(const oc_rep_t *rep, oc_rep_value_type_t type) +{ + if ((rep->type & type) != type) { + return CborErrorIllegalType; + } + return CborNoError; +} + +static int +cbor_type_to_oc_rep_value_type(CborType type) +{ + switch (type) { + case CborIntegerType: + return OC_REP_INT; + case CborDoubleType: + return OC_REP_DOUBLE; + case CborBooleanType: + return OC_REP_BOOL; + case CborMapType: + return OC_REP_OBJECT; + case CborArrayType: + return OC_REP_ARRAY; + case CborByteStringType: + return OC_REP_BYTE_STRING; + case CborTextStringType: + return OC_REP_STRING; + default: + break; + } + return OC_REP_ERROR_INTERNAL; +} + +static CborError +rep_parse_simple_array(CborValue *array, size_t array_len, oc_rep_t *rep) +{ + size_t k = 0; + while (!cbor_value_at_end(array)) { + int ret = cbor_type_to_oc_rep_value_type(array->type); + if (ret < 0) { + return CborErrorIllegalType; + } + oc_rep_value_type_t value_type = (oc_rep_value_type_t)ret; + CborError err = rep_array_value_type_check(rep, value_type); + if (err != CborNoError) { + return err; + } + + switch (rep->type) { + case OC_REP_INT_ARRAY: + err = cbor_value_get_int64(array, oc_int_array(rep->value.array) + k); + break; + case OC_REP_DOUBLE_ARRAY: + err = cbor_value_get_double(array, oc_double_array(rep->value.array) + k); + break; + case OC_REP_BOOL_ARRAY: + err = cbor_value_get_boolean(array, oc_bool_array(rep->value.array) + k); + break; + case OC_REP_BYTE_STRING_ARRAY: { + size_t len = 0; + err |= cbor_value_calculate_string_length(array, &len); + if (len >= STRING_ARRAY_ITEM_MAX_LEN) { + len = STRING_ARRAY_ITEM_MAX_LEN - 1; + } + uint8_t *size = + (uint8_t *)oc_byte_string_array_get_item(rep->value.array, k); + size -= 1; + *size = (uint8_t)len; + err |= cbor_value_copy_byte_string( + array, (uint8_t *)oc_byte_string_array_get_item(rep->value.array, k), + &len, NULL); + } break; + case OC_REP_STRING_ARRAY: { + size_t len = 0; + err |= cbor_value_calculate_string_length(array, &len); + len++; + if (len > STRING_ARRAY_ITEM_MAX_LEN) { + len = STRING_ARRAY_ITEM_MAX_LEN; + } + err |= cbor_value_copy_text_string( + array, oc_string_array_get_item(rep->value.array, k), &len, NULL); + } break; + default: + return CborErrorIllegalType; + } + if (err != CborNoError) { + return err; + } + ++k; + err = cbor_value_advance(array); + if (err != CborNoError) { + return err; + } + } + if (k != array_len) { + return CborErrorInternalError; + } + return CborNoError; +} + +static oc_rep_error_t +rep_array_init(oc_rep_t *rep, oc_rep_value_type_t array_type, size_t len) +{ + switch (array_type) { + case OC_REP_INT_ARRAY: + oc_new_int_array(&rep->value.array, len); + break; + case OC_REP_DOUBLE_ARRAY: + oc_new_double_array(&rep->value.array, len); + break; + case OC_REP_BOOL_ARRAY: + oc_new_bool_array(&rep->value.array, len); + break; + case OC_REP_BYTE_STRING_ARRAY: // NOLINT(bugprone-branch-clone) + oc_new_byte_string_array(&rep->value.array, len); + break; + case OC_REP_STRING_ARRAY: + oc_new_string_array(&rep->value.array, len); + break; + case OC_REP_OBJECT_ARRAY: + rep->value.object_array = oc_alloc_rep(); + if (rep->value.object_array == NULL) { + return OC_REP_ERROR_OUT_OF_MEMORY; + } + break; + default: + return OC_REP_ERROR_INTERNAL; + } + + rep->type = array_type; + return OC_REP_NO_ERROR; +} + +typedef struct +{ + CborValue value; + oc_rep_value_type_t type; + size_t length; +} oc_parse_array_rep_t; + +static CborError +rep_parse_array_init(const CborValue *value, oc_parse_array_rep_t *rep_array) +{ + CborValue array; + CborError err = cbor_value_enter_container(value, &array); + if (err != CborNoError) { + return err; + } + size_t len = 0; + cbor_value_get_array_length(value, &len); + if (len == 0) { + CborValue t = array; + while (!cbor_value_at_end(&t)) { + len++; + err = cbor_value_advance(&t); + if (err != CborNoError) { + return err; + } + } + } + + if (len == 0) { + rep_array->value = array; + rep_array->type = OC_REP_NIL; + rep_array->length = len; + return CborNoError; + } + + // we support only arrays with a single type + int ret = cbor_type_to_oc_rep_value_type(array.type); + if (ret < 0) { + return CborErrorIllegalType; + } + oc_rep_value_type_t value_type = (oc_rep_value_type_t)ret; + + rep_array->value = array; + rep_array->type = value_type | OC_REP_ARRAY; + rep_array->length = len; + return CborNoError; +} + +static CborError +rep_parse_array(const CborValue *value, oc_rep_t *rep) +{ + oc_parse_array_rep_t rep_array; + CborError err = rep_parse_array_init(value, &rep_array); + if (err != CborNoError) { + return err; + } + if (rep_array.length == 0) { + return CborNoError; + } + + oc_rep_error_t rep_err = + rep_array_init(rep, rep_array.type, rep_array.length); + if (rep_err != OC_REP_NO_ERROR) { + OC_ERR("initialize rep array error(%d)", rep_err); + return rep_err == OC_REP_ERROR_OUT_OF_MEMORY ? CborErrorOutOfMemory + : CborErrorInternalError; + } + + if (rep->type == OC_REP_OBJECT_ARRAY) { + return rep_parse_object_array(&rep_array.value, rep_array.length, rep); + } + return rep_parse_simple_array(&rep_array.value, rep_array.length, rep); +} + +static CborError +oc_parse_rep_value(CborValue *value, oc_rep_t **rep) +{ + /* skip over CBOR Tags */ + CborError err = cbor_value_skip_tag(value); + if (err != CborNoError) { + return err; + } + + oc_rep_t *cur = *rep; + switch (value->type) { + case CborIntegerType: { + err = cbor_value_get_int64(value, &cur->value.integer); + cur->type = OC_REP_INT; + return err; + } + case CborBooleanType: { + err = cbor_value_get_boolean(value, &cur->value.boolean); + cur->type = OC_REP_BOOL; + return err; + } + case CborDoubleType: { + err = cbor_value_get_double(value, &cur->value.double_p); + cur->type = OC_REP_DOUBLE; + return err; + } + case CborByteStringType: { + size_t len; + err = cbor_value_calculate_string_length(value, &len); + if (err != CborNoError) { + return err; + } + len++; + if (len == 0) { + return CborErrorInternalError; + } + cur->type = OC_REP_BYTE_STRING; + oc_alloc_string(&cur->value.string, len); + err |= cbor_value_copy_byte_string( + value, oc_cast(cur->value.string, uint8_t), &len, NULL); + return err; + } + case CborTextStringType: { + size_t len; + err = cbor_value_calculate_string_length(value, &len); + if (err != CborNoError) { + return err; + } + len++; + if (len == 0) { + return CborErrorInternalError; + } + cur->type = OC_REP_STRING; + oc_alloc_string(&cur->value.string, len); + err |= cbor_value_copy_text_string(value, oc_string(cur->value.string), + &len, NULL); + return err; + } + case CborMapType: { + oc_rep_t **obj = &cur->value.object; + cur->type = OC_REP_OBJECT; + CborValue map; + err = cbor_value_enter_container(value, &map); + while (!cbor_value_at_end(&map)) { + err = rep_parse_object(&map, obj); + if (err != CborNoError) { + return err; + } + (*obj)->next = NULL; + obj = &(*obj)->next; + err |= cbor_value_advance(&map); + } + return err; + } + case CborArrayType: + return rep_parse_array(value, cur); + case CborInvalidType: + return CborErrorIllegalType; + default: + break; + } + + return CborNoError; +} + +/* + An Object is a collection of key-value pairs. + A value_object value points to the first key-value pair, + and subsequent items are accessed via the next pointer. + + An Object Array is a collection of objects, where each object + is a collection of key-value pairs. + A value_object_array value points to the first object in the + array. This object is then traversed via its value_object pointer. + Subsequent objects in the object array are then accessed through + the next pointer of the first object. +*/ + +static CborError +rep_parse_key(const CborValue *value, oc_rep_t **rep) +{ + oc_rep_t *cur = *rep; + if (!cbor_value_is_text_string(value)) { + return CborErrorIllegalType; + } + size_t len; + CborError err = cbor_value_calculate_string_length(value, &len); + if (err != CborNoError) { + return err; + } + len++; + if (len == 0) { + return CborErrorInternalError; + } + oc_alloc_string(&cur->name, len); + return cbor_value_copy_text_string(value, oc_string(cur->name), &len, NULL); +} + +/* Parse single property */ +static CborError +rep_parse_object(CborValue *value, oc_rep_t **rep) +{ + oc_rep_t *cur = oc_alloc_rep(); + if (cur == NULL) { + return CborErrorOutOfMemory; + } + cur->next = NULL; + cur->value.object_array = NULL; + + CborError err = rep_parse_key(value, &cur); + if (err != CborNoError) { + OC_ERR("failed to parse rep: cannot parse key(%d)", err); + oc_free_rep(cur); + return err; + } + err = cbor_value_advance(value); + if (err != CborNoError) { + OC_ERR("failed to parse rep: cannot advance iterator(%d)", err); + oc_free_rep(cur); + return err; + } + + err = oc_parse_rep_value(value, &cur); + if (err != CborNoError) { + OC_ERR("failed to parse rep: cannot parse value(%d)", err); + oc_free_rep(cur); + return err; + } + + *rep = cur; + return CborNoError; +} + +static int +oc_rep_parse_cbor(const uint8_t *payload, size_t payload_size, + oc_rep_t **out_rep) +{ + CborParser parser; + CborValue root_value; + CborError err = + cbor_parser_init(payload, payload_size, 0, &parser, &root_value); + if (err != CborNoError) { + return err; + } + *out_rep = NULL; + if (cbor_value_is_map(&root_value)) { + CborValue cur_value; + err = cbor_value_enter_container(&root_value, &cur_value); + oc_rep_t **cur = out_rep; + while (cbor_value_is_valid(&cur_value) && err == CborNoError) { + err |= rep_parse_object(&cur_value, cur); + if (err != CborNoError) { + return err; + } + err |= cbor_value_advance(&cur_value); + assert(*cur != NULL); + cur = &(*cur)->next; + } + return err; + } + if (cbor_value_is_array(&root_value)) { + CborValue map; + err = cbor_value_enter_container(&root_value, &map); + oc_rep_t **cur = out_rep; + while (cbor_value_is_valid(&map)) { + *cur = oc_alloc_rep(); + if (*cur == NULL) { + return CborErrorOutOfMemory; + } + (*cur)->type = OC_REP_OBJECT; + oc_rep_t **kv = &(*cur)->value.object; + CborValue cur_value; + err |= cbor_value_enter_container(&map, &cur_value); + while (cbor_value_is_valid(&cur_value) && err == CborNoError) { + err |= rep_parse_object(&cur_value, kv); + err |= cbor_value_advance(&cur_value); + assert(*kv != NULL); + (*kv)->next = NULL; + kv = &(*kv)->next; + } + (*cur)->next = NULL; + cur = &(*cur)->next; + err |= cbor_value_advance(&map); + if (err != CborNoError) { + return err; + } + } + return err; + } + return CborNoError; +} + +void +oc_rep_decoder_set_type(oc_rep_decoder_type_t decoder_type) +{ + g_rep_decoder.type = decoder_type; + if (g_rep_decoder.type == OC_REP_CBOR_DECODER) { + g_rep_decoder.parse = &oc_rep_parse_cbor; + return; + } +#ifdef OC_JSON_ENCODER + if (g_rep_decoder.type == OC_REP_JSON_DECODER) { + g_rep_decoder.parse = &oc_rep_parse_json; + return; + } +#endif /* OC_JSON_ENCODER */ +} + +oc_rep_decoder_type_t +oc_rep_decoder_get_type(void) +{ + return g_rep_decoder.type; +} + +bool +oc_rep_decoder_set_type_by_content_format(oc_content_format_t content_format) +{ + if (content_format == APPLICATION_CBOR || + content_format == APPLICATION_VND_OCF_CBOR || + content_format == APPLICATION_NOT_DEFINED) { + oc_rep_decoder_set_type(OC_REP_CBOR_DECODER); + return true; + } +#ifdef OC_JSON_ENCODER + if (content_format == APPLICATION_JSON || + content_format == APPLICATION_TD_JSON) { + oc_rep_decoder_set_type(OC_REP_JSON_DECODER); + return true; + } +#endif /* OC_JSON_ENCODER */ + return false; +} + +int +oc_parse_rep(const uint8_t *payload, size_t payload_size, oc_rep_t **out_rep) +{ + if (out_rep == NULL) { + return -1; + } + return g_rep_decoder.parse(payload, payload_size, out_rep); +} diff --git a/api/oc_rep_decode_internal.h b/api/oc_rep_decode_internal.h new file mode 100644 index 0000000000..8f8c7f2e93 --- /dev/null +++ b/api/oc_rep_decode_internal.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef OC_REP_DECODE_INTERNAL_H +#define OC_REP_DECODE_INTERNAL_H + +#include "oc_ri.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum oc_rep_decoder_type_t { + OC_REP_CBOR_DECODER = 0 /* default decoder */, +#ifdef OC_JSON_ENCODER + OC_REP_JSON_DECODER = 1, +#endif /* OC_JSON_ENCODER */ +} oc_rep_decoder_type_t; + +/** + * @brief Set the decoder type to decode the request payload to oc_rep_t. + * + * @param decoder_type decoder + */ +void oc_rep_decoder_set_type(oc_rep_decoder_type_t decoder_type); + +/** + * @brief Get the decoder type to decode the request payload to oc_rep_t. + * + * @return decoder + */ +oc_rep_decoder_type_t oc_rep_decoder_get_type(void); + +/** + * @brief Set the decoder type to decode the request payload to oc_rep_t + * according to the content format. + * + * @param content_format the content format + * @return true if the decoder type was set + * @return false otherwise + */ +bool oc_rep_decoder_set_type_by_content_format( + oc_content_format_t content_format); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_REP_DECODE_INTERNAL_H */ diff --git a/api/oc_rep_decode_json.c b/api/oc_rep_decode_json.c new file mode 100644 index 0000000000..47e30d8fc1 --- /dev/null +++ b/api/oc_rep_decode_json.c @@ -0,0 +1,524 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "api/oc_rep_decode_json_internal.h" +#include "port/oc_log_internal.h" +#include "util/jsmn/jsmn_internal.h" +#include "util/oc_macros_internal.h" + +#include +#include + +typedef struct +{ + oc_rep_t *root; + oc_rep_t *previous; + oc_rep_t *cur; + int err; +} rep_data_t; + +static void +json_parse_string_value(rep_data_t *data, const char *str, size_t len) +{ + data->cur->type = OC_REP_STRING; + oc_new_string(&data->cur->value.string, str, len); + data->cur = NULL; +} + +static bool +json_set_rep(rep_data_t *data, oc_rep_value_type_t value_type) +{ + if (data->cur == NULL) { + data->cur = oc_alloc_rep(); + if (data->cur == NULL) { + data->err = CborErrorOutOfMemory; + return false; + } + if (data->root == NULL) { + data->root = data->cur; + } + if (data->previous != NULL) { + data->previous->next = data->cur; + } + data->previous = data->cur; + } else if (data->cur->name.size == 0) { + data->err = CborErrorIllegalType; + return false; + } + data->cur->type = value_type; + return true; +} + +#define OC_REP_UNKNOWN_TYPE 255 + +static bool +json_parse_string(rep_data_t *data, const char *str, size_t len) +{ + if (data->cur) { + if (data->cur->name.size == 0) { + data->err = CborErrorIllegalType; + return false; + } + json_parse_string_value(data, str, len); + return true; + } + if (!json_set_rep(data, OC_REP_UNKNOWN_TYPE)) { + return false; + } + oc_new_string(&data->cur->name, str, len); + return true; +} + +static bool +json_parse_null(const char *str, size_t len) +{ +#define JSON_NULL "null" + return (len == OC_CHAR_ARRAY_LEN(JSON_NULL) && + strncmp(str, JSON_NULL, OC_CHAR_ARRAY_LEN(JSON_NULL)) == 0); +#undef JSON_NULL +} + +static bool +json_parse_bool(const char *str, size_t len, bool *value) +{ +#define JSON_TRUE "true" +#define JSON_FALSE "false" + if (len == OC_CHAR_ARRAY_LEN(JSON_TRUE) && + strncmp(str, JSON_TRUE, OC_CHAR_ARRAY_LEN(JSON_TRUE)) == 0) { + if (value != NULL) { + *value = true; + } + return true; + } + if (len == OC_CHAR_ARRAY_LEN(JSON_FALSE) && + strncmp(str, JSON_FALSE, OC_CHAR_ARRAY_LEN(JSON_FALSE)) == 0) { + if (value != NULL) { + *value = false; + } + return true; + } + return false; +#undef JSON_TRUE +#undef JSON_FALSE +} + +static bool +json_parse_int(const char *str, size_t len, int64_t *value) +{ + // ASAN with strict_string_checks=true checks that the string is + // null-terminated + char buf[32] = { 0 }; + memcpy(buf, str, MIN(len, sizeof(buf) - 1)); + buf[sizeof(buf) - 1] = '\0'; + errno = 0; + char *eptr = NULL; + int64_t val = strtoll(buf, &eptr, 10); + if (errno != 0 || eptr == buf) { + return false; + } + if (value != NULL) { + *value = val; + } + return true; +} + +static bool +json_parse_primitive(rep_data_t *data, const char *str, size_t len) +{ + (void)len; + if (data->cur == NULL) { + data->err = CborErrorIllegalType; + return false; + } + if (json_parse_null(str, len)) { + data->cur->type = OC_REP_NIL; + } else if (json_parse_bool(str, len, &data->cur->value.boolean)) { + data->cur->type = OC_REP_BOOL; + } else { + int64_t value; + if (!json_parse_int(str, len, &value)) { + data->err = CborErrorIllegalNumber; + return false; + } + data->cur->type = OC_REP_INT; + data->cur->value.integer = value; + } + data->cur = NULL; + return true; +} + +static bool json_parse_token(const jsmntok_t *token, const char *js, + void *data); + +static bool +json_parse_object(rep_data_t *data, const char *start, size_t len) +{ + rep_data_t obj_data = { + .root = NULL, + .previous = NULL, + .cur = NULL, + .err = CborNoError, + }; + jsmn_parser_t parser; + jsmn_init(&parser); + int r = jsmn_parse(&parser, start, len, json_parse_token, &obj_data); + if (r < 0) { + oc_free_rep(obj_data.root); + data->err = + obj_data.err != CborNoError ? obj_data.err : CborErrorUnexpectedEOF; + return false; + } + if (obj_data.root != NULL && + // must have a string key and a valid value + (oc_string(obj_data.root->name) == NULL || + obj_data.root->type == OC_REP_UNKNOWN_TYPE)) { + oc_free_rep(obj_data.root); + data->err = CborErrorIllegalType; + return false; + } + if (!json_set_rep(data, OC_REP_OBJECT)) { + oc_free_rep(obj_data.root); + return false; + } + data->cur->value.object = obj_data.root; + data->cur = NULL; + return true; +} + +typedef struct +{ + oc_rep_value_type_t type; + int size; + CborError err; +} array_scan_data_t; + +static bool +json_scan_array_type(const jsmntok_t *token, const char *js, void *data) +{ + array_scan_data_t *array_data = (array_scan_data_t *)data; + oc_rep_value_type_t type = OC_REP_UNKNOWN_TYPE; + if (token->type == JSMN_PRIMITIVE) { + if (json_parse_null(js + token->start, token->end - token->start)) { + type = OC_REP_NIL; + } else if (json_parse_bool(js + token->start, token->end - token->start, + NULL)) { + type = OC_REP_BOOL; + } else if (json_parse_int(js + token->start, token->end - token->start, + NULL)) { + type = OC_REP_INT; + } + } else if (token->type == JSMN_STRING) { + type = OC_REP_STRING; + } else if (token->type == JSMN_OBJECT) { + type = OC_REP_OBJECT; + } else if (token->type == JSMN_ARRAY) { + type = OC_REP_ARRAY; + } + if (type == OC_REP_UNKNOWN_TYPE) { + array_data->err = CborErrorIllegalType; + return false; + } + if (array_data->type == OC_REP_UNKNOWN_TYPE) { + array_data->type = type; + } + if (array_data->type != type) { + array_data->err = CborErrorIllegalType; + return false; + } + array_data->size++; + return true; +} + +typedef struct +{ + oc_array_t array; + size_t idx; + CborError err; +} json_array_values_t; + +static bool +json_assign_array_bool_values(const jsmntok_t *token, const char *js, + void *data) +{ + // json_scan_array_type already checked that the token is an array of booleans + assert(token->type == JSMN_PRIMITIVE); + json_array_values_t *d = (json_array_values_t *)data; + if (!json_parse_bool(js + token->start, token->end - token->start, + (oc_bool_array(d->array) + d->idx))) { + d->err = CborErrorIllegalType; + return false; + } + d->idx++; + return true; +} + +static bool +json_parse_array_bool(rep_data_t *data, const char *start, size_t len, + size_t array_size) +{ + if (!json_set_rep(data, OC_REP_BOOL_ARRAY)) { + return false; + } + oc_new_bool_array(&data->cur->value.array, array_size); + json_array_values_t arr_data = { + .array = data->cur->value.array, + .idx = 0, + .err = CborNoError, + }; + jsmn_parser_t parser; + jsmn_init(&parser); + int r = + jsmn_parse(&parser, start, len, json_assign_array_bool_values, &arr_data); + if (r < 0) { + data->err = + arr_data.err != CborNoError ? arr_data.err : CborErrorUnexpectedEOF; + return false; + } + return true; +} + +static bool +json_assign_array_int_values(const jsmntok_t *token, const char *js, void *data) +{ + // json_scan_array_type already checked that the token is an array of integers + assert(token->type == JSMN_PRIMITIVE); + json_array_values_t *d = (json_array_values_t *)data; + int64_t v; + if (!json_parse_int(js + token->start, token->end - token->start, &v)) { + d->err = CborErrorIllegalNumber; + return false; + } + *(oc_int_array(d->array) + d->idx) = v; + d->idx++; + return true; +} + +static bool +json_parse_array_int(rep_data_t *data, const char *start, size_t len, + size_t array_size) +{ + if (!json_set_rep(data, OC_REP_INT_ARRAY)) { + return false; + } + oc_new_int_array(&data->cur->value.array, array_size); + json_array_values_t arr_data = { + .array = data->cur->value.array, + .idx = 0, + .err = CborNoError, + }; + jsmn_parser_t parser; + jsmn_init(&parser); + int r = + jsmn_parse(&parser, start, len, json_assign_array_int_values, &arr_data); + if (r < 0) { + data->err = + arr_data.err != CborNoError ? arr_data.err : CborErrorUnexpectedEOF; + return false; + } + return true; +} + +static bool +json_assign_array_string_values(const jsmntok_t *token, const char *js, + void *data) +{ + // json_scan_array_type already checked that the token is an array of strings + assert(token->type == JSMN_STRING); + json_array_values_t *d = (json_array_values_t *)data; + size_t len = token->end - token->start; + if (len >= STRING_ARRAY_ITEM_MAX_LEN) { + len = STRING_ARRAY_ITEM_MAX_LEN - 1; + OC_DBG("Truncating string array item(%s) trucated to %d chars", + js + token->start, STRING_ARRAY_ITEM_MAX_LEN - 1); + } + memcpy(oc_string_array_get_item(d->array, d->idx), js + token->start, len); + oc_string_array_get_item(d->array, d->idx)[len] = '\0'; + d->idx++; + return true; +} + +static bool +json_parse_array_string(rep_data_t *data, const char *start, size_t len, + size_t array_size) +{ + if (!json_set_rep(data, OC_REP_STRING_ARRAY)) { + return false; + } + oc_new_string_array(&data->cur->value.array, array_size); + json_array_values_t arr_data = { + .array = data->cur->value.array, + .idx = 0, + .err = CborNoError, + }; + jsmn_parser_t parser; + jsmn_init(&parser); + int r = + jsmn_parse(&parser, start, len, json_assign_array_string_values, &arr_data); + if (r < 0) { + data->err = + arr_data.err != CborNoError ? arr_data.err : CborErrorUnexpectedEOF; + return false; + } + return true; +} + +static bool +json_assign_array_object_values(const jsmntok_t *token, const char *js, + void *data) +{ + // json_scan_array_type already checked that the token is an array of objects + assert(token->type == JSMN_OBJECT); + rep_data_t *d = (rep_data_t *)data; + return json_parse_object(d, js + token->start, token->end - token->start); +} + +static bool +json_parse_array_object(rep_data_t *data, const char *start, size_t len, + size_t array_size) +{ + (void)array_size; + jsmn_parser_t parser; + jsmn_init(&parser); + rep_data_t obj_data = { + .root = NULL, + .cur = NULL, + .previous = NULL, + .err = CborNoError, + }; + int r = + jsmn_parse(&parser, start, len, json_assign_array_object_values, &obj_data); + if (r < 1) { + if (obj_data.err != CborNoError) { + oc_free_rep(obj_data.cur); + return obj_data.err; + } + return CborErrorUnexpectedEOF; + } + if (!json_set_rep(data, OC_REP_OBJECT_ARRAY)) { + return false; + } + data->cur->value.object_array = obj_data.root; + return true; +} + +typedef bool (*json_array_value_parser_t)(rep_data_t *data, const char *start, + size_t len, size_t array_size); + +static bool +json_parse_array(rep_data_t *data, const char *start, size_t len) +{ + array_scan_data_t array_scan_data = { + .type = OC_REP_UNKNOWN_TYPE, + .size = 0, + .err = CborNoError, + }; + jsmn_parser_t parser; + jsmn_init(&parser); + int r = + jsmn_parse(&parser, start, len, json_scan_array_type, &array_scan_data); + if (r < 0) { + data->err = array_scan_data.err != CborNoError ? array_scan_data.err + : CborErrorUnexpectedEOF; + return false; + } + json_array_value_parser_t value_parser_fn = NULL; + switch (array_scan_data.type) { + case OC_REP_ARRAY: + data->err = CborErrorIllegalType; + return false; + case OC_REP_BOOL: + value_parser_fn = json_parse_array_bool; + break; + case OC_REP_INT: + value_parser_fn = json_parse_array_int; + break; + case OC_REP_STRING: + value_parser_fn = json_parse_array_string; + break; + case OC_REP_OBJECT: + value_parser_fn = json_parse_array_object; + break; + default: + if (!json_set_rep(data, OC_REP_NIL)) { + return false; + } + break; + } + if (value_parser_fn != NULL && + !value_parser_fn(data, start, len, array_scan_data.size)) { + return false; + } + data->cur = NULL; + return true; +} + +static bool +json_parse_token(const jsmntok_t *token, const char *js, void *data) +{ + rep_data_t *d = (rep_data_t *)data; + if (token->type == JSMN_PRIMITIVE) { + return json_parse_primitive(d, js + token->start, + token->end - token->start); + } + if (token->type == JSMN_STRING) { + return json_parse_string(d, js + token->start, token->end - token->start); + } + if (token->type == JSMN_ARRAY) { + return json_parse_array(d, js + token->start, token->end - token->start); + } + if (token->type == JSMN_OBJECT) { + return json_parse_object(d, js + token->start, token->end - token->start); + } + OC_DBG("Skipping unexpected token type: %d", token->type); + return true; +} + +int +oc_rep_parse_json(const uint8_t *json, size_t json_len, oc_rep_t **out_rep) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + rep_data_t data = { + .root = NULL, + .cur = NULL, + .previous = NULL, + .err = CborNoError, + }; + int r = + jsmn_parse(&parser, (const char *)json, json_len, json_parse_token, &data); + if (r < 1) { + return CborErrorUnexpectedEOF; + } + assert(data.err == CborNoError); + if ((data.root->type == OC_REP_OBJECT_ARRAY || + data.root->type == OC_REP_OBJECT) && + data.root->name.size == 0 && data.root->next == NULL) { + *out_rep = data.root->value.object_array; + data.root->value.object_array = NULL; + oc_free_rep(data.root); + } else { + *out_rep = data.root; + } + return CborNoError; +} + +#endif /* OC_JSON_ENCODER */ diff --git a/api/oc_rep_decode_json_internal.h b/api/oc_rep_decode_json_internal.h new file mode 100644 index 0000000000..3bf50e3954 --- /dev/null +++ b/api/oc_rep_decode_json_internal.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef OC_REP_DECODE_JSON_INTERNAL_H +#define OC_REP_DECODE_JSON_INTERNAL_H + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "oc_rep.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Parse a JSON root object or root array */ +int oc_rep_parse_json(const uint8_t *json, size_t json_len, oc_rep_t **out_rep); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_JSON_ENCODER */ + +#endif /* OC_REP_DECODE_JSON_INTERNAL_H */ diff --git a/api/oc_rep_encode.c b/api/oc_rep_encode.c index 038f5641d6..580af53249 100644 --- a/api/oc_rep_encode.c +++ b/api/oc_rep_encode.c @@ -19,19 +19,64 @@ #include "oc_rep.h" #include "oc_rep_encode_internal.h" #include "port/oc_log_internal.h" + +#ifdef OC_JSON_ENCODER +#include "oc_rep_encode_json_internal.h" +#endif /* OC_JSON_ENCODER */ + #include #include #include #include +typedef struct +{ + uint8_t *buffer; + +#ifdef OC_DYNAMIC_ALLOCATION + size_t buffer_size; + size_t buffer_max_size; + uint8_t **buffer_ptr; + bool enable_realloc; +#endif /* OC_DYNAMIC_ALLOCATION */ +} oc_rep_buffer_t; + CborEncoder g_encoder; -static uint8_t *g_buf; + +static oc_rep_buffer_t g_rep_buffer = { + .buffer = NULL, #ifdef OC_DYNAMIC_ALLOCATION -static bool g_enable_realloc; -static size_t g_buf_size; -static size_t g_buf_max_size; -static uint8_t **g_buf_ptr; + .buffer_size = 0, + .buffer_max_size = 0, + .buffer_ptr = NULL, + .enable_realloc = false, #endif /* OC_DYNAMIC_ALLOCATION */ +}; + +#define OC_REP_CBOR_ENCODER_INIT \ + { \ + .type = OC_REP_CBOR_ENCODER, \ + \ + .encode_null = &cbor_encode_null, .encode_boolean = &cbor_encode_boolean, \ + .encode_int = &cbor_encode_int, .encode_uint = &cbor_encode_uint, \ + .encode_floating_point = &cbor_encode_floating_point, \ + .encode_double = &cbor_encode_double, \ + .encode_text_string = &cbor_encode_text_string, \ + .encode_byte_string = &cbor_encode_byte_string, \ + .create_array = &cbor_encoder_create_array, \ + .create_map = &cbor_encoder_create_map, \ + .close_container = &cbor_encoder_close_container, \ + } + +static oc_rep_encoder_t g_rep_encoder = OC_REP_CBOR_ENCODER_INIT; + +oc_rep_encoder_t +oc_rep_cbor_encoder(void) +{ + return (oc_rep_encoder_t)OC_REP_CBOR_ENCODER_INIT; +} + +#undef OC_REP_CBOR_ENCODER_INIT CborEncoder * oc_rep_encoder_convert_offset_to_ptr(CborEncoder *encoder) @@ -39,11 +84,12 @@ oc_rep_encoder_convert_offset_to_ptr(CborEncoder *encoder) if (!encoder || (encoder->data.ptr && !encoder->end)) { return encoder; } - encoder->data.ptr = g_buf + (intptr_t)encoder->data.ptr; + encoder->data.ptr = g_rep_buffer.buffer + (intptr_t)encoder->data.ptr; #ifdef OC_DYNAMIC_ALLOCATION - encoder->end = g_buf ? g_buf + g_buf_size : NULL; + encoder->end = + g_rep_buffer.buffer ? g_rep_buffer.buffer + g_rep_buffer.buffer_size : NULL; #else /* OC_DYNAMIC_ALLOCATION */ - encoder->end = g_buf + (intptr_t)encoder->end; + encoder->end = g_rep_buffer.buffer + (intptr_t)encoder->end; #endif /* !OC_DYNAMIC_ALLOCATION */ return encoder; } @@ -54,8 +100,8 @@ oc_rep_encoder_convert_ptr_to_offset(CborEncoder *encoder) if (!encoder || (encoder->data.ptr && !encoder->end)) { return encoder; } - encoder->data.ptr = (uint8_t *)(encoder->data.ptr - g_buf); - encoder->end = (uint8_t *)(encoder->end - g_buf); + encoder->data.ptr = (uint8_t *)(encoder->data.ptr - g_rep_buffer.buffer); + encoder->end = (uint8_t *)(encoder->end - g_rep_buffer.buffer); return encoder; } @@ -72,52 +118,105 @@ oc_rep_encoder_get_extra_bytes_needed(CborEncoder *encoder) static CborError realloc_buffer(size_t needed) { - if (!g_enable_realloc || g_buf_size + needed > g_buf_max_size) { + if (!g_rep_buffer.enable_realloc || + g_rep_buffer.buffer_size + needed > g_rep_buffer.buffer_max_size) { return CborErrorOutOfMemory; } // preallocate buffer to avoid reallocation - if (2 * (g_buf_size + needed) < (g_buf_max_size / 4)) { - needed += g_buf_size + needed; + if (2 * (g_rep_buffer.buffer_size + needed) < + (g_rep_buffer.buffer_max_size / 4)) { + needed += g_rep_buffer.buffer_size + needed; } else { - needed = g_buf_max_size - g_buf_size; + needed = g_rep_buffer.buffer_max_size - g_rep_buffer.buffer_size; } - uint8_t *tmp = (uint8_t *)realloc(*g_buf_ptr, g_buf_size + needed); + uint8_t *tmp = (uint8_t *)realloc(*g_rep_buffer.buffer_ptr, + g_rep_buffer.buffer_size + needed); if (tmp == NULL) { return CborErrorOutOfMemory; } - *g_buf_ptr = tmp; - g_buf = tmp; - g_buf_size = g_buf_size + needed; + *g_rep_buffer.buffer_ptr = tmp; + g_rep_buffer.buffer = tmp; + g_rep_buffer.buffer_size = g_rep_buffer.buffer_size + needed; return CborNoError; } #endif /* OC_DYNAMIC_ALLOCATION */ void -oc_rep_encoder_init(uint8_t *buffer, size_t size) +oc_rep_encoder_set_type(oc_rep_encoder_type_t encoder_type) +{ + if (encoder_type == OC_REP_CBOR_ENCODER) { + g_rep_encoder = oc_rep_cbor_encoder(); + return; + } + +#ifdef OC_JSON_ENCODER + if (encoder_type == OC_REP_JSON_ENCODER) { + g_rep_encoder = oc_rep_json_encoder(); + return; + } +#endif /* OC_JSON_ENCODER */ +} + +oc_rep_encoder_type_t +oc_rep_encoder_get_type(void) +{ + return g_rep_encoder.type; +} + +bool +oc_rep_encoder_set_type_by_accept(oc_content_format_t accept) +{ + if (accept == APPLICATION_CBOR || accept == APPLICATION_VND_OCF_CBOR || + accept == APPLICATION_NOT_DEFINED) { + oc_rep_encoder_set_type(OC_REP_CBOR_ENCODER); + return true; + } +#ifdef OC_JSON_ENCODER + if (accept == APPLICATION_JSON || accept == APPLICATION_TD_JSON) { + oc_rep_encoder_set_type(OC_REP_JSON_ENCODER); + return true; + } +#endif /* OC_JSON_ENCODER */ + return false; +} + +oc_content_format_t +oc_rep_encoder_get_content_format(void) +{ +#ifdef OC_JSON_ENCODER + if (g_rep_encoder.type == OC_REP_JSON_ENCODER) { + return APPLICATION_JSON; + } +#endif /* OC_JSON_ENCODER */ + return APPLICATION_VND_OCF_CBOR; +} + +void +oc_rep_buffer_init(uint8_t *buffer, size_t size) { - g_buf = buffer; + g_rep_buffer.buffer = buffer; #ifdef OC_DYNAMIC_ALLOCATION - g_enable_realloc = false; - g_buf_size = size; - g_buf_ptr = NULL; - g_buf_max_size = size; + g_rep_buffer.enable_realloc = false; + g_rep_buffer.buffer_size = size; + g_rep_buffer.buffer_ptr = NULL; + g_rep_buffer.buffer_max_size = size; #endif /* OC_DYNAMIC_ALLOCATION */ - cbor_encoder_init(&g_encoder, g_buf, size, 0); + cbor_encoder_init(&g_encoder, g_rep_buffer.buffer, size, 0); oc_rep_encoder_convert_ptr_to_offset(&g_encoder); } #ifdef OC_DYNAMIC_ALLOCATION void -oc_rep_encoder_realloc_init(uint8_t **buffer, size_t size, size_t max_size) +oc_rep_buffer_realloc_init(uint8_t **buffer, size_t size, size_t max_size) { assert(buffer != NULL); - g_enable_realloc = true; - g_buf_size = size; - g_buf_max_size = max_size; - g_buf_ptr = buffer; - g_buf = *buffer; - cbor_encoder_init(&g_encoder, g_buf, size, 0); + g_rep_buffer.buffer_size = size; + g_rep_buffer.buffer_max_size = max_size; + g_rep_buffer.buffer_ptr = buffer; + g_rep_buffer.buffer = *buffer; + g_rep_buffer.enable_realloc = true; + cbor_encoder_init(&g_encoder, g_rep_buffer.buffer, size, 0); oc_rep_encoder_convert_ptr_to_offset(&g_encoder); } #endif /* OC_DYNAMIC_ALLOCATION */ @@ -131,14 +230,14 @@ oc_rep_get_encoder(void) const uint8_t * oc_rep_get_encoder_buf(void) { - return g_buf; + return g_rep_buffer.buffer; } #ifdef OC_DYNAMIC_ALLOCATION int oc_rep_get_encoder_buffer_size(void) { - return (int)g_buf_size; + return (int)g_rep_buffer.buffer_size; } #endif /* OC_DYNAMIC_ALLOCATION */ @@ -148,7 +247,8 @@ oc_rep_shrink_encoder_buf(uint8_t *buf) #ifndef OC_DYNAMIC_ALLOCATION return buf; #else /* !OC_DYNAMIC_ALLOCATION */ - if (!g_enable_realloc || !buf || !g_buf_ptr || buf != g_buf) + if (!g_rep_buffer.enable_realloc || !buf || !g_rep_buffer.buffer_ptr || + buf != g_rep_buffer.buffer) return buf; int size = oc_rep_get_encoded_payload_size(); if (size <= 0) { @@ -159,11 +259,11 @@ oc_rep_shrink_encoder_buf(uint8_t *buf) if (tmp == NULL && size > 0) { return buf; } - OC_DBG("cbor encoder buffer was shrinked from %d to %d", (int)g_buf_size, - size); - g_buf_size = (size_t)size; - *g_buf_ptr = tmp; - g_buf = tmp; + OC_DBG("cbor encoder buffer was shrinked from %d to %d", + (int)g_rep_buffer.buffer_size, size); + g_rep_buffer.buffer_size = (size_t)size; + *g_rep_buffer.buffer_ptr = tmp; + g_rep_buffer.buffer = tmp; return tmp; #endif /* OC_DYNAMIC_ALLOCATION */ } @@ -172,7 +272,7 @@ int oc_rep_get_encoded_payload_size(void) { oc_rep_encoder_convert_offset_to_ptr(&g_encoder); - size_t size = cbor_encoder_get_buffer_size(&g_encoder, g_buf); + size_t size = cbor_encoder_get_buffer_size(&g_encoder, g_rep_buffer.buffer); size_t needed = cbor_encoder_get_extra_bytes_needed(&g_encoder); oc_rep_encoder_convert_ptr_to_offset(&g_encoder); if (g_err == CborErrorOutOfMemory) { @@ -196,10 +296,10 @@ oc_rep_encode_raw(const uint8_t *data, size_t len) return; } #ifdef OC_DYNAMIC_ALLOCATION - size_t remaining = g_buf_size - (size_t)g_encoder.data.ptr; + size_t remaining = g_rep_buffer.buffer_size - (size_t)g_encoder.data.ptr; if (remaining < len) { size_t needed = len - remaining; - if (!g_enable_realloc) { + if (!g_rep_buffer.enable_realloc) { OC_WRN("Insufficient memory: Increase OC_MAX_APP_DATA_SIZE to " "accomodate a larger payload(+%d)", (int)needed); @@ -236,7 +336,7 @@ static CborError oc_rep_encode_null_internal(CborEncoder *encoder) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_null(encoder); + CborError err = g_rep_encoder.encode_null(encoder); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -266,7 +366,7 @@ static CborError oc_rep_encode_boolean_internal(CborEncoder *encoder, bool value) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_boolean(encoder, value); + CborError err = g_rep_encoder.encode_boolean(encoder, value); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -296,7 +396,7 @@ static CborError oc_rep_encode_int_internal(CborEncoder *encoder, int64_t value) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_int(encoder, value); + CborError err = g_rep_encoder.encode_int(encoder, value); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -326,7 +426,7 @@ static CborError oc_rep_encode_uint_internal(CborEncoder *encoder, uint64_t value) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_uint(encoder, value); + CborError err = g_rep_encoder.encode_uint(encoder, value); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -357,7 +457,7 @@ oc_rep_encode_floating_point_internal(CborEncoder *encoder, CborType fpType, const void *value) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_floating_point(encoder, fpType, value); + CborError err = g_rep_encoder.encode_floating_point(encoder, fpType, value); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -388,7 +488,7 @@ static CborError oc_rep_encode_double_internal(CborEncoder *encoder, double value) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_double(encoder, value); + CborError err = g_rep_encoder.encode_double(encoder, value); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -419,7 +519,7 @@ oc_rep_encode_text_string_internal(CborEncoder *encoder, const char *string, size_t length) { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_text_string(encoder, string, length); + CborError err = g_rep_encoder.encode_text_string(encoder, string, length); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -449,9 +549,10 @@ oc_rep_encode_text_string(CborEncoder *encoder, const char *string, static CborError oc_rep_encode_byte_string_internal(CborEncoder *encoder, const uint8_t *string, size_t length) + { oc_rep_encoder_convert_offset_to_ptr(encoder); - CborError err = cbor_encode_byte_string(encoder, string, length); + CborError err = g_rep_encoder.encode_byte_string(encoder, string, length); oc_rep_encoder_convert_ptr_to_offset(encoder); return err; } @@ -484,7 +585,7 @@ oc_rep_encoder_create_array_internal(CborEncoder *encoder, { oc_rep_encoder_convert_offset_to_ptr(encoder); oc_rep_encoder_convert_offset_to_ptr(arrayEncoder); - CborError err = cbor_encoder_create_array(encoder, arrayEncoder, length); + CborError err = g_rep_encoder.create_array(encoder, arrayEncoder, length); oc_rep_encoder_convert_ptr_to_offset(encoder); oc_rep_encoder_convert_ptr_to_offset(arrayEncoder); return err; @@ -522,7 +623,7 @@ oc_rep_encoder_create_map_internal(CborEncoder *encoder, { oc_rep_encoder_convert_offset_to_ptr(encoder); oc_rep_encoder_convert_offset_to_ptr(mapEncoder); - CborError err = cbor_encoder_create_map(encoder, mapEncoder, length); + CborError err = g_rep_encoder.create_map(encoder, mapEncoder, length); oc_rep_encoder_convert_ptr_to_offset(encoder); oc_rep_encoder_convert_ptr_to_offset(mapEncoder); return err; @@ -560,7 +661,7 @@ oc_rep_encoder_close_container_internal(CborEncoder *encoder, { oc_rep_encoder_convert_offset_to_ptr(encoder); oc_rep_encoder_convert_offset_to_ptr(containerEncoder); - CborError err = cbor_encoder_close_container(encoder, containerEncoder); + CborError err = g_rep_encoder.close_container(encoder, containerEncoder); oc_rep_encoder_convert_ptr_to_offset(encoder); oc_rep_encoder_convert_ptr_to_offset(containerEncoder); return err; diff --git a/api/oc_rep_encode_internal.h b/api/oc_rep_encode_internal.h index d5ed95e6dc..8e1b5a4968 100644 --- a/api/oc_rep_encode_internal.h +++ b/api/oc_rep_encode_internal.h @@ -19,7 +19,12 @@ #ifndef OC_REP_ENCODE_INTERNAL_H #define OC_REP_ENCODE_INTERNAL_H +#include "oc_rep.h" +#include "oc_ri.h" +#include "util/oc_compiler.h" + #include +#include #include #include @@ -27,33 +32,89 @@ extern "C" { #endif +/** Encoding interface */ +typedef CborError (*oc_rep_encode_null_t)(CborEncoder *encoder) OC_NONNULL(); + +typedef CborError (*oc_rep_encode_boolean_t)(CborEncoder *encoder, bool value) + OC_NONNULL(); + +typedef CborError (*oc_rep_encode_int_t)(CborEncoder *encoder, int64_t value) + OC_NONNULL(); + +typedef CborError (*oc_rep_encode_uint_t)(CborEncoder *encoder, uint64_t value) + OC_NONNULL(); + +typedef CborError (*oc_rep_encode_floating_point_t)(CborEncoder *encoder, + CborType fpType, + const void *value) + OC_NONNULL(); + +typedef CborError (*oc_rep_encode_double_t)(CborEncoder *encoder, double value) + OC_NONNULL(); + +typedef CborError (*oc_rep_encode_text_string_t)(CborEncoder *encoder, + const char *string, + size_t length) OC_NONNULL(); + +typedef CborError (*oc_rep_encode_byte_string_t)(CborEncoder *encoder, + const uint8_t *string, + size_t length) OC_NONNULL(); + +typedef CborError (*oc_rep_encoder_create_array_t)(CborEncoder *encoder, + CborEncoder *arrayEncoder, + size_t length) OC_NONNULL(); + +typedef CborError (*oc_rep_encoder_create_map_t)(CborEncoder *encoder, + CborEncoder *mapEncoder, + size_t length) OC_NONNULL(); +typedef CborError (*oc_rep_encoder_close_container_t)( + CborEncoder *encoder, const CborEncoder *containerEncoder) OC_NONNULL(); + +typedef struct oc_rep_encoder_t +{ + oc_rep_encoder_type_t type; + + oc_rep_encode_null_t encode_null; + oc_rep_encode_boolean_t encode_boolean; + oc_rep_encode_int_t encode_int; + oc_rep_encode_uint_t encode_uint; + oc_rep_encode_floating_point_t encode_floating_point; + oc_rep_encode_double_t encode_double; + oc_rep_encode_text_string_t encode_text_string; + oc_rep_encode_byte_string_t encode_byte_string; + oc_rep_encoder_create_array_t create_array; + oc_rep_encoder_create_map_t create_map; + oc_rep_encoder_close_container_t close_container; +} oc_rep_encoder_t; + +/** Return an initialized CBOR encoder. */ +oc_rep_encoder_t oc_rep_cbor_encoder(void); + /** - * @brief Initialize global cbor encoder with buffer. + * @brief Initialize global encoder buffer. * - * @note the encoder doesn't store the pointer to the buffer directly, instead - * it stores an offset from the global buffer to allow reallocation. + * @note the pointer to the buffer directly isn't stored directly, instead + * an offset is stored to allow reallocation. * * @param buffer buffer used by the global encoder (cannot be NULL) * @param size size of the buffer */ -void oc_rep_encoder_init(uint8_t *buffer, size_t size); +void oc_rep_buffer_init(uint8_t *buffer, size_t size); /** - * @brief Initialize global cbor encoder with buffer and enable buffer - * reallocation. + * @brief Initialize global encoder buffer and enable buffer reallocation. * * If the buffer is too small then the buffer will be enlarged using the realloc * syscall. The size of the buffer cannot exceed the maximal allowed size. * - * @note the encoder doesn't store the pointer to the buffer directly, instead - * it stores an offset from the global buffer to allow reallocation. + * @note the pointer to the buffer directly isn't stored directly, instead + * an offset is stored to allow reallocation. * * @param buffer pointer buffer used by the global encoder (cannot be NULL) * @param size size of the buffer * @param max_size maximal allowed size of the buffer */ -void oc_rep_encoder_realloc_init(uint8_t **buffer, size_t size, - size_t max_size); +void oc_rep_buffer_realloc_init(uint8_t **buffer, size_t size, size_t max_size); /** * @brief Recalcute the pointer to the buffer and the pointer to the end of the @@ -67,6 +128,18 @@ CborEncoder *oc_rep_encoder_convert_ptr_to_offset(CborEncoder *encoder); */ CborEncoder *oc_rep_encoder_convert_offset_to_ptr(CborEncoder *encoder); +/** + * @brief Set the encoder type to encode the response payload according to the + * accept option. + * + * @param accept the accept option + * @return true if the encoder type is set successfully + */ +bool oc_rep_encoder_set_type_by_accept(oc_content_format_t accept); + +/** Get content format of the global encoder */ +oc_content_format_t oc_rep_encoder_get_content_format(void); + #ifdef __cplusplus } #endif diff --git a/api/oc_rep_encode_json.c b/api/oc_rep_encode_json.c new file mode 100644 index 0000000000..7078399452 --- /dev/null +++ b/api/oc_rep_encode_json.c @@ -0,0 +1,339 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "oc_rep_encode_json_internal.h" +#include "util/oc_macros_internal.h" + +#include +#include +#include + +typedef enum json_types_t { + KeyType = 1 << 0, + ValueType = 1 << 1, + ArrayType = 1 << 2, + MapType = 1 << 3, +} json_types_t; + +static bool +rep_json_would_overflow(CborEncoder *encoder, size_t len) +{ + ptrdiff_t remaining = (ptrdiff_t)encoder->end; + remaining -= + remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; + remaining -= (ptrdiff_t)len; + return remaining < 0; +} + +static void +rep_json_advance_ptr(CborEncoder *encoder, size_t n) +{ + if (encoder->end != NULL) { + encoder->data.ptr += n; + } else { + encoder->data.bytes_needed += (ptrdiff_t)n; + } +} + +static CborError +rep_json_append_to_buffer(CborEncoder *encoder, const void *data, size_t len) +{ + if (rep_json_would_overflow(encoder, len)) { + if (encoder->end != NULL) { + len -= encoder->end - encoder->data.ptr; + encoder->end = NULL; + encoder->data.bytes_needed = 0; + } + + rep_json_advance_ptr(encoder, len); + return CborErrorOutOfMemory; + } + + memcpy(encoder->data.ptr, data, len); + encoder->data.ptr += len; + return CborNoError; +} + +static CborError +rep_json_append_separator(CborEncoder *encoder) +{ + if ((encoder->flags & MapType) != 0) { + if ((encoder->flags & KeyType) != 0) { + return rep_json_append_to_buffer(encoder, ":", 1); + } + if ((encoder->flags & ValueType) != 0) { + return rep_json_append_to_buffer(encoder, ",", 1); + } + return CborNoError; + } + if (encoder->flags == ArrayType) { + return CborNoError; + } + if ((encoder->flags & ArrayType) != 0) { + return rep_json_append_to_buffer(encoder, ",", 1); + } + return CborNoError; +} + +static CborError +rep_json_encode_null(CborEncoder *encoder) +{ + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + CborError err = rep_json_append_separator(encoder); +#define JSON_NULL "null" + err |= + rep_json_append_to_buffer(encoder, JSON_NULL, OC_CHAR_ARRAY_LEN(JSON_NULL)); +#undef JSON_NULL + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } + return err; +} + +static CborError +rep_json_encode_boolean(CborEncoder *encoder, bool value) +{ + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + CborError err = rep_json_append_separator(encoder); +#define JSON_TRUE "true" +#define JSON_FALSE "false" + if (value) { + err |= rep_json_append_to_buffer(encoder, JSON_TRUE, + OC_CHAR_ARRAY_LEN(JSON_TRUE)); + } else { + err |= rep_json_append_to_buffer(encoder, JSON_FALSE, + OC_CHAR_ARRAY_LEN(JSON_FALSE)); + } +#undef JSON_TRUE +#undef JSON_FALSE + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } + return err; +} + +static CborError +rep_json_encode_int(CborEncoder *encoder, int64_t value) +{ + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + if ((value > OC_REP_JSON_INT_MAX) || (value < OC_REP_JSON_INT_MIN)) { + return CborErrorDataTooLarge; + } + char buf[32] = { 0 }; + int len = snprintf(buf, sizeof(buf), "%" PRId64, value); + if (len < 0 || (size_t)len >= sizeof(buf)) { + return CborErrorUnexpectedEOF; + } + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, buf, len); + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } + return err; +} + +static CborError +rep_json_encode_uint(CborEncoder *encoder, uint64_t value) +{ + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + if (value > OC_REP_JSON_UINT_MAX) { + return CborErrorDataTooLarge; + } + char buf[32] = { 0 }; + int len = snprintf(buf, sizeof(buf), "%" PRIu64, value); + if (len < 0 || (size_t)len >= sizeof(buf)) { + return CborErrorUnexpectedEOF; + } + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, buf, len); + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } + return err; +} + +static CborError +rep_json_encode_byte_string(CborEncoder *encoder, const uint8_t *string, + size_t length) +{ + // TODO: implement, encode as base64 + (void)encoder; + (void)string; + (void)length; + return CborErrorUnsupportedType; +} + +static CborError +rep_json_encode_text_string(CborEncoder *encoder, const char *string, + size_t length) +{ + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, "\"", 1); + err |= rep_json_append_to_buffer(encoder, string, length); + err |= rep_json_append_to_buffer(encoder, "\"", 1); + if (err != CborNoError) { + return err; + } + if (encoder->flags & MapType) { + if (encoder->flags & KeyType) { + // "key" -> value was encoded + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } else if (encoder->flags & ValueType) { + // "value" -> key was encoded + encoder->flags &= ~ValueType; + encoder->flags |= KeyType; + } else { + // empty map -> key was encoded + encoder->flags |= KeyType; + } + } + if (encoder->flags & ArrayType) { + encoder->flags |= ValueType; + } + return err; +} + +static CborError +rep_json_encode_floating_point(CborEncoder *encoder, CborType fpType, + const void *value) +{ + // TODO: implement + (void)encoder; + (void)fpType; + (void)value; + return CborErrorUnsupportedType; +} + +static CborError +rep_json_encode_double(CborEncoder *encoder, double value) +{ + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + char buf[320] = { 0 }; + int len = snprintf(buf, sizeof(buf), "%f", value); + if (len < 0 || (size_t)len >= sizeof(buf)) { + return CborErrorUnexpectedEOF; + } + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, buf, len); + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + } + return err; +} + +static void +rep_json_prepare_container(CborEncoder *encoder, CborEncoder *container, + json_types_t json_type) +{ + container->data.ptr = encoder->data.ptr; + container->end = encoder->end; + container->flags = json_type; +} + +static CborError +rep_json_encoder_create_array(CborEncoder *encoder, CborEncoder *container, + size_t length) +{ + (void)length; + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, "[", 1); + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + rep_json_prepare_container(encoder, container, ArrayType); + } else if (err == CborErrorOutOfMemory) { + memcpy(container, encoder, sizeof(CborEncoder)); + } + return err; +} + +static CborError +rep_json_encoder_create_map(CborEncoder *encoder, CborEncoder *container, + size_t length) +{ + (void)length; + if (((encoder->flags & KeyType) == 0) && (encoder->flags & MapType) != 0) { + return CborErrorImproperValue; + } + CborError err = rep_json_append_separator(encoder); + err |= rep_json_append_to_buffer(encoder, "{", 1); + if (err == CborNoError) { + encoder->flags &= ~KeyType; + encoder->flags |= ValueType; + rep_json_prepare_container(encoder, container, MapType); + } else if (err == CborErrorOutOfMemory) { + memcpy(container, encoder, sizeof(CborEncoder)); + } + return err; +} + +static CborError +rep_json_encoder_close_container(CborEncoder *encoder, + const CborEncoder *container) +{ + // synchronise buffer state with that of the container + encoder->end = container->end; + encoder->data = container->data; + const char *break_byte = (container->flags & MapType) != 0 ? "}" : "]"; + return rep_json_append_to_buffer(encoder, break_byte, 1); +} + +oc_rep_encoder_t +oc_rep_json_encoder(void) +{ + return (oc_rep_encoder_t){ + .type = OC_REP_JSON_ENCODER, + + .encode_null = &rep_json_encode_null, + .encode_boolean = &rep_json_encode_boolean, + .encode_int = &rep_json_encode_int, + .encode_uint = &rep_json_encode_uint, + .encode_byte_string = &rep_json_encode_byte_string, + .encode_text_string = &rep_json_encode_text_string, + .encode_floating_point = &rep_json_encode_floating_point, + .encode_double = &rep_json_encode_double, + .create_array = &rep_json_encoder_create_array, + .create_map = &rep_json_encoder_create_map, + .close_container = &rep_json_encoder_close_container, + }; +} + +#endif /* OC_JSON_ENCODER */ diff --git a/api/oc_rep_encode_json_internal.h b/api/oc_rep_encode_json_internal.h new file mode 100644 index 0000000000..0efe74066f --- /dev/null +++ b/api/oc_rep_encode_json_internal.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#ifndef OC_REP_ENCODE_JSON_INTERNAL_H +#define OC_REP_ENCODE_JSON_INTERNAL_H + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "api/oc_rep_encode_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define OC_REP_JSON_INT_MAX (1LL << 53) +#define OC_REP_JSON_INT_MIN ~(1LL << 52) +#define OC_REP_JSON_UINT_MAX (1ULL << 53) + +/** Return an initialized JSON encoder. */ +oc_rep_encoder_t oc_rep_json_encoder(void); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_JSON_ENCODER */ + +#endif /* OC_REP_ENCODE_JSON_INTERNAL_H */ diff --git a/api/oc_rep_internal.h b/api/oc_rep_internal.h index ddcf77e231..edfb0516d4 100644 --- a/api/oc_rep_internal.h +++ b/api/oc_rep_internal.h @@ -29,6 +29,13 @@ extern "C" { #endif +typedef enum oc_rep_error_t { + OC_REP_NO_ERROR = 0, + + OC_REP_ERROR_INTERNAL = -1, + OC_REP_ERROR_OUT_OF_MEMORY = -2, +} oc_rep_error_t; + /** * @brief Check whether property matches by name. * @@ -66,7 +73,7 @@ bool oc_rep_is_property_with_type(const oc_rep_t *rep, * * @see oc_process_baseline_interface */ -bool oc_rep_is_baseline_interface_property(const oc_rep_t *rep); +bool oc_rep_is_baseline_interface_property(const oc_rep_t *rep) OC_NONNULL(); #ifdef __cplusplus } diff --git a/api/oc_ri.c b/api/oc_ri.c index dd9452f8d8..9290e592ad 100644 --- a/api/oc_ri.c +++ b/api/oc_ri.c @@ -20,6 +20,8 @@ #include "api/oc_events_internal.h" #include "api/oc_message_buffer_internal.h" #include "api/oc_network_events_internal.h" +#include "api/oc_rep_encode_internal.h" +#include "api/oc_rep_decode_internal.h" #include "messaging/coap/coap_internal.h" #include "messaging/coap/coap_options.h" #include "messaging/coap/constants.h" @@ -840,8 +842,8 @@ static ocf_version_t ri_get_ocf_version_from_header(const coap_packet_t *request) { #ifdef OC_SPEC_VER_OIC - uint16_t accept = 0; - if (coap_options_get_accept(request, &accept) == 1) { + oc_content_format_t accept = APPLICATION_NOT_DEFINED; + if (coap_options_get_accept(request, &accept)) { if (accept == APPLICATION_CBOR) { return OIC_VER_1_1_0; } @@ -1236,6 +1238,7 @@ oc_ri_invoke_coap_entity_handler(coap_make_response_ctx_t *ctx, */ oc_response_buffer_t response_buffer; memset(&response_buffer, 0, sizeof(response_buffer)); + response_buffer.content_format = APPLICATION_NOT_DEFINED; oc_response_t response_obj; memset(&response_obj, 0, sizeof(response_obj)); @@ -1260,7 +1263,7 @@ oc_ri_invoke_coap_entity_handler(coap_make_response_ctx_t *ctx, coap_options_get_content_format(ctx->request, &cf); /* Read the accept CoAP option in the request */ - uint16_t accept = 0; + oc_content_format_t accept = APPLICATION_NOT_DEFINED; coap_options_get_accept(ctx->request, &accept); /* Initialize OCF interface selector. */ @@ -1312,8 +1315,7 @@ oc_ri_invoke_coap_entity_handler(coap_make_response_ctx_t *ctx, bool bad_request = false; bool entity_too_large = false; - if (payload_len > 0 && - (cf == APPLICATION_CBOR || cf == APPLICATION_VND_OCF_CBOR)) { + if (payload_len > 0 && oc_rep_decoder_set_type_by_content_format(cf)) { /* Attempt to parse request payload using tinyCBOR via oc_rep helper * functions. The result of this parse is a tree of oc_rep_t structures * which will reflect the schema of the payload. @@ -1519,6 +1521,7 @@ oc_ri_invoke_coap_entity_handler(coap_make_response_ctx_t *ctx, #else /* OC_DYNAMIC_ALLOCATION */ oc_rep_new_v1(response_buffer.buffer, response_buffer.buffer_size); #endif /* !OC_DYNAMIC_ALLOCATION */ + oc_rep_encoder_set_type_by_accept(accept); oc_status_t ret = ri_invoke_request_handler( cur_resource, method, &request_obj, iface_mask, resource_is_collection); diff --git a/api/oc_server_api.c b/api/oc_server_api.c index 64d2907202..df021111ed 100644 --- a/api/oc_server_api.c +++ b/api/oc_server_api.c @@ -155,7 +155,14 @@ oc_send_response_with_callback(oc_request_t *request, oc_status_t response_code, if (!request) { return; } + + // if no accept header is present, use APPLICATION_VND_OCF_CBOR oc_content_format_t content_format = APPLICATION_VND_OCF_CBOR; + if (request->response->response_buffer->content_format == + APPLICATION_NOT_DEFINED && + request->accept != APPLICATION_NOT_DEFINED) { + content_format = request->accept; + } #ifdef OC_SPEC_VER_OIC if (request->origin && request->origin->version == OIC_VER_1_1_0) { content_format = APPLICATION_CBOR; diff --git a/api/unittest/discoverytest.cpp b/api/unittest/discoverytest.cpp index 4ef02c1c96..546862b44e 100644 --- a/api/unittest/discoverytest.cpp +++ b/api/unittest/discoverytest.cpp @@ -762,32 +762,6 @@ TEST_F(TestDiscoveryWithServer, GetRequestBaseline) #ifdef OC_RES_BATCH_SUPPORT -static void -verifyBatchPayloadResource(const DiscoveryBatchData &dbd, - const oc_resource_t *resource) -{ - ASSERT_NE(nullptr, resource); - const auto &it = dbd.find(std::string(oc_string(resource->uri))); - ASSERT_NE(std::end(dbd), it) - << "resource: " << oc_string(resource->uri) << " not found"; -#ifdef OC_HAS_FEATURE_ETAG - oc_coap_etag_t etag{}; - std::copy(it->second.etag.begin(), it->second.etag.end(), etag.value); - etag.length = static_cast(it->second.etag.size()); - TestDiscoveryWithServer::assertResourceETag(etag, resource); -#endif /* OC_HAS_FEATURE_ETAG */ -} - -static void -verifyBatchPayload(const DiscoveryBatchData &dbd, - const std::vector &expected) -{ - ASSERT_EQ(expected.size(), dbd.size()); - for (const auto *resource : expected) { - verifyBatchPayloadResource(dbd, resource); - } -} - struct batch_resources_t { const oc_endpoint_t *endpoint; @@ -814,6 +788,34 @@ getBatchResources(const oc_endpoint_t *endpoint) return batch; } +#ifndef OC_SECURITY + +static void +verifyBatchPayloadResource(const DiscoveryBatchData &dbd, + const oc_resource_t *resource) +{ + ASSERT_NE(nullptr, resource); + const auto &it = dbd.find(std::string(oc_string(resource->uri))); + ASSERT_NE(std::end(dbd), it) + << "resource: " << oc_string(resource->uri) << " not found"; +#ifdef OC_HAS_FEATURE_ETAG + oc_coap_etag_t etag{}; + std::copy(it->second.etag.begin(), it->second.etag.end(), etag.value); + etag.length = static_cast(it->second.etag.size()); + TestDiscoveryWithServer::assertResourceETag(etag, resource); +#endif /* OC_HAS_FEATURE_ETAG */ +} + +static void +verifyBatchPayload(const DiscoveryBatchData &dbd, + const std::vector &expected) +{ + ASSERT_EQ(expected.size(), dbd.size()); + for (const auto *resource : expected) { + verifyBatchPayloadResource(dbd, resource); + } +} + static void verifyBatchPayload(const DiscoveryBatchData &dbd, const oc_endpoint_t *endpoint) { @@ -821,6 +823,8 @@ verifyBatchPayload(const DiscoveryBatchData &dbd, const oc_endpoint_t *endpoint) verifyBatchPayload(dbd, br.resources); } +#endif /* !OC_SECURITY */ + static DiscoveryBatchData parseBatchPayload(const oc_rep_t *payload) { @@ -1285,31 +1289,6 @@ TEST_F(TestDiscoveryWithServer, GetRequestBatchIncremental_AllAscending) #ifdef OC_DISCOVERY_RESOURCE_OBSERVABLE -namespace { - -void -updateResourceByPost(std::string_view uri, const oc_endpoint_t *endpoint, - const std::function &payloadFn) -{ - auto post_handler = [](oc_client_response_t *data) { - oc::TestDevice::Terminate(); - EXPECT_EQ(OC_STATUS_CHANGED, data->code); - OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); - *static_cast(data->user_data) = true; - }; - - bool invoked = false; - ASSERT_TRUE(oc_init_post(uri.data(), endpoint, nullptr, post_handler, LOW_QOS, - &invoked)); - payloadFn(); - auto timeout = 1s; - ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); - oc::TestDevice::PoolEventsMsV1(timeout, true); - ASSERT_TRUE(invoked); -} - -} // namespace - // observe with default (LL) interface TEST_F(TestDiscoveryWithServer, Observe) { @@ -1545,6 +1524,27 @@ onBatchObserve(oc_client_response_t *cr) #endif /* OC_HAS_FEATURE_ETAG */ } +static void +updateResourceByPost(std::string_view uri, const oc_endpoint_t *endpoint, + const std::function &payloadFn) +{ + auto post_handler = [](oc_client_response_t *data) { + oc::TestDevice::Terminate(); + EXPECT_EQ(OC_STATUS_CHANGED, data->code); + OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); + *static_cast(data->user_data) = true; + }; + + bool invoked = false; + ASSERT_TRUE(oc_init_post(uri.data(), endpoint, nullptr, post_handler, LOW_QOS, + &invoked)); + payloadFn(); + auto timeout = 1s; + ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); + oc::TestDevice::PoolEventsMsV1(timeout, true); + ASSERT_TRUE(invoked); +} + TEST_F(TestDiscoveryWithServer, ObserveBatchWithResourceUpdate) { auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); diff --git a/api/unittest/repdecodejsontest.cpp b/api/unittest/repdecodejsontest.cpp new file mode 100644 index 0000000000..71b2e37650 --- /dev/null +++ b/api/unittest/repdecodejsontest.cpp @@ -0,0 +1,440 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "api/oc_rep_decode_json_internal.h" +#include "api/oc_rep_encode_json_internal.h" +#include "oc_helpers.h" +#include "oc_rep.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/RepPool.h" +#include "tests/gtest/Utility.h" + +#include +#include + +class TestRepDecodeJson : public testing::Test { +public: + void SetUp() override + { + oc_rep_set_pool(&rep_objects_); +#ifndef OC_DYNAMIC_ALLOCATION + memset(rep_objects_alloc_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); + memset(rep_objects_pool_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(oc_rep_t)); +#endif /* !OC_DYNAMIC_ALLOCATION */ + } + +private: +#ifdef OC_DYNAMIC_ALLOCATION + oc_memb rep_objects_{ sizeof(oc_rep_t), 0, nullptr, nullptr, nullptr }; +#else /* !OC_DYNAMIC_ALLOCATION */ + char rep_objects_alloc_[OC_MAX_NUM_REP_OBJECTS]; + oc_rep_t rep_objects_pool_[OC_MAX_NUM_REP_OBJECTS]; + oc_memb rep_objects_{ sizeof(oc_rep_t), OC_MAX_NUM_REP_OBJECTS, + rep_objects_alloc_, (void *)rep_objects_pool_, + nullptr }; +#endif /* OC_DYNAMIC_ALLOCATION */ +}; + +static int +parseJsonToRep(const std::string &json, oc_rep_t **rep) +{ + auto jsonObj = + oc::GetVector(std::string("{\"json\": ") + json + "}", true); + return oc_rep_parse_json(jsonObj.data(), jsonObj.size(), rep); +} + +static oc::oc_rep_unique_ptr +parseJson(const std::string &json) +{ + oc_rep_t *rep = nullptr; + EXPECT_EQ(CborNoError, parseJsonToRep(json, &rep)); + return oc::oc_rep_unique_ptr(rep, &oc_free_rep); +} + +TEST_F(TestRepDecodeJson, DecodeNull) +{ + auto jsonRep = parseJson("null"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_NIL, jsonRep->type); +} + +TEST_F(TestRepDecodeJson, DecodeBoolean) +{ + auto jsonRep = parseJson("true"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_BOOL, jsonRep->type); + EXPECT_EQ(true, jsonRep->value.boolean); + + jsonRep = parseJson("false"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_BOOL, jsonRep->type); + EXPECT_EQ(false, jsonRep->value.boolean); +} + +TEST_F(TestRepDecodeJson, Decode_InvalidPrimitive) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep("", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("n", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("nil", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("NULL", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("nnull", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("nulll", &rep)); + + ASSERT_NE(CborNoError, parseJsonToRep("t", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("TRUE", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("ttrue", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("truee", &rep)); + + ASSERT_NE(CborNoError, parseJsonToRep("f", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("FALSE", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("ffalse", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("falsee", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeString) +{ + auto jsonRep = parseJson(R"("Hello World")"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_STRING, jsonRep->type); + EXPECT_STREQ("Hello World", oc_string(jsonRep->value.string)); +} + +// empty array is parsed as null +TEST_F(TestRepDecodeJson, DecodeEmptyArray) +{ + auto jsonRep = parseJson("[]"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_NIL, jsonRep->type); +} + +// arrays of nulls are not supported and are parsed as null +TEST_F(TestRepDecodeJson, DecodeNullArray) +{ + auto jsonRep = parseJson("[null, null, null]"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_NIL, jsonRep->type); +} + +TEST_F(TestRepDecodeJson, DecodeNullArray_InvalidValues) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep("[null, true]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[null, false]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[null, 123]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[null, \"string\"]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[null, {}]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[null, []]", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeBoolArray) +{ + auto jsonRep = parseJson("[true, true, false, true]"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_BOOL_ARRAY, jsonRep->type); + ASSERT_EQ(4, oc_bool_array_size(jsonRep->value.array)); + EXPECT_TRUE(oc_bool_array(jsonRep->value.array)[0]); + EXPECT_TRUE(oc_bool_array(jsonRep->value.array)[1]); + EXPECT_FALSE(oc_bool_array(jsonRep->value.array)[2]); + EXPECT_TRUE(oc_bool_array(jsonRep->value.array)[3]); +} + +TEST_F(TestRepDecodeJson, DecodeBoolArray_InvalidValues) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep("[t]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, TRUE]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, truy]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[TRUE, true]", &rep)); + + ASSERT_NE(CborNoError, parseJsonToRep("[f]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[false, FALSE]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[false, falsy]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[FALSE, false]", &rep)); + + ASSERT_NE(CborNoError, parseJsonToRep("[true, null]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, 123]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, \"string\"]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, {}]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[true, []]", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeIntArray) +{ + auto jsonRep = parseJson("[0, 42, -1337]"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_INT_ARRAY, jsonRep->type); + ASSERT_EQ(3, oc_int_array_size(jsonRep->value.array)); + EXPECT_EQ(0, oc_int_array(jsonRep->value.array)[0]); + EXPECT_EQ(42, oc_int_array(jsonRep->value.array)[1]); + EXPECT_EQ(-1337, oc_int_array(jsonRep->value.array)[2]); +} + +TEST_F(TestRepDecodeJson, DecodeIntArray_InvalidValues) +{ + oc_rep_t *rep = nullptr; + std::string intTooLarge = std::to_string(INT64_MAX) + "0"; + ASSERT_NE(CborNoError, parseJsonToRep("[" + intTooLarge + "]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, " + intTooLarge + "]", &rep)); + + std::string intTooSmall = std::to_string(INT64_MIN) + "0"; + ASSERT_NE(CborNoError, parseJsonToRep("[" + intTooSmall + "]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, " + intTooSmall + "]", &rep)); + + ASSERT_NE(CborNoError, parseJsonToRep("[1, null]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, true]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, \"string\"]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, {}]", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep("[1, []]", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeStringArray) +{ + auto jsonRep = parseJson(R"(["This", "is", "a", "test"])"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_STRING_ARRAY, jsonRep->type); + ASSERT_EQ(4, oc_string_array_get_allocated_size(jsonRep->value.array)); + EXPECT_STREQ("This", oc_string_array_get_item(jsonRep->value.array, 0)); + EXPECT_STREQ("is", oc_string_array_get_item(jsonRep->value.array, 1)); + EXPECT_STREQ("a", oc_string_array_get_item(jsonRep->value.array, 2)); + EXPECT_STREQ("test", oc_string_array_get_item(jsonRep->value.array, 3)); +} + +TEST_F(TestRepDecodeJson, DecodeStringArray_Truncate) +{ + // STRING_ARRAY_ITEM_MAX_LEN is the maximum length of a string array item + // without null terminator + auto tooLong = std::string(STRING_ARRAY_ITEM_MAX_LEN, 'a'); + auto jsonRep = parseJson("[\"" + tooLong + "\"]"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_STRING_ARRAY, jsonRep->type); + ASSERT_EQ(1, oc_string_array_get_allocated_size(jsonRep->value.array)); + EXPECT_STREQ(std::string(STRING_ARRAY_ITEM_MAX_LEN - 1, 'a').c_str(), + oc_string_array_get_item(jsonRep->value.array, 0)); +} + +TEST_F(TestRepDecodeJson, DecodeStringArray_InvalidValues) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep(R"(["str", null])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"(["str", true])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"(["str", 1])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"(["str", []])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"(["str", {}])", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeArray_InvalidValues) +{ + // arrays of arrays are not supported + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep(R"([[]])", &rep)); + ASSERT_NE(CborNoError, + parseJsonToRep(R"([[true, false], [true, false]])", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeEmptyObject) +{ + auto jsonRep = parseJson("{}"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT, jsonRep->type); +} + +TEST_F(TestRepDecodeJson, DecodeEmptyKeyObject) +{ + auto jsonRep = parseJson(R"({"": null})"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT, jsonRep->type); +} + +TEST_F(TestRepDecodeJson, DecodeObjectWithPrimitiveValues) +{ + auto jsonRep = parseJson( + R"({"null": null, "bool": true, "int": 42, "string": "Hello World"})"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT, jsonRep->type); + + bool is_null = false; + ASSERT_TRUE(oc_rep_is_null(jsonRep->value.object, "null", &is_null)); + EXPECT_TRUE(is_null); + + bool value = false; + ASSERT_TRUE(oc_rep_get_bool(jsonRep->value.object, "bool", &value)); + EXPECT_TRUE(value); + + int64_t i = 0; + ASSERT_TRUE(oc_rep_get_int(jsonRep->value.object, "int", &i)); + EXPECT_EQ(42, i); + + char *s = nullptr; + size_t size = 0; + ASSERT_TRUE(oc_rep_get_string(jsonRep->value.object, "string", &s, &size)); + ASSERT_EQ(std::string("Hello World").length(), size); + EXPECT_STREQ("Hello World", s); +} + +TEST_F(TestRepDecodeJson, DecodeObjectWithArrays) +{ + auto jsonRep = parseJson( + R"({"intArray": [1, 42, 1337], "boolArray": [false,true,false,false], )" + R"("stringArray": ["This", "is", "a", "test"]})"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT, jsonRep->type); + + int64_t *intArray = nullptr; + size_t size = 0; + ASSERT_TRUE( + oc_rep_get_int_array(jsonRep->value.object, "intArray", &intArray, &size)); + ASSERT_EQ(3, size); + EXPECT_EQ(1, intArray[0]); + EXPECT_EQ(42, intArray[1]); + EXPECT_EQ(1337, intArray[2]); + + bool *boolArray = nullptr; + size = 0; + ASSERT_TRUE(oc_rep_get_bool_array(jsonRep->value.object, "boolArray", + &boolArray, &size)); + ASSERT_EQ(4, size); + EXPECT_FALSE(boolArray[0]); + EXPECT_TRUE(boolArray[1]); + EXPECT_FALSE(boolArray[2]); + EXPECT_FALSE(boolArray[3]); + + oc_string_array_t str_array{}; + size = 0; + ASSERT_TRUE(oc_rep_get_string_array(jsonRep->value.object, "stringArray", + &str_array, &size)); + EXPECT_STREQ("This", oc_string_array_get_item(str_array, 0)); + EXPECT_STREQ("is", oc_string_array_get_item(str_array, 1)); + EXPECT_STREQ("a", oc_string_array_get_item(str_array, 2)); + EXPECT_STREQ("test", oc_string_array_get_item(str_array, 3)); +} + +TEST_F(TestRepDecodeJson, DecodeObjectWithObjects) +{ + auto jsonRep = parseJson( + R"({"empty": {}, "first_layer": {"second_layer": {"first_value": 13, "second_value": ["Hello", "World"]}}})"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT, jsonRep->type); + + oc_rep_t *empty = nullptr; + ASSERT_TRUE(oc_rep_get_object(jsonRep->value.object, "empty", &empty)); + ASSERT_EQ(nullptr, empty); + + oc_rep_t *first_layer = nullptr; + ASSERT_TRUE( + oc_rep_get_object(jsonRep->value.object, "first_layer", &first_layer)); + ASSERT_NE(nullptr, first_layer); + ASSERT_EQ(OC_REP_OBJECT, first_layer->type); + + int64_t i = 0; + ASSERT_TRUE(oc_rep_get_int(first_layer->value.object, "first_value", &i)); + EXPECT_EQ(13, i); + + oc_string_array_t str_array{}; + size_t size = 0; + ASSERT_TRUE(oc_rep_get_string_array(first_layer->value.object, "second_value", + &str_array, &size)); + ASSERT_EQ(2, size); + EXPECT_STREQ("Hello", oc_string_array_get_item(str_array, 0)); + EXPECT_STREQ("World", oc_string_array_get_item(str_array, 1)); +} + +TEST_F(TestRepDecodeJson, DecodeObject_InvalidKey) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep(R"({null: null})", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"({true: false})", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"({1: 2})", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"({[]: []})", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"({{}: {}})", &rep)); +} + +TEST_F(TestRepDecodeJson, DecodeObjectArray) +{ + auto jsonRep = parseJson( + R"([{"bool": false}, {"int": 1337}, {"stringArray": ["This", "is", "a", "test"]}])"); + ASSERT_NE(nullptr, jsonRep.get()); + ASSERT_EQ(OC_REP_OBJECT_ARRAY, jsonRep->type); + + oc_rep_t *boolObject = nullptr; + oc_rep_t *intObject = nullptr; + oc_rep_t *stringArrayObject = nullptr; + for (oc_rep_t *objects = jsonRep->value.object_array; objects != nullptr; + objects = objects->next) { + oc_rep_t *obj = objects->value.object; + if (std::string(oc_string(obj->name)) == "bool") { + boolObject = obj; + continue; + } + if (std::string(oc_string(obj->name)) == "int") { + intObject = obj; + continue; + } + if (std::string(oc_string(obj->name)) == "stringArray") { + stringArrayObject = obj; + continue; + } + ASSERT_FALSE(true) << "Unexpected object: " << oc_string(obj->name); + } + ASSERT_NE(nullptr, boolObject); + ASSERT_NE(nullptr, intObject); + ASSERT_NE(nullptr, stringArrayObject); + + bool value{}; + ASSERT_TRUE(oc_rep_get_bool(boolObject, "bool", &value)); + EXPECT_FALSE(value); + + int64_t i = 0; + ASSERT_TRUE(oc_rep_get_int(intObject, "int", &i)); + EXPECT_EQ(1337, i); + + oc_string_array_t str_array{}; + size_t size = 0; + ASSERT_TRUE(oc_rep_get_string_array(stringArrayObject, "stringArray", + &str_array, &size)); + ASSERT_EQ(4, size); + EXPECT_STREQ("This", oc_string_array_get_item(str_array, 0)); + EXPECT_STREQ("is", oc_string_array_get_item(str_array, 1)); + EXPECT_STREQ("a", oc_string_array_get_item(str_array, 2)); + EXPECT_STREQ("test", oc_string_array_get_item(str_array, 3)); +} + +TEST_F(TestRepDecodeJson, DecodeObjectArray_InvalidValues) +{ + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, parseJsonToRep(R"([{}, null])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"([{}, true])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"([{}, 1])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"([{}, "str"])", &rep)); + ASSERT_NE(CborNoError, parseJsonToRep(R"([{}, []])", &rep)); +} + +TEST_F(TestRepDecodeJson, Decode_InvalidJson) +{ + oc_rep_t *rep = nullptr; + std::string json = R"({"json":: )"; + auto jsonObj = oc::GetVector(json, true); + ASSERT_NE(CborNoError, + oc_rep_parse_json(jsonObj.data(), jsonObj.size(), &rep)); +} + +#endif /* OC_JSON_ENCODER */ diff --git a/api/unittest/repdecodetest.cpp b/api/unittest/repdecodetest.cpp new file mode 100644 index 0000000000..24c3ea8792 --- /dev/null +++ b/api/unittest/repdecodetest.cpp @@ -0,0 +1,60 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************/ + +#include "api/oc_rep_decode_internal.h" + +#include + +class TestRepDecode : public testing::Test {}; + +TEST_F(TestRepDecode, SetTypeByContentFormat) +{ + auto is_accepted_format = [](oc_content_format_t cf) { + return cf == APPLICATION_CBOR || cf == APPLICATION_VND_OCF_CBOR +#ifdef OC_JSON_ENCODER + || cf == APPLICATION_JSON || cf == APPLICATION_TD_JSON +#endif /* OC_JSON_ENCODER */ + ; + }; + + for (int i = 0; i < APPLICATION_NOT_DEFINED; ++i) { + auto cf = static_cast(i); + if (is_accepted_format(cf)) { + EXPECT_TRUE(oc_rep_decoder_set_type_by_content_format(cf)); + oc_rep_decoder_type_t exp_type = OC_REP_CBOR_DECODER; +#ifdef OC_JSON_ENCODER + if (cf == APPLICATION_JSON || cf == APPLICATION_TD_JSON) { + exp_type = OC_REP_JSON_DECODER; + } +#endif /* OC_JSON_ENCODER */ + EXPECT_EQ(exp_type, oc_rep_decoder_get_type()); + continue; + } + EXPECT_FALSE(oc_rep_decoder_set_type_by_content_format(cf)) + << "unexpected valid decoder for cf: " << cf; + } + + EXPECT_TRUE( + oc_rep_decoder_set_type_by_content_format(APPLICATION_NOT_DEFINED)); + EXPECT_EQ(OC_REP_CBOR_DECODER, oc_rep_decoder_get_type()); +} + +TEST_F(TestRepDecode, ParseFail) +{ + EXPECT_EQ(-1, oc_parse_rep(nullptr, 0, nullptr)); +} diff --git a/api/unittest/repencodejsontest.cpp b/api/unittest/repencodejsontest.cpp new file mode 100644 index 0000000000..ad194e3060 --- /dev/null +++ b/api/unittest/repencodejsontest.cpp @@ -0,0 +1,471 @@ +/****************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "api/oc_rep_encode_internal.h" +#include "api/oc_rep_encode_json_internal.h" +#include "api/oc_rep_decode_internal.h" +#include "oc_rep.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/RepPool.h" + +#include +#include +#include +#include +#include + +static const oc_rep_encoder_type_t g_rep_default_encoder = + oc_rep_encoder_get_type(); +static const oc_rep_decoder_type_t g_rep_default_decoder = + oc_rep_decoder_get_type(); + +class TestJsonRepEncodeWithRealloc : public testing::Test { +public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_JSON_ENCODER); + oc_rep_decoder_set_type(OC_REP_JSON_DECODER); + } + + static void TearDownTestCase() + { + oc_rep_encoder_set_type(g_rep_default_encoder); + oc_rep_decoder_set_type(g_rep_default_decoder); + } + + void TearDown() override + { +#ifdef OC_DYNAMIC_ALLOCATION + free(buffer_); +#endif /* OC_DYNAMIC_ALLOCATION */ + } + + /* buffer for oc_rep_t */ + void SetRepBuffer(size_t size = 1024, size_t max_size = 1024) + { +#ifdef OC_DYNAMIC_ALLOCATION + if (buffer_ != nullptr) { + free(buffer_); + } + buffer_ = nullptr; + if (size > 0) { + buffer_ = static_cast(malloc(size)); + } + oc_rep_new_realloc_v1(&buffer_, size, max_size); +#else /* OC_DYNAMIC_ALLOCATION */ + (void)size; + buffer_.resize(max_size); + oc_rep_new_v1(buffer_.data(), buffer_.size()); + memset(rep_objects_alloc_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); + memset(rep_objects_pool_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(oc_rep_t)); +#endif /* OC_DYNAMIC_ALLOCATION */ + } + + void Shrink() + { +#ifdef OC_DYNAMIC_ALLOCATION + buffer_ = oc_rep_shrink_encoder_buf(buffer_); +#endif /* OC_DYNAMIC_ALLOCATION */ + } + + oc::oc_rep_unique_ptr ParsePayload() + { + const uint8_t *payload = oc_rep_get_encoder_buf(); + int payload_len = oc_rep_get_encoded_payload_size(); + EXPECT_NE(payload_len, -1); + oc_rep_set_pool(&rep_objects_); + oc_rep_t *rep = nullptr; + EXPECT_EQ(CborNoError, oc_parse_rep(payload, payload_len, &rep)); + return oc::oc_rep_unique_ptr(rep, &oc_free_rep); + } + +#ifdef OC_DYNAMIC_ALLOCATION + uint8_t *buffer_{ nullptr }; + oc_memb rep_objects_{ sizeof(oc_rep_t), 0, nullptr, nullptr, nullptr }; +#else /* !OC_DYNAMIC_ALLOCATION */ + std::vector buffer_{}; + char rep_objects_alloc_[OC_MAX_NUM_REP_OBJECTS]; + oc_rep_t rep_objects_pool_[OC_MAX_NUM_REP_OBJECTS]; + oc_memb rep_objects_{ sizeof(oc_rep_t), OC_MAX_NUM_REP_OBJECTS, + rep_objects_alloc_, (void *)rep_objects_pool_, + nullptr }; +#endif /* OC_DYNAMIC_ALLOCATION */ +}; + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodedPayloadRealloc) +{ + SetRepBuffer(1, 1024); + + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(root, hello, "world"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_double(root, double, 3.14); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(root, bool, true); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, int, -1); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_uint(root, uint, OC_REP_JSON_UINT_MAX); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); +#if 0 + std::vector byte_string = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + oc_rep_set_byte_string(root, byte_string_key, byte_string.data(), + byte_string.size()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); +#endif + std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; + oc_rep_set_key(oc_rep_object(root), "fibonacci"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_begin_array(oc_rep_object(root), fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + for (const auto &val : fib) { + oc_rep_add_int(fibonacci, val); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + } + oc_rep_end_array(oc_rep_object(root), fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::vector math_constants = { 3.14159, 2.71828, 1.414121, 1.61803 }; + oc_rep_set_double_array(root, math_constants, math_constants.data(), + math_constants.size()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + auto rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); + size_t payload_size = oc_rep_get_encoded_payload_size(); + EXPECT_EQ(176, payload_size); +#ifdef OC_DYNAMIC_ALLOCATION + EXPECT_GT(oc_rep_get_encoder_buffer_size(), payload_size); +#endif /* OC_DYNAMIC_ALLOCATION */ + Shrink(); +#ifdef OC_DYNAMIC_ALLOCATION + EXPECT_EQ(oc_rep_get_encoder_buffer_size(), payload_size); +#endif /* OC_DYNAMIC_ALLOCATION */ +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeRaw) +{ + std::vector in{ '\0' }; + SetRepBuffer(0, 0); + oc_rep_encode_raw(in.data(), in.size()); + EXPECT_EQ(CborErrorInternalError, oc_rep_get_cbor_errno()); + + SetRepBuffer(1, 1); + oc_rep_encode_raw(in.data(), in.size()); + EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_encode_raw(in.data(), in.size()); + EXPECT_EQ(CborErrorOutOfMemory, oc_rep_get_cbor_errno()); + + SetRepBuffer(1, 8); + for (size_t i = 0; i < 8; ++i) { + oc_rep_encode_raw(in.data(), in.size()); + EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + } + EXPECT_EQ(8, oc_rep_get_encoded_payload_size()); + + oc_rep_encode_raw(in.data(), in.size()); + EXPECT_EQ(CborErrorOutOfMemory, oc_rep_get_cbor_errno()); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeNull) +{ + /* null */ + constexpr size_t json_null_size = 4; + SetRepBuffer(1, json_null_size); + ASSERT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); + EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_null(oc_rep_get_encoder())); + + SetRepBuffer(1, 8 * json_null_size); + for (size_t i = 0; i < 8; ++i) { + ASSERT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); + } + EXPECT_EQ(8 * json_null_size, oc_rep_get_encoded_payload_size()); + EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_null(oc_rep_get_encoder())); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeNull_InvalidNullMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // null cannot be used as a map key + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_null(&map)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeBool) +{ + /* true, false */ + SetRepBuffer(1, 5); + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(oc_rep_get_encoder(), false)); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_boolean(oc_rep_get_encoder(), true)); + + // 4 * true + 4 * false = 36 + SetRepBuffer(1, 4 * 4 + 4 * 5); + for (size_t i = 0; i < 8; ++i) { + ASSERT_EQ(CborNoError, + oc_rep_encode_boolean(oc_rep_get_encoder(), i % 2 == 0)); + } + ASSERT_EQ(4 * 4 + 4 * 5, oc_rep_get_encoded_payload_size()); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_boolean(oc_rep_get_encoder(), false)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeBool_InvalidBoolMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // bool cannot be used as a map key + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_boolean(&map, true)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeInt) +{ + SetRepBuffer(1, 1); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_int(oc_rep_get_encoder(), OC_REP_JSON_INT_MAX)); + + /* 2^52 (9007199254740992) -> 16 digits */ + SetRepBuffer(1, 16); + ASSERT_EQ(CborNoError, + oc_rep_encode_int(oc_rep_get_encoder(), OC_REP_JSON_INT_MAX)); + ASSERT_EQ(16, oc_rep_get_encoded_payload_size()); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_int(oc_rep_get_encoder(), OC_REP_JSON_INT_MAX)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeInt_InvalidIntMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // int cannot be used as a map key + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_int(&map, 1)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeInt_InvalidIntValue) +{ + SetRepBuffer(); + EXPECT_EQ(CborErrorDataTooLarge, + oc_rep_encode_int(oc_rep_get_encoder(), OC_REP_JSON_INT_MAX + 1)); + EXPECT_EQ(CborErrorDataTooLarge, + oc_rep_encode_int(oc_rep_get_encoder(), OC_REP_JSON_INT_MIN - 1)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeUint) +{ + SetRepBuffer(1, 1); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_uint(oc_rep_get_encoder(), OC_REP_JSON_UINT_MAX)); + + /* 2^52 (9007199254740992) -> 16 digits */ + SetRepBuffer(1, 16); + ASSERT_EQ(CborNoError, + oc_rep_encode_uint(oc_rep_get_encoder(), OC_REP_JSON_UINT_MAX)); + ASSERT_EQ(16, oc_rep_get_encoded_payload_size()); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_uint(oc_rep_get_encoder(), OC_REP_JSON_UINT_MAX)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeUint_InvalidUintMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // uint cannot be used as a map key + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_uint(&map, 1)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeUint_InvalidUintValue) +{ + SetRepBuffer(); + EXPECT_EQ(CborErrorDataTooLarge, + oc_rep_encode_uint(oc_rep_get_encoder(), OC_REP_JSON_UINT_MAX + 1)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeFloat_UnsupportedType) +{ + float val = 0; + SetRepBuffer(); + EXPECT_EQ( + CborErrorUnsupportedType, + oc_rep_encode_floating_point(oc_rep_get_encoder(), CborFloatType, &val)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeDouble) +{ + SetRepBuffer(1, 1); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_double(oc_rep_get_encoder(), + std::numeric_limits::max())); + + /* 1.79769e+308 */ + SetRepBuffer(1, 316); + ASSERT_EQ(CborNoError, + oc_rep_encode_double(oc_rep_get_encoder(), + std::numeric_limits::max())); + ASSERT_EQ(316, oc_rep_get_encoded_payload_size()); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encode_double(oc_rep_get_encoder(), + std::numeric_limits::max())); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeDouble_InvalidDoubleMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // double cannot be used as a map key + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_double(&map, 0.0)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeTextString) +{ + SetRepBuffer(1, 1); + std::string str = "test"; + EXPECT_EQ( + CborErrorOutOfMemory, + oc_rep_encode_text_string(oc_rep_get_encoder(), str.c_str(), str.length())); + + str = "this is 16 chars"; + // "\"this is 16 chars\"" + SetRepBuffer(1, 18); + ASSERT_EQ(CborNoError, oc_rep_encode_text_string(oc_rep_get_encoder(), + str.c_str(), str.length())); + ASSERT_EQ(18, oc_rep_get_encoded_payload_size()); + + // no additional char should fit + str = "c"; + EXPECT_EQ( + CborErrorOutOfMemory, + oc_rep_encode_text_string(oc_rep_get_encoder(), str.c_str(), str.length())); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeByteString_UnsupportedType) +{ + std::vector bstr = { 0x42, 0x0, 0x42, 0x0, 0x42, 0x0, 0x42, 0x42, + 0x0, 0x42, 0x0, 0x42, 0x0, 0x42, 0x42, 0x0 }; + SetRepBuffer(); + EXPECT_EQ( + CborErrorUnsupportedType, + oc_rep_encode_byte_string(oc_rep_get_encoder(), bstr.data(), bstr.size())); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeArray) +{ + SetRepBuffer(1, 1); + CborEncoder array{}; + CborEncoder inner_array{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( + oc_rep_get_encoder(), &array, CborIndefiniteLength)); + EXPECT_EQ( + CborErrorOutOfMemory, + oc_rep_encoder_create_array(&array, &inner_array, CborIndefiniteLength)); + + SetRepBuffer(1, 1); + array = {}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( + oc_rep_get_encoder(), &array, CborIndefiniteLength)); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encoder_close_container(oc_rep_get_encoder(), &array)); + + // [true] + SetRepBuffer(1, 6); + array = {}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( + oc_rep_get_encoder(), &array, CborIndefiniteLength)); + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(&array, true)); + ASSERT_EQ(CborNoError, + oc_rep_encoder_close_container(oc_rep_get_encoder(), &array)); + ASSERT_EQ(6, oc_rep_get_encoded_payload_size()); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeArray_InvalidArrayMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // array cannot be used as a map key + CborEncoder array{}; + EXPECT_EQ(CborErrorImproperValue, + oc_rep_encoder_create_array(&map, &array, CborIndefiniteLength)); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeMap) +{ + std::string key = "key"; + SetRepBuffer(1, 7); + CborEncoder map{}; + CborEncoder inner_map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + ASSERT_EQ(CborNoError, + oc_rep_encode_text_string(&map, key.c_str(), key.length())); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encoder_create_map(&map, &inner_map, CborIndefiniteLength)); + + SetRepBuffer(1, 1); + map = {}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + EXPECT_EQ(CborErrorOutOfMemory, + oc_rep_encoder_close_container(oc_rep_get_encoder(), &map)); + + SetRepBuffer(1, 12); + map = {}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + ASSERT_EQ(CborNoError, + oc_rep_encode_text_string(&map, key.c_str(), key.length())); + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(&map, true)); + ASSERT_EQ(CborNoError, + oc_rep_encoder_close_container(oc_rep_get_encoder(), &map)); + ASSERT_EQ(12, oc_rep_get_encoded_payload_size()); + + auto rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); +} + +TEST_F(TestJsonRepEncodeWithRealloc, OCRepEncodeMap_InvalidObjectMapKey) +{ + SetRepBuffer(); + CborEncoder map{}; + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + CborIndefiniteLength)); + // array cannot be used as a map key + CborEncoder inner_map{}; + EXPECT_EQ(CborErrorImproperValue, + oc_rep_encoder_create_map(&map, &inner_map, CborIndefiniteLength)); +} + +#endif /* OC_JSON_ENCODER */ diff --git a/api/unittest/repencodetest.cpp b/api/unittest/repencodetest.cpp index 1f06e4350b..92a5179337 100644 --- a/api/unittest/repencodetest.cpp +++ b/api/unittest/repencodetest.cpp @@ -1,6 +1,6 @@ /****************************************************************** * - * Copyright 2018 Samsung Electronics All Rights Reserved. + * Copyright (c) 2023 plgd.dev s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"), * you may not use this file except in compliance with the License. @@ -16,7 +16,11 @@ * ******************************************************************/ +#include "api/oc_rep_encode_internal.h" +#include "api/oc_rep_decode_internal.h" #include "oc_rep.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/RepPool.h" #include #include @@ -24,8 +28,25 @@ #include #include +static const oc_rep_encoder_type_t g_rep_default_encoder = + oc_rep_encoder_get_type(); +static const oc_rep_decoder_type_t g_rep_default_decoder = + oc_rep_decoder_get_type(); + class TestRepEncodeWithRealloc : public testing::Test { -protected: +public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_CBOR_ENCODER); + oc_rep_decoder_set_type(OC_REP_CBOR_DECODER); + } + + static void TearDownTestCase() + { + oc_rep_encoder_set_type(g_rep_default_encoder); + oc_rep_decoder_set_type(g_rep_default_decoder); + } + void TearDown() override { #ifdef OC_DYNAMIC_ALLOCATION @@ -33,9 +54,8 @@ class TestRepEncodeWithRealloc : public testing::Test { #endif /* OC_DYNAMIC_ALLOCATION */ } -public: /* buffer for oc_rep_t */ - void SetRepBuffer(size_t size, size_t max_size) + void SetRepBuffer(size_t size = 1024, size_t max_size = 1024) { #ifdef OC_DYNAMIC_ALLOCATION if (buffer_ != nullptr) { @@ -48,8 +68,10 @@ class TestRepEncodeWithRealloc : public testing::Test { oc_rep_new_realloc_v1(&buffer_, size, max_size); #else /* OC_DYNAMIC_ALLOCATION */ (void)size; - buffer_.reserve(max_size); - oc_rep_new_v1(buffer_.data(), buffer_.capacity()); + buffer_.resize(max_size); + oc_rep_new_v1(buffer_.data(), buffer_.size()); + memset(rep_objects_alloc_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); + memset(rep_objects_pool_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(oc_rep_t)); #endif /* OC_DYNAMIC_ALLOCATION */ } @@ -60,10 +82,27 @@ class TestRepEncodeWithRealloc : public testing::Test { #endif /* OC_DYNAMIC_ALLOCATION */ } + oc::oc_rep_unique_ptr ParsePayload() + { + const uint8_t *payload = oc_rep_get_encoder_buf(); + int payload_len = oc_rep_get_encoded_payload_size(); + EXPECT_NE(payload_len, -1); + oc_rep_set_pool(&rep_objects_); + oc_rep_t *rep = nullptr; + EXPECT_EQ(CborNoError, oc_parse_rep(payload, payload_len, &rep)); + return oc::oc_rep_unique_ptr(rep, &oc_free_rep); + } + #ifdef OC_DYNAMIC_ALLOCATION uint8_t *buffer_{ nullptr }; + oc_memb rep_objects_{ sizeof(oc_rep_t), 0, nullptr, nullptr, nullptr }; #else /* !OC_DYNAMIC_ALLOCATION */ std::vector buffer_{}; + char rep_objects_alloc_[OC_MAX_NUM_REP_OBJECTS]; + oc_rep_t rep_objects_pool_[OC_MAX_NUM_REP_OBJECTS]; + oc_memb rep_objects_{ sizeof(oc_rep_t), OC_MAX_NUM_REP_OBJECTS, + rep_objects_alloc_, (void *)rep_objects_pool_, + nullptr }; #endif /* OC_DYNAMIC_ALLOCATION */ }; @@ -72,41 +111,44 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodedPayloadRealloc) SetRepBuffer(1, 1024); oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_text_string(root, "hello", "world"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_double(root, "double", 3.14); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_boolean(root, "bool", true); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_int(root, "int", -1); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_uint(root, "uint", -1); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(root, hello, "world"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_double(root, double, 3.14); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(root, bool, true); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, int, -1); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_uint(root, uint, -1); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector byte_string = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; oc_rep_set_byte_string(root, byte_string_key, byte_string.data(), byte_string.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; oc_rep_set_key(oc_rep_object(root), "fibonacci"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_begin_array(oc_rep_object(root), fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); for (const auto &val : fib) { oc_rep_add_int(fibonacci, val); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); } oc_rep_end_array(oc_rep_object(root), fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector math_constants = { 3.14159, 2.71828, 1.414121, 1.61803 }; oc_rep_set_double_array(root, math_constants, math_constants.data(), math_constants.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + auto rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); size_t payload_size = oc_rep_get_encoded_payload_size(); - EXPECT_EQ(166, payload_size); + EXPECT_EQ(156, payload_size); #ifdef OC_DYNAMIC_ALLOCATION EXPECT_GT(oc_rep_get_encoder_buffer_size(), payload_size); #endif /* OC_DYNAMIC_ALLOCATION */ @@ -143,30 +185,30 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeRaw) TEST_F(TestRepEncodeWithRealloc, OCRepEncodeNull) { SetRepBuffer(1, 1); - EXPECT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); + ASSERT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_null(oc_rep_get_encoder())); SetRepBuffer(1, 8); for (size_t i = 0; i < 8; ++i) { - EXPECT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); + ASSERT_EQ(CborNoError, oc_rep_encode_null(oc_rep_get_encoder())); } - EXPECT_EQ(8, oc_rep_get_encoded_payload_size()); + ASSERT_EQ(8, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_null(oc_rep_get_encoder())); } TEST_F(TestRepEncodeWithRealloc, OCRepEncodeBool) { SetRepBuffer(1, 1); - EXPECT_EQ(CborNoError, oc_rep_encode_boolean(oc_rep_get_encoder(), false)); + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(oc_rep_get_encoder(), false)); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_boolean(oc_rep_get_encoder(), true)); SetRepBuffer(1, 8); for (size_t i = 0; i < 8; ++i) { - EXPECT_EQ(CborNoError, + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(oc_rep_get_encoder(), i % 2 == 0)); } - EXPECT_EQ(8, oc_rep_get_encoded_payload_size()); + ASSERT_EQ(8, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_boolean(oc_rep_get_encoder(), false)); } @@ -177,8 +219,9 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeInt) EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_int(oc_rep_get_encoder(), INT64_MAX)); - SetRepBuffer(1, 15); - EXPECT_EQ(CborNoError, oc_rep_encode_int(oc_rep_get_encoder(), INT64_MAX)); + SetRepBuffer(1, 9); + ASSERT_EQ(CborNoError, oc_rep_encode_int(oc_rep_get_encoder(), INT64_MAX)); + ASSERT_EQ(9, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_int(oc_rep_get_encoder(), INT64_MAX)); } @@ -189,8 +232,9 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeUint) EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_uint(oc_rep_get_encoder(), UINT64_MAX)); - SetRepBuffer(1, 15); - EXPECT_EQ(CborNoError, oc_rep_encode_uint(oc_rep_get_encoder(), UINT64_MAX)); + SetRepBuffer(1, 9); + ASSERT_EQ(CborNoError, oc_rep_encode_uint(oc_rep_get_encoder(), UINT64_MAX)); + ASSERT_EQ(9, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_uint(oc_rep_get_encoder(), UINT64_MAX)); } @@ -202,10 +246,11 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeFloat) EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_floating_point( oc_rep_get_encoder(), CborFloatType, &val)); - SetRepBuffer(1, 7); + SetRepBuffer(1, 5); val = std::numeric_limits::max(); - EXPECT_EQ(CborNoError, oc_rep_encode_floating_point(oc_rep_get_encoder(), + ASSERT_EQ(CborNoError, oc_rep_encode_floating_point(oc_rep_get_encoder(), CborFloatType, &val)); + ASSERT_EQ(5, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_floating_point( oc_rep_get_encoder(), CborFloatType, &val)); } @@ -217,10 +262,11 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeDouble) oc_rep_encode_double(oc_rep_get_encoder(), std::numeric_limits::max())); - SetRepBuffer(1, 15); - EXPECT_EQ(CborNoError, + SetRepBuffer(1, 9); + ASSERT_EQ(CborNoError, oc_rep_encode_double(oc_rep_get_encoder(), std::numeric_limits::max())); + ASSERT_EQ(9, oc_rep_get_encoded_payload_size()); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encode_double(oc_rep_get_encoder(), std::numeric_limits::max())); @@ -234,10 +280,16 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeTextString) CborErrorOutOfMemory, oc_rep_encode_text_string(oc_rep_get_encoder(), str.c_str(), str.length())); - SetRepBuffer(1, 20); + SetRepBuffer(1, 17); str = "this is 16 chars"; - EXPECT_EQ(CborNoError, oc_rep_encode_text_string(oc_rep_get_encoder(), + ASSERT_EQ(CborNoError, oc_rep_encode_text_string(oc_rep_get_encoder(), str.c_str(), str.length())); + ASSERT_EQ(17, oc_rep_get_encoded_payload_size()); + + str = "c"; + EXPECT_EQ( + CborErrorOutOfMemory, + oc_rep_encode_text_string(oc_rep_get_encoder(), str.c_str(), str.length())); } TEST_F(TestRepEncodeWithRealloc, OCRepEncodeByteString) @@ -259,7 +311,7 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeArray) SetRepBuffer(1, 1); CborEncoder array{}; CborEncoder inner_array{}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_array( + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( oc_rep_get_encoder(), &array, CborIndefiniteLength)); EXPECT_EQ( CborErrorOutOfMemory, @@ -267,18 +319,19 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeArray) SetRepBuffer(1, 1); array = {}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_array( + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( oc_rep_get_encoder(), &array, CborIndefiniteLength)); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encoder_close_container(oc_rep_get_encoder(), &array)); - SetRepBuffer(1, 20); + SetRepBuffer(1, 3); array = {}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_array( + ASSERT_EQ(CborNoError, oc_rep_encoder_create_array( oc_rep_get_encoder(), &array, CborIndefiniteLength)); - EXPECT_EQ(CborNoError, oc_rep_encode_boolean(&array, true)); - EXPECT_EQ(CborNoError, + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(&array, true)); + ASSERT_EQ(CborNoError, oc_rep_encoder_close_container(oc_rep_get_encoder(), &array)); + ASSERT_EQ(3, oc_rep_get_encoded_payload_size()); } TEST_F(TestRepEncodeWithRealloc, OCRepEncodeMap) @@ -286,23 +339,31 @@ TEST_F(TestRepEncodeWithRealloc, OCRepEncodeMap) SetRepBuffer(1, 1); CborEncoder map{}; CborEncoder inner_map{}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, CborIndefiniteLength)); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encoder_create_map(&map, &inner_map, CborIndefiniteLength)); SetRepBuffer(1, 1); map = {}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, CborIndefiniteLength)); EXPECT_EQ(CborErrorOutOfMemory, oc_rep_encoder_close_container(oc_rep_get_encoder(), &map)); - SetRepBuffer(1, 20); + std::string key = "key"; + SetRepBuffer(1, 7); map = {}; - EXPECT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, + ASSERT_EQ(CborNoError, oc_rep_encoder_create_map(oc_rep_get_encoder(), &map, CborIndefiniteLength)); - EXPECT_EQ(CborNoError, oc_rep_encode_boolean(&map, true)); - EXPECT_EQ(CborNoError, + ASSERT_EQ(CborNoError, + oc_rep_encode_text_string(&map, key.c_str(), key.length())); + ASSERT_EQ(CborNoError, oc_rep_encode_boolean(&map, true)); + ASSERT_EQ(CborNoError, oc_rep_encoder_close_container(oc_rep_get_encoder(), &map)); + ASSERT_EQ(7, oc_rep_get_encoded_payload_size()); + + auto rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); } diff --git a/api/unittest/repjsontest.cpp b/api/unittest/repjsontest.cpp new file mode 100644 index 0000000000..b4abbb362d --- /dev/null +++ b/api/unittest/repjsontest.cpp @@ -0,0 +1,1257 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "api/oc_con_resource_internal.h" +#include "api/oc_rep_decode_internal.h" +#include "api/oc_rep_encode_internal.h" +#include "api/oc_rep_encode_json_internal.h" +#include "oc_rep.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/RepPool.h" +#include "tests/gtest/Resource.h" +#include "tests/gtest/Utility.h" +#include "util/oc_memb.h" +#include "util/oc_secure_string_internal.h" + +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +static constexpr size_t kDeviceID{ 0 }; +static const oc_rep_encoder_type_t g_rep_default_encoder = + oc_rep_encoder_get_type(); +static const oc_rep_decoder_type_t g_rep_default_decoder = + oc_rep_decoder_get_type(); + +class TestJsonRepWithPool : public testing::Test { +public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_JSON_ENCODER); + oc_rep_decoder_set_type(OC_REP_JSON_DECODER); + } + + static void TearDownTestCase() + { + oc_rep_encoder_set_type(g_rep_default_encoder); + oc_rep_decoder_set_type(g_rep_default_decoder); + } + + void SetUp() override { ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); } + + oc_memb *GetRepObjectsPool() { return pool_.GetRepObjectsPool(); } + + oc::oc_rep_unique_ptr ParsePayload() { return pool_.ParsePayload(); } + + static void CheckJson(const oc_rep_t *rep, const std::string &expected, + bool pretty_print) + { + oc::RepPool::CheckJson(rep, expected, pretty_print); + } + +private: + oc::RepPool pool_{}; +}; + +TEST_F(TestJsonRepWithPool, OCRepInvalidFormat) +{ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + EXPECT_EQ(CborErrorImproperValue, oc_rep_encode_int(&root_map, 42)); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + auto invalid_json = oc::GetVector("{42}"); + /* convert JsonEncoder to oc_rep_t */ + oc_rep_set_pool(GetRepObjectsPool()); + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, + oc_parse_rep(invalid_json.data(), invalid_json.size(), &rep)); + ASSERT_EQ(nullptr, rep); +} + +TEST_F(TestJsonRepWithPool, OCRepInvalidArray) +{ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_key(oc_rep_object(root), "mixed"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_begin_array(oc_rep_object(root), mixed); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_int(mixed, 42); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_text_string(mixed, "1337"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_array(oc_rep_object(root), mixed); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + const uint8_t *payload = oc_rep_get_encoder_buf(); + ASSERT_NE(nullptr, payload); + int payload_len = oc_rep_get_encoded_payload_size(); + ASSERT_NE(payload_len, -1); + oc_rep_set_pool(GetRepObjectsPool()); + oc_rep_t *rep = nullptr; + ASSERT_NE(CborNoError, oc_parse_rep(payload, payload_len, &rep)); + ASSERT_EQ(nullptr, rep); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetNull) +{ + /* add null value to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_null(root, nothing); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + bool is_null = false; + EXPECT_TRUE(oc_rep_is_null(rep.get(), "nothing", &is_null)); + EXPECT_TRUE(is_null); + + /* error handling */ + EXPECT_FALSE(oc_rep_is_null(nullptr, "nothing", &is_null)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), nullptr, &is_null)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), "", &is_null)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), "", &is_null)); + EXPECT_FALSE(oc_rep_is_null( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &is_null)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), "nothing", nullptr)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), "not_the_key", &is_null)); + + CheckJson(rep.get(), "{\"nothing\":null}", false); + CheckJson(rep.get(), "{\n \"nothing\" : null\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetDouble) +{ + /* add double values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_double(root, pi, 3.14159); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + // TODO: implement parsing of double/float values +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetInt) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, ultimate_answer, 10000000000); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, negative, -1024); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, zero, 0); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, max_positive, OC_REP_JSON_INT_MAX); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, max_negative, OC_REP_JSON_INT_MIN); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + int64_t ultimate_answer_out = 0; + EXPECT_TRUE( + oc_rep_get_int(rep.get(), "ultimate_answer", &ultimate_answer_out)); + EXPECT_EQ(10000000000, ultimate_answer_out); + int64_t negative_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "negative", &negative_out)); + EXPECT_EQ(-1024, negative_out); + int64_t zero_out = -1; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "zero", &zero_out)); + EXPECT_EQ(0, zero_out); + int64_t max_positive_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "max_positive", &max_positive_out)); + EXPECT_EQ(OC_REP_JSON_INT_MAX, max_positive_out); + int64_t max_negative_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "max_negative", &max_negative_out)); + EXPECT_EQ(OC_REP_JSON_INT_MIN, max_negative_out); + + /* check error handling */ + EXPECT_FALSE(oc_rep_get_int(nullptr, "zero", &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), nullptr, &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "", &zero_out)); + EXPECT_FALSE(oc_rep_get_int( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "zero", nullptr)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "not_a_key", &zero_out)); + + std::string json = + R"({"ultimate_answer":10000000000,"negative":-1024,"zero":0,"max_positive":)" + + std::to_string(OC_REP_JSON_INT_MAX) + R"(,"max_negative":)" + + std::to_string(OC_REP_JSON_INT_MIN) + R"(})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"ultimate_answer\" : 10000000000,\n" + " \"negative\" : -1024,\n" + " \"zero\" : 0,\n" + " \"max_positive\" : " + + std::to_string(OC_REP_JSON_INT_MAX) + + ",\n" + " \"max_negative\" : " + + std::to_string(OC_REP_JSON_INT_MIN) + + "\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetUint) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_uint(root, ultimate_answer, 42); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + /* + * Assuming 32 bit int, which should be true for systems the gtest will + * be running on, the largest value for 32 bit int is 2,147,483,647 or + * 0x7FFFFFFF + */ + oc_rep_set_uint(root, larger_than_int, 3000000000); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_uint(root, zero, 0); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + EXPECT_EQ(OC_REP_INT, rep->type); + int64_t ultimate_answer_out = 0; + EXPECT_TRUE( + oc_rep_get_int(rep.get(), "ultimate_answer", &ultimate_answer_out)); + EXPECT_EQ(42u, (unsigned)ultimate_answer_out); + int64_t larger_than_int_out = 0; + EXPECT_TRUE( + oc_rep_get_int(rep.get(), "larger_than_int", &larger_than_int_out)); + EXPECT_EQ(3000000000u, (unsigned)larger_than_int_out); + int64_t zero_out = -1; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "zero", &zero_out)); + EXPECT_EQ(0u, (unsigned)zero_out); + + /* check error handling */ + EXPECT_FALSE(oc_rep_get_int(nullptr, "zero", &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), nullptr, &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "", &zero_out)); + EXPECT_FALSE(oc_rep_get_int( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "zero", nullptr)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "not_a_key", &zero_out)); + + std::string json = + R"({"ultimate_answer":42,"larger_than_int":3000000000,"zero":0})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"ultimate_answer\" : 42,\n" + " \"larger_than_int\" : 3000000000,\n" + " \"zero\" : 0\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetBool) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(root, true_flag, true); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(root, false_flag, false); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the ultimate_answer from the oc_rep_t */ + bool true_flag_out = false; + EXPECT_TRUE(oc_rep_get_bool(rep.get(), "true_flag", &true_flag_out)); + EXPECT_TRUE(true_flag_out); + bool false_flag_out = true; + EXPECT_TRUE(oc_rep_get_bool(rep.get(), "false_flag", &false_flag_out)); + EXPECT_FALSE(false_flag_out); + + /* check error handling */ + EXPECT_FALSE(oc_rep_get_bool(nullptr, "true_flag", &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool(rep.get(), nullptr, &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool(rep.get(), "", &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool(rep.get(), "true_flag", nullptr)); + EXPECT_FALSE(oc_rep_get_bool(rep.get(), "not_a_key", &true_flag_out)); + + std::string json = R"({"true_flag":true,"false_flag":false})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"true_flag\" : true,\n" + " \"false_flag\" : false\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetTextString) +{ + /* add text string value "hal9000":"Dave" to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(root, empty, ""); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(root, hal9000, "Dave"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + /* test utf8 character support "hello world" in russian */ + oc_rep_set_text_string(root, ru_character_set, "Привет, мир"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the hal9000 from the oc_rep_t */ + char *empty_out = nullptr; + size_t str_len; + EXPECT_TRUE(oc_rep_get_string(rep.get(), "empty", &empty_out, &str_len)); + EXPECT_STREQ("", empty_out); + char *hal9000_out = nullptr; + EXPECT_TRUE(oc_rep_get_string(rep.get(), "hal9000", &hal9000_out, &str_len)); + EXPECT_STREQ("Dave", hal9000_out); + EXPECT_EQ(4, str_len); + char *ru_character_set_out = nullptr; + EXPECT_TRUE(oc_rep_get_string(rep.get(), "ru_character_set", + &ru_character_set_out, &str_len)); + EXPECT_STREQ("Привет, мир", ru_character_set_out); + /* + * to encode Привет, мир takes more bytes than the number of characters so + * calculate the the number of bytes using the strlen function. + */ + EXPECT_EQ(strlen("Привет, мир"), str_len); + + /* check error handling */ + EXPECT_FALSE(oc_rep_get_string(nullptr, "hal9000", &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), nullptr, &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), "", &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), + std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), + &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), "hal9000", nullptr, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), "hal9000", &hal9000_out, nullptr)); + EXPECT_FALSE( + oc_rep_get_string(rep.get(), "not_a_key", &hal9000_out, &str_len)); + + std::string json = + R"({"empty":"","hal9000":"Dave","ru_character_set":"Привет, мир"})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"empty\" : \"\",\n" + " \"hal9000\" : \"Dave\",\n" + " \"ru_character_set\" : \"Привет, мир\"\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetEmptyIntArray) +{ + /* + { + "emptyInt": null, + } + */ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int_array(root, emptyInt, (int64_t *)nullptr, 0); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + CheckJson(rep.get(), "{\"emptyInt\":null}", false); + CheckJson(rep.get(), "{\n \"emptyInt\" : null\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetIntArray) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::vector fib = { + 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 10000000000 + }; + oc_rep_set_int_array(root, fibonacci, fib.data(), fib.size()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + int64_t *fib_out = nullptr; + size_t fib_len; + EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); + ASSERT_EQ(fib.size(), fib_len); + for (size_t i = 0; i < fib_len; ++i) { + EXPECT_EQ(fib[i], fib_out[i]); + } + + /* Error handling */ + EXPECT_FALSE(oc_rep_get_int_array(nullptr, "fibonacci", &fib_out, &fib_len)); + EXPECT_FALSE(oc_rep_get_int_array(rep.get(), nullptr, &fib_out, &fib_len)); + EXPECT_FALSE(oc_rep_get_int_array(rep.get(), "", &fib_out, &fib_len)); + EXPECT_FALSE(oc_rep_get_int_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &fib_out, + &fib_len)); + EXPECT_FALSE(oc_rep_get_int_array(rep.get(), "fibonacci", nullptr, &fib_len)); + EXPECT_FALSE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, nullptr)); + EXPECT_FALSE( + oc_rep_get_int_array(rep.get(), "not_a_key", &fib_out, &fib_len)); + + std::string json = "{\"fibonacci\":[1,1,2,3,5,8,13,21,34,55,89,10000000000]}"; + CheckJson(rep.get(), json, false); + std::string pretty_json = + "{\n" + " \"fibonacci\" : [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 10000000000]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepAddGetIntArray) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; + oc_rep_open_array(root, fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + for (const auto &v : fib) { + oc_rep_add_int(fibonacci, v); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + } + oc_rep_close_array(root, fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + int64_t *fib_out = nullptr; + size_t fib_len = 0; + EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); + ASSERT_EQ(fib.size(), fib_len); + for (size_t i = 0; i < fib_len; ++i) { + EXPECT_EQ(fib[i], fib_out[i]); + } + + std::string json = "{\"fibonacci\":[1,1,2,3,5,8,13,21,34,55,89]}"; + CheckJson(rep.get(), json, false); + std::string pretty_json = + "{\n" + " \"fibonacci\" : [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepAddGetIntArrayUsingSetKeyAndBeginArray) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; + oc_rep_set_key(oc_rep_object(root), "fibonacci"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_begin_array(oc_rep_object(root), fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + for (const auto &v : fib) { + oc_rep_add_int(fibonacci, v); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + } + oc_rep_end_array(oc_rep_object(root), fibonacci); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + int64_t *fib_out = nullptr; + size_t fib_len = 0; + EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); + ASSERT_EQ(fib.size(), fib_len); + for (size_t i = 0; i < fib_len; ++i) { + EXPECT_EQ(fib[i], fib_out[i]); + } + + std::string json = "{\"fibonacci\":[1,1,2,3,5,8,13,21,34,55,89]}"; + CheckJson(rep.get(), json, false); + std::string pretty_json = + "{\n" + " \"fibonacci\" : [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetEmptyBoolArray) +{ + /* + { + "emptyBool": null, + } + */ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_bool_array(root, emptyBool, (bool *)nullptr, 0); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + CheckJson(rep.get(), "{\"emptyBool\":null}", false); + CheckJson(rep.get(), "{\n \"emptyBool\" : null\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetBoolArray) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::array flip = { false, false, true, false, false }; + oc_rep_set_bool_array(root, flip, flip.data(), flip.size()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + bool *flip_out = nullptr; + size_t flip_len; + EXPECT_TRUE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, &flip_len)); + ASSERT_EQ(flip.size(), flip_len); + for (size_t i = 0; i < flip_len; ++i) { + EXPECT_EQ(flip[i], flip_out[i]); + } + + /* Error handling */ + EXPECT_FALSE(oc_rep_get_bool_array(nullptr, "flip", &flip_out, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), nullptr, &flip_out, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "", &flip_out, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &flip_out, + &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "flip", nullptr, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, nullptr)); + EXPECT_FALSE( + oc_rep_get_bool_array(rep.get(), "not_a_key", &flip_out, &flip_len)); + + std::string json = "{\"flip\":[false,false,true,false,false]}"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"flip\" : [false, false, true, false, false]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepAddGetBoolArray) +{ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + std::array flip = { false, false, true, false, false }; + oc_rep_open_array(root, flip); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + for (const auto &v : flip) { + oc_rep_add_boolean(flip, v); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + } + oc_rep_close_array(root, flip); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + bool *flip_out = nullptr; + size_t flip_len = 0; + EXPECT_TRUE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, &flip_len)); + ASSERT_EQ(flip.size(), flip_len); + for (size_t i = 0; i < flip_len; ++i) { + EXPECT_EQ(flip[i], flip_out[i]); + } + + std::string json = "{\"flip\":[false,false,true,false,false]}"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"flip\" : [false, false, true, false, false]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetEmptyObject) +{ + /* + { + "empty": {}, + } + */ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_open_object(root, empty); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_object(root, empty); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + CheckJson(rep.get(), "{\"empty\":{}}", false); + CheckJson(rep.get(), "{\n \"empty\" : {\n }\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetObject) +{ + /* + * { + * "my_object": { + * "a": 1 + * "b": false + * "c": "three" + * } + * } + */ + /* add values to root object */ + std::string c_value = "three"; + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_object(root, my_object); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(my_object, a, 1); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(my_object, b, false); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(my_object, c, c_value.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_object(root, my_object); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + oc_rep_t *my_object_out = nullptr; + EXPECT_TRUE(oc_rep_get_object(rep.get(), "my_object", &my_object_out)); + ASSERT_NE(nullptr, my_object_out); + int64_t a_out; + EXPECT_TRUE(oc_rep_get_int(my_object_out, "a", &a_out)); + EXPECT_EQ(1, a_out); + bool b_out = true; + EXPECT_TRUE(oc_rep_get_bool(my_object_out, "b", &b_out)); + EXPECT_FALSE(b_out); + char *c_out = nullptr; + size_t c_out_size = 0; + EXPECT_TRUE(oc_rep_get_string(my_object_out, "c", &c_out, &c_out_size)); + EXPECT_EQ(c_value.length(), c_out_size); + EXPECT_STREQ(c_value.c_str(), c_out); + + /* Error handling */ + EXPECT_FALSE(oc_rep_get_object(nullptr, "my_object", &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), nullptr, &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "", &my_object_out)); + EXPECT_FALSE(oc_rep_get_object( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "my_object", nullptr)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "not_a_key", &my_object_out)); + + std::string json = R"({"my_object":{"a":1,"b":false,"c":"three"}})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"my_object\" : {\n" + " \"a\" : 1,\n" + " \"b\" : false,\n" + " \"c\" : \"three\"\n" + " }\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetEmptyObjectArray) +{ + /* + { + "emptyObj": null, + } + */ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_open_array(root, emptyObj); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_array(root, emptyObj); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + CheckJson(rep.get(), "{\"emptyObj\":null}", false); + CheckJson(rep.get(), "{\n \"emptyObj\" : null\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetObjectArray) +{ + /* + * { + * "space_2001": [ + * {"name": "Dave Bowman", "job": "astronaut"}, + * {"name": "Frank Poole", "job": "astronaut"}, + * {"name": "Hal 9000", "job": "AI computer"} + * ] + */ + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_array(root, space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc_rep_object_array_start_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, name, "Dave Bowman"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, job, "astronaut"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_end_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc_rep_object_array_start_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, name, "Frank Poole"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, job, "astronaut"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_end_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc_rep_object_array_start_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, name, "Hal 9000"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(space_2001, job, "AI computer"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_end_item(space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc_rep_close_array(root, space_2001); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + /* calling this an object_array is a bit of a misnomer internally it is a + * linked list */ + oc_rep_t *space_2001_out = nullptr; + EXPECT_TRUE( + oc_rep_get_object_array(rep.get(), "space_2001", &space_2001_out)); + ASSERT_TRUE(space_2001_out != nullptr); + + char *name_out = nullptr; + size_t name_out_size = 0; + char *job_out = nullptr; + size_t job_out_size = 0; + EXPECT_EQ(OC_REP_OBJECT, space_2001_out->type); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "name", &name_out, + &name_out_size)); + EXPECT_EQ(strlen("Dave Bowman"), name_out_size); + EXPECT_STREQ("Dave Bowman", name_out); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "job", &job_out, + &job_out_size)); + EXPECT_EQ(strlen("astronaut"), job_out_size); + EXPECT_STREQ("astronaut", job_out); + + space_2001_out = space_2001_out->next; + ASSERT_TRUE(space_2001_out != nullptr); + EXPECT_EQ(OC_REP_OBJECT, space_2001_out->type); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "name", &name_out, + &name_out_size)); + EXPECT_EQ(strlen("Frank Poole"), name_out_size); + EXPECT_STREQ("Frank Poole", name_out); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "job", &job_out, + &job_out_size)); + EXPECT_EQ(strlen("astronaut"), job_out_size); + EXPECT_STREQ("astronaut", job_out); + + space_2001_out = space_2001_out->next; + ASSERT_TRUE(space_2001_out != nullptr); + EXPECT_EQ(OC_REP_OBJECT, space_2001_out->type); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "name", &name_out, + &name_out_size)); + EXPECT_EQ(strlen("Hal 9000"), name_out_size); + EXPECT_STREQ("Hal 9000", name_out); + EXPECT_TRUE(oc_rep_get_string(space_2001_out->value.object, "job", &job_out, + &job_out_size)); + EXPECT_EQ(strlen("AI computer"), job_out_size); + EXPECT_STREQ("AI computer", job_out); + + /* Error handling */ + EXPECT_FALSE(oc_rep_get_object_array(nullptr, "space_2001", &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), nullptr, &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), "", &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), + &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), "space_2001", nullptr)); + EXPECT_FALSE( + oc_rep_get_object_array(rep.get(), "not_a_key", &space_2001_out)); + + std::string json = + R"({"space_2001":[{"name":"Dave Bowman","job":"astronaut"},)" + R"({"name":"Frank Poole","job":"astronaut"},)" + R"({"name":"Hal 9000","job":"AI computer"}]})"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "{\n" + " \"space_2001\" : [\n" + " {\n" + " \"name\" : \"Dave Bowman\",\n" + " \"job\" : \"astronaut\"\n },\n" + " {\n" + " \"name\" : \"Frank Poole\",\n" + " \"job\" : \"astronaut\"\n" + " },\n" + " {\n" + " \"name\" : \"Hal 9000\",\n" + " \"job\" : \"AI computer\"\n" + " }]\n" + "}\n"; + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetEmptyStringArray) +{ + /* + { + "emptyStr": null, + } + */ + /* add values to root object */ + oc_rep_begin_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_string_array_t emptyStr{}; + oc_rep_set_string_array(root, emptyStr, emptyStr); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + CheckJson(rep.get(), "{\"emptyStr\":null}", false); + CheckJson(rep.get(), "{\n \"emptyStr\" : null\n}\n", true); +} + +TEST_F(TestJsonRepWithPool, OCRepSetGetStringArray) +{ + /* Strings for testing + Note: check STRING_ARRAY_ITEM_MAX_LEN for maximal allowed string item length + in a string array. + */ +#ifdef OC_DYNAMIC_ALLOCATION + std::string STR0 = + "Do not take life too seriously. You will never get out of it alive."; + std::string STR1 = "All generalizations are false, including this one."; + std::string STR2 = "Those who believe in telekinetics, raise my hand."; + std::string STR3 = + "I refuse to join any club that would have me as a member."; +#else /* !OC_DYNAMIC_ALLOCATION */ + std::string STR0 = "Do not take life too seriously."; + std::string STR1 = "All generalizations are false."; + std::string STR2 = "Raise my hand."; + std::string STR3 = "I refuse to join any club."; +#endif /* OC_DYNAMIC_ALLOCATION */ + + oc_string_array_t quotes; + oc_new_string_array("es, static_cast(4)); + EXPECT_TRUE(oc_string_array_add_item(quotes, STR0.c_str())); + EXPECT_TRUE(oc_string_array_add_item(quotes, STR1.c_str())); + EXPECT_TRUE(oc_string_array_add_item(quotes, STR2.c_str())); + EXPECT_TRUE(oc_string_array_add_item(quotes, STR3.c_str())); + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_string_array(root, quotes, quotes); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_free_string_array("es); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + oc_string_array_t quotes_out{}; + size_t quotes_len = 0; + EXPECT_TRUE( + oc_rep_get_string_array(rep.get(), "quotes", "es_out, "es_len)); + ASSERT_EQ(4, quotes_len); + + /* Error handling */ + EXPECT_FALSE( + oc_rep_get_string_array(nullptr, "quotes", "es_out, "es_len)); + EXPECT_FALSE( + oc_rep_get_string_array(rep.get(), nullptr, "es_out, "es_len)); + EXPECT_FALSE( + oc_rep_get_string_array(rep.get(), "", "es_out, "es_len)); + EXPECT_FALSE(oc_rep_get_string_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), "es_out, + "es_len)); + EXPECT_FALSE( + oc_rep_get_string_array(rep.get(), "quotes", nullptr, "es_len)); + EXPECT_FALSE( + oc_rep_get_string_array(rep.get(), "quotes", "es_out, nullptr)); + + EXPECT_EQ(STR0.length(), oc_string_array_get_item_size(quotes_out, 0)); + EXPECT_STREQ(STR0.c_str(), oc_string_array_get_item(quotes_out, 0)); + EXPECT_EQ(STR1.length(), oc_string_array_get_item_size(quotes_out, 1)); + EXPECT_STREQ(STR1.c_str(), oc_string_array_get_item(quotes_out, 1)); + EXPECT_EQ(STR2.length(), oc_string_array_get_item_size(quotes_out, 2)); + EXPECT_STREQ(STR2.c_str(), oc_string_array_get_item(quotes_out, 2)); + EXPECT_EQ(STR3.length(), oc_string_array_get_item_size(quotes_out, 3)); + EXPECT_STREQ(STR3.c_str(), oc_string_array_get_item(quotes_out, 3)); + + // clang-format off + std::string json = R"({"quotes":)" + R"([")" + STR0 + R"(",)" + R"(")" + STR1 + R"(",)" + R"(")" + STR2 + R"(",)" + R"(")" + STR3 + R"("]})"; + // clang-format on + CheckJson(rep.get(), json, false); + // clang-format off + std::string pretty_json = "{\n" + " \"quotes\" : [\n" + " \"" + STR0 + "\",\n" + " \"" + STR1 + "\",\n" + " \"" + STR2 + "\",\n" + " \"" + STR3 + "\"\n" + " ]\n" + "}\n"; + // clang-format on + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepAddGetStringArray) +{ + /* Strings for testing + Note: check STRING_ARRAY_ITEM_MAX_LEN for maximal allowed string item length + in a string array. + */ +#ifdef OC_DYNAMIC_ALLOCATION + std::string STR0 = + "Do not take life too seriously. You will never get out of it alive."; + std::string STR1 = "All generalizations are false, including this one."; + std::string STR2 = "Those who believe in telekinetics, raise my hand."; + std::string STR3 = + "I refuse to join any club that would have me as a member."; +#else /* !OC_DYNAMIC_ALLOCATION */ + std::string STR0 = "Do not take life too seriously."; + std::string STR1 = "All generalizations are false."; + std::string STR2 = "Raise my hand."; + std::string STR3 = "I refuse to join any club."; +#endif /* OC_DYNAMIC_ALLOCATION */ + + /* add values to root object */ + oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_open_array(root, quotes); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_text_string(quotes, STR0.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_text_string(quotes, STR1.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_text_string(quotes, STR2.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_add_text_string(quotes, STR3.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_array(root, quotes); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + oc_string_array_t quotes_out{}; + size_t quotes_len = 0; + EXPECT_TRUE( + oc_rep_get_string_array(rep.get(), "quotes", "es_out, "es_len)); + ASSERT_EQ(4, quotes_len); + + EXPECT_EQ(STR0.length(), oc_string_array_get_item_size(quotes_out, 0)); + EXPECT_STREQ(STR0.c_str(), oc_string_array_get_item(quotes_out, 0)); + EXPECT_EQ(STR1.length(), oc_string_array_get_item_size(quotes_out, 1)); + EXPECT_STREQ(STR1.c_str(), oc_string_array_get_item(quotes_out, 1)); + EXPECT_EQ(STR2.length(), oc_string_array_get_item_size(quotes_out, 2)); + EXPECT_STREQ(STR2.c_str(), oc_string_array_get_item(quotes_out, 2)); + EXPECT_EQ(STR3.length(), oc_string_array_get_item_size(quotes_out, 3)); + EXPECT_STREQ(STR3.c_str(), oc_string_array_get_item(quotes_out, 3)); + + // clang-format off + std::string json = R"({"quotes":)" + R"([")" + STR0 + R"(",)" + R"(")" + STR1 + R"(",)" + R"(")" + STR2 + R"(",)" + R"(")" + STR3 + R"("]})"; + // clang-format on + CheckJson(rep.get(), json, false); + // clang-format off + std::string pretty_json = "{\n" + " \"quotes\" : [\n" + " \"" + STR0 + "\",\n" + " \"" + STR1 + "\",\n" + " \"" + STR2 + "\",\n" + " \"" + STR3 + "\"\n" + " ]\n" + "}\n"; + // clang-format on + CheckJson(rep.get(), pretty_json, true); +} + +TEST_F(TestJsonRepWithPool, OCRepRootArrayObject) +{ + /* + * create root object array + * "[{"href":"/light/1","rep":{"state":true}},{"href":"/count/1","rep":{"count":100}}]" + */ + /* add values to root object */ + oc_rep_start_links_array(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_start_item(links); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(links, href, "/light/1"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_object(links, rep); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_boolean(rep, state, true); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_object(links, rep); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_end_item(links); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_start_item(links); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(links, href, "/count/1"); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_object(links, rep); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(rep, count, 100); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_close_object(links, rep); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_object_array_end_item(links); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_end_links_array(); + + /* convert CborEncoder to oc_rep_t */ + oc::oc_rep_unique_ptr rep = ParsePayload(); + ASSERT_NE(nullptr, rep.get()); + + /* read the values from the oc_rep_t */ + /* calling this an object_array is a bit of a misnomer internally it is a + * linked list */ + EXPECT_EQ(0, oc_string_len(rep->name)); + EXPECT_EQ(OC_REP_OBJECT, rep->type); + oc_rep_t *links = rep.get(); + ASSERT_TRUE(links != nullptr); + + char *href_out = nullptr; + size_t href_out_size = 0; + oc_rep_t *rep_out = nullptr; + EXPECT_TRUE( + oc_rep_get_string(links->value.object, "href", &href_out, &href_out_size)); + EXPECT_EQ(strlen("/light/1"), href_out_size); + EXPECT_STREQ("/light/1", href_out); + + EXPECT_TRUE(oc_rep_get_object(links->value.object, "rep", &rep_out)); + ASSERT_TRUE(rep_out != nullptr); + + EXPECT_EQ(OC_REP_BOOL, rep_out->type); + bool state_out = false; + EXPECT_TRUE(oc_rep_get_bool(rep_out, "state", &state_out)); + EXPECT_TRUE(state_out); + + links = links->next; + // "[{"href":"/light/1","rep":{"state":true}},{"href":"/count/1","rep":{"count":100}}]" + EXPECT_TRUE( + oc_rep_get_string(links->value.object, "href", &href_out, &href_out_size)); + EXPECT_EQ(strlen("/count/1"), href_out_size); + EXPECT_STREQ("/count/1", href_out); + + EXPECT_TRUE(oc_rep_get_object(links->value.object, "rep", &rep_out)); + ASSERT_TRUE(rep_out != nullptr); + + EXPECT_EQ(OC_REP_INT, rep_out->type); + int64_t count_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep_out, "count", &count_out)); + EXPECT_EQ(100, count_out); + + std::string json = R"([{"href":"/light/1","rep":{"state":true}},)" + R"({"href":"/count/1","rep":{"count":100}}])"; + CheckJson(rep.get(), json, false); + std::string pretty_json = "[\n" + " {\n" + " \"href\" : \"/light/1\",\n" + " \"rep\" : {\n" + " \"state\" : true\n" + " }\n" + " },\n" + " {\n" + " \"href\" : \"/count/1\",\n" + " \"rep\" : {\n" + " \"count\" : 100\n }\n" + " }\n" + "]\n"; + CheckJson(rep.get(), pretty_json, true); +} + +#if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) + +class TestJsonRepWithServer : public testing::Test { +public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_JSON_ENCODER); + oc_rep_decoder_set_type(OC_REP_JSON_DECODER); + + oc_set_con_res_announced(true); + ASSERT_TRUE(oc::TestDevice::StartServer()); +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + ASSERT_TRUE(oc::SetAccessInRFOTM(OCF_CON, kDeviceID, false, + OC_PERM_RETRIEVE | OC_PERM_UPDATE)); +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + } + + static void TearDownTestCase() + { + oc::TestDevice::StopServer(); + + oc_rep_encoder_set_type(OC_REP_CBOR_ENCODER); + oc_rep_decoder_set_type(OC_REP_CBOR_DECODER); + + oc_log_set_level(OC_LOG_LEVEL_INFO); + } +}; + +TEST_F(TestJsonRepWithServer, EncodePostPayload) +{ + constexpr std::string_view kNewName = "IoTivity Test Server"; + + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto post_handler = [](oc_client_response_t *data) { + EXPECT_EQ(OC_STATUS_CHANGED, data->code); + oc::TestDevice::Terminate(); + OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); + *static_cast(data->user_data) = true; + }; + + bool invoked = false; + oc_init_post(OC_CON_URI, &ep, nullptr, post_handler, HIGH_QOS, &invoked); + + oc_rep_start_root_object(); + oc_rep_set_text_string_v1(root, n, kNewName.data(), kNewName.length()); + oc_rep_end_root_object(); + + auto timeout = 1s; + ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); + + oc::TestDevice::PoolEventsMsV1(timeout, true); + ASSERT_TRUE(invoked); +} + +#endif /* !OC_SECURITY || OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + +#endif /* OC_JSON_ENCODER */ diff --git a/api/unittest/reptest.cpp b/api/unittest/reptest.cpp index f6c6aacfe6..9e539e4aa0 100644 --- a/api/unittest/reptest.cpp +++ b/api/unittest/reptest.cpp @@ -16,17 +16,32 @@ * ******************************************************************/ +#include "api/oc_con_resource_internal.h" +#include "api/oc_rep_decode_internal.h" #include "api/oc_rep_encode_internal.h" -#include "tests/gtest/RepPool.h" #include "oc_rep.h" +#include "port/oc_log_internal.h" +#include "tests/gtest/Device.h" +#include "tests/gtest/RepPool.h" +#include "tests/gtest/Resource.h" +#include "util/oc_secure_string_internal.h" #include +#include #include #include #include #include #include +using namespace std::chrono_literals; + +static constexpr size_t kDeviceID{ 0 }; +static const oc_rep_encoder_type_t g_rep_default_encoder = + oc_rep_encoder_get_type(); +static const oc_rep_decoder_type_t g_rep_default_decoder = + oc_rep_decoder_get_type(); + template static std::string BufferToString(InputIt first, InputIt last) @@ -38,13 +53,28 @@ BufferToString(InputIt first, InputIt last) return val; } -TEST(TestRep, OCRepEncodedPayloadSize_P) +class TestRep : public testing::Test { +public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_CBOR_ENCODER); + oc_rep_decoder_set_type(OC_REP_CBOR_DECODER); + } + + static void TearDownTestCase() + { + oc_rep_encoder_set_type(g_rep_default_encoder); + oc_rep_decoder_set_type(g_rep_default_decoder); + } +}; + +TEST_F(TestRep, OCRepEncodedPayloadSize_P) { int repSize = oc_rep_get_encoded_payload_size(); EXPECT_NE(repSize, -1); } -TEST(TestRep, OCRepEncodedPayloadSizeTooSmall) +TEST_F(TestRep, OCRepEncodedPayloadSizeTooSmall) { /* buffer for oc_rep_t */ std::array buf{}; // Purposely small buffer @@ -60,7 +90,7 @@ TEST(TestRep, OCRepEncodedPayloadSizeTooSmall) EXPECT_EQ(-1, oc_rep_get_encoded_payload_size()); } -TEST(TestRep, RepToJson_null) +TEST_F(TestRep, RepToJson_null) { const oc_rep_t *rep = nullptr; EXPECT_EQ(2, oc_rep_to_json(rep, nullptr, 0, false)); @@ -76,6 +106,18 @@ TEST(TestRep, RepToJson_null) class TestRepWithPool : public testing::Test { public: + static void SetUpTestCase() + { + oc_rep_encoder_set_type(OC_REP_CBOR_ENCODER); + oc_rep_decoder_set_type(OC_REP_CBOR_DECODER); + } + + static void TearDownTestCase() + { + oc_rep_encoder_set_type(g_rep_default_encoder); + oc_rep_decoder_set_type(g_rep_default_decoder); + } + oc_memb *GetRepObjectsPool() { return pool_.GetRepObjectsPool(); } oc::oc_rep_unique_ptr ParsePayload() { return pool_.ParsePayload(); } @@ -98,11 +140,11 @@ class TestRepWithPool : public testing::Test { TEST_F(TestRepWithPool, OCRepInvalidFormat) { oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_encode_int(&root_map, 42); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ const uint8_t *payload = oc_rep_get_encoder_buf(); @@ -119,19 +161,19 @@ TEST_F(TestRepWithPool, OCRepInvalidFormat) TEST_F(TestRepWithPool, OCRepInvalidArray) { oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_key(oc_rep_object(root), "mixed"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_begin_array(oc_rep_object(root), mixed); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_int(mixed, 42); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_text_string(mixed, "1337"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_array(oc_rep_object(root), mixed); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ const uint8_t *payload = oc_rep_get_encoder_buf(); @@ -149,11 +191,11 @@ TEST_F(TestRepWithPool, OCRepSetGetNull) { /* add null value to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_null(root, nothing); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -165,6 +207,9 @@ TEST_F(TestRepWithPool, OCRepSetGetNull) /* error handling */ EXPECT_FALSE(oc_rep_is_null(nullptr, "nothing", &is_null)); EXPECT_FALSE(oc_rep_is_null(rep.get(), nullptr, &is_null)); + EXPECT_FALSE(oc_rep_is_null(rep.get(), "", &is_null)); + EXPECT_FALSE(oc_rep_is_null( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &is_null)); EXPECT_FALSE(oc_rep_is_null(rep.get(), "nothing", nullptr)); EXPECT_FALSE(oc_rep_is_null(rep.get(), "not_the_key", &is_null)); @@ -174,13 +219,13 @@ TEST_F(TestRepWithPool, OCRepSetGetNull) TEST_F(TestRepWithPool, OCRepSetGetDouble) { - /* add int values to root object */ + /* add double values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_double(root, pi, 3.14159); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -190,9 +235,13 @@ TEST_F(TestRepWithPool, OCRepSetGetDouble) double pi_out = 0; EXPECT_TRUE(oc_rep_get_double(rep.get(), "pi", &pi_out)); EXPECT_EQ(3.14159, pi_out); + /* error handling */ EXPECT_FALSE(oc_rep_get_double(nullptr, "pi", &pi_out)); EXPECT_FALSE(oc_rep_get_double(rep.get(), nullptr, &pi_out)); + EXPECT_FALSE(oc_rep_get_double(rep.get(), "", &pi_out)); + EXPECT_FALSE(oc_rep_get_double( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &pi_out)); EXPECT_FALSE(oc_rep_get_double(rep.get(), "pi", nullptr)); EXPECT_FALSE(oc_rep_get_double(rep.get(), "no_a_key", &pi_out)); @@ -204,21 +253,25 @@ TEST_F(TestRepWithPool, OCRepSetGetInt) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_int(root, ultimate_answer, 10000000000); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_int(root, negative, -1024); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_int(root, zero, 0); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, max_positive, INT64_MAX); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_int(root, max_negative, INT64_MIN); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); ASSERT_NE(nullptr, rep.get()); - /* read the values from the oc_rep_t */ + /* read the values from the oc_rep_t */ int64_t ultimate_answer_out = 0; EXPECT_TRUE( oc_rep_get_int(rep.get(), "ultimate_answer", &ultimate_answer_out)); @@ -229,19 +282,31 @@ TEST_F(TestRepWithPool, OCRepSetGetInt) int64_t zero_out = -1; EXPECT_TRUE(oc_rep_get_int(rep.get(), "zero", &zero_out)); EXPECT_EQ(0, zero_out); + int64_t max_positive_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "max_positive", &max_positive_out)); + EXPECT_EQ(INT64_MAX, max_positive_out); + int64_t max_negative_out = 0; + EXPECT_TRUE(oc_rep_get_int(rep.get(), "max_negative", &max_negative_out)); + EXPECT_EQ(INT64_MIN, max_negative_out); + /* check error handling */ EXPECT_FALSE(oc_rep_get_int(nullptr, "zero", &zero_out)); EXPECT_FALSE(oc_rep_get_int(rep.get(), nullptr, &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "", &zero_out)); + EXPECT_FALSE(oc_rep_get_int( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &zero_out)); EXPECT_FALSE(oc_rep_get_int(rep.get(), "zero", nullptr)); EXPECT_FALSE(oc_rep_get_int(rep.get(), "not_a_key", &zero_out)); std::string json = - R"({"ultimate_answer":10000000000,"negative":-1024,"zero":0})"; + R"({"ultimate_answer":10000000000,"negative":-1024,"zero":0,"max_positive":9223372036854775807,"max_negative":-9223372036854775808})"; CheckJson(rep.get(), json, false); std::string pretty_json = "{\n" " \"ultimate_answer\" : 10000000000,\n" " \"negative\" : -1024,\n" - " \"zero\" : 0\n" + " \"zero\" : 0,\n" + " \"max_positive\" : 9223372036854775807,\n" + " \"max_negative\" : -9223372036854775808\n" "}\n"; CheckJson(rep.get(), pretty_json, true); } @@ -262,20 +327,20 @@ TEST_F(TestRepWithPool, OCRepSetGetUint) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_uint(root, ultimate_answer, 42); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* * Assuming 32 bit int, which should be true for systems the gtest will * be running on, the largest value for 32 bit int is 2,147,483,647 or * 0x7FFFFFFF */ oc_rep_set_uint(root, larger_than_int, 3000000000); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_uint(root, zero, 0); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -294,9 +359,13 @@ TEST_F(TestRepWithPool, OCRepSetGetUint) int64_t zero_out = -1; EXPECT_TRUE(oc_rep_get_int(rep.get(), "zero", &zero_out)); EXPECT_EQ(0u, (unsigned)zero_out); + /* check error handling */ EXPECT_FALSE(oc_rep_get_int(nullptr, "zero", &zero_out)); EXPECT_FALSE(oc_rep_get_int(rep.get(), nullptr, &zero_out)); + EXPECT_FALSE(oc_rep_get_int(rep.get(), "", &zero_out)); + EXPECT_FALSE(oc_rep_get_int( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &zero_out)); EXPECT_FALSE(oc_rep_get_int(rep.get(), "zero", nullptr)); EXPECT_FALSE(oc_rep_get_int(rep.get(), "not_a_key", &zero_out)); @@ -316,13 +385,13 @@ TEST_F(TestRepWithPool, OCRepSetGetBool) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_boolean(root, true_flag, true); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_boolean(root, false_flag, false); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -335,9 +404,13 @@ TEST_F(TestRepWithPool, OCRepSetGetBool) bool false_flag_out = true; EXPECT_TRUE(oc_rep_get_bool(rep.get(), "false_flag", &false_flag_out)); EXPECT_FALSE(false_flag_out); + /* check error handling */ EXPECT_FALSE(oc_rep_get_bool(nullptr, "true_flag", &true_flag_out)); EXPECT_FALSE(oc_rep_get_bool(rep.get(), nullptr, &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool(rep.get(), "", &true_flag_out)); + EXPECT_FALSE(oc_rep_get_bool( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &true_flag_out)); EXPECT_FALSE(oc_rep_get_bool(rep.get(), "true_flag", nullptr)); EXPECT_FALSE(oc_rep_get_bool(rep.get(), "not_a_key", &true_flag_out)); @@ -366,16 +439,16 @@ TEST_F(TestRepWithPool, OCRepSetGetTextString) { /* add text string value "hal9000":"Dave" to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(root, empty, ""); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(root, hal9000, "Dave"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* test utf8 character support "hello world" in russian */ oc_rep_set_text_string(root, ru_character_set, "Привет, мир"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -399,9 +472,14 @@ TEST_F(TestRepWithPool, OCRepSetGetTextString) * calculate the the number of bytes using the strlen function. */ EXPECT_EQ(strlen("Привет, мир"), str_len); + /* check error handling */ EXPECT_FALSE(oc_rep_get_string(nullptr, "hal9000", &hal9000_out, &str_len)); EXPECT_FALSE(oc_rep_get_string(rep.get(), nullptr, &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), "", &hal9000_out, &str_len)); + EXPECT_FALSE(oc_rep_get_string(rep.get(), + std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), + &hal9000_out, &str_len)); EXPECT_FALSE(oc_rep_get_string(rep.get(), "hal9000", nullptr, &str_len)); EXPECT_FALSE(oc_rep_get_string(rep.get(), "hal9000", &hal9000_out, nullptr)); EXPECT_FALSE( @@ -426,15 +504,15 @@ TEST_F(TestRepWithPool, OCRepSetGetByteString) { /* add text string value "hal9000":"Dave" to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_byte_string(root, empty_byte_string, nullptr, 0); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); const uint8_t test_byte_string[] = { 0x01, 0x02, 0x03, 0x04, 0x02, 0x00 }; oc_rep_set_byte_string(root, test_byte_string, test_byte_string, sizeof(test_byte_string)); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -455,11 +533,17 @@ TEST_F(TestRepWithPool, OCRepSetGetByteString) * through the array. */ EXPECT_STREQ((const char *)test_byte_string, test_byte_string_out); + /* error handling */ EXPECT_FALSE(oc_rep_get_byte_string(nullptr, "test_byte_string", &test_byte_string_out, &str_len)); EXPECT_FALSE(oc_rep_get_byte_string(rep.get(), nullptr, &test_byte_string_out, &str_len)); + EXPECT_FALSE( + oc_rep_get_byte_string(rep.get(), "", &test_byte_string_out, &str_len)); + EXPECT_FALSE(oc_rep_get_byte_string( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), + &test_byte_string_out, &str_len)); EXPECT_FALSE( oc_rep_get_byte_string(rep.get(), "test_byte_string", nullptr, &str_len)); EXPECT_FALSE(oc_rep_get_byte_string(rep.get(), "test_byte_string", @@ -513,14 +597,14 @@ TEST_F(TestRepWithPool, OCRepSetGetIntArray) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 10000000000 }; oc_rep_set_int_array(root, fibonacci, fib.data(), fib.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -528,7 +612,7 @@ TEST_F(TestRepWithPool, OCRepSetGetIntArray) /* read the values from the oc_rep_t */ int64_t *fib_out = nullptr; - size_t fib_len; + size_t fib_len = 0; EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); ASSERT_EQ(fib.size(), fib_len); for (size_t i = 0; i < fib_len; ++i) { @@ -560,19 +644,18 @@ TEST_F(TestRepWithPool, OCRepAddGetIntArray) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; - oc_rep_open_array(root, fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); for (const auto &v : fib) { oc_rep_add_int(fibonacci, v); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); } oc_rep_close_array(root, fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -580,7 +663,7 @@ TEST_F(TestRepWithPool, OCRepAddGetIntArray) /* read the values from the oc_rep_t */ int64_t *fib_out = nullptr; - size_t fib_len; + size_t fib_len = 0; EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); ASSERT_EQ(fib.size(), fib_len); for (size_t i = 0; i < fib_len; ++i) { @@ -604,20 +687,20 @@ TEST_F(TestRepWithPool, OCRepAddGetIntArrayUsingSetKeyAndBeginArray) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::vector fib = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 }; oc_rep_set_key(oc_rep_object(root), "fibonacci"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_begin_array(oc_rep_object(root), fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); for (const auto &v : fib) { oc_rep_add_int(fibonacci, v); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); } oc_rep_end_array(oc_rep_object(root), fibonacci); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -625,7 +708,7 @@ TEST_F(TestRepWithPool, OCRepAddGetIntArrayUsingSetKeyAndBeginArray) /* read the values from the oc_rep_t */ int64_t *fib_out = nullptr; - size_t fib_len; + size_t fib_len = 0; EXPECT_TRUE(oc_rep_get_int_array(rep.get(), "fibonacci", &fib_out, &fib_len)); ASSERT_EQ(fib.size(), fib_len); for (size_t i = 0; i < fib_len; ++i) { @@ -650,10 +733,11 @@ TEST_F(TestRepWithPool, OCRepSetGetEmptyBoolArray) */ /* add values to root object */ oc_rep_start_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_bool_array(root, emptyBool, (bool *)nullptr, 0); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -667,12 +751,12 @@ TEST_F(TestRepWithPool, OCRepSetGetBoolArray) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::array flip = { false, false, true, false, false }; oc_rep_set_bool_array(root, flip, flip.data(), flip.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -680,7 +764,7 @@ TEST_F(TestRepWithPool, OCRepSetGetBoolArray) /* read the values from the oc_rep_t */ bool *flip_out = nullptr; - size_t flip_len; + size_t flip_len = 0; EXPECT_TRUE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, &flip_len)); ASSERT_EQ(flip.size(), flip_len); for (size_t i = 0; i < flip_len; ++i) { @@ -690,6 +774,10 @@ TEST_F(TestRepWithPool, OCRepSetGetBoolArray) /* Error handling */ EXPECT_FALSE(oc_rep_get_bool_array(nullptr, "flip", &flip_out, &flip_len)); EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), nullptr, &flip_out, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "", &flip_out, &flip_len)); + EXPECT_FALSE(oc_rep_get_bool_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &flip_out, + &flip_len)); EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "flip", nullptr, &flip_len)); EXPECT_FALSE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, nullptr)); EXPECT_FALSE( @@ -711,18 +799,18 @@ TEST_F(TestRepWithPool, OCRepAddGetBoolArray) { /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); std::array flip = { false, false, true, false, false }; oc_rep_open_array(root, flip); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); for (const auto &v : flip) { oc_rep_add_boolean(flip, v); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); } oc_rep_close_array(root, flip); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -730,7 +818,7 @@ TEST_F(TestRepWithPool, OCRepAddGetBoolArray) /* read the values from the oc_rep_t */ bool *flip_out = nullptr; - size_t flip_len; + size_t flip_len = 0; EXPECT_TRUE(oc_rep_get_bool_array(rep.get(), "flip", &flip_out, &flip_len)); ASSERT_EQ(flip.size(), flip_len); for (size_t i = 0; i < flip_len; ++i) { @@ -868,13 +956,13 @@ TEST_F(TestRepWithPool, OCRepSetGetEmptyObject) */ /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_open_object(root, empty); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_object(root, empty); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -896,20 +984,21 @@ TEST_F(TestRepWithPool, OCRepSetGetObject) * } */ /* add values to root object */ + std::string c_value = "three"; oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_object(root, my_object); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_int(my_object, a, 1); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_boolean(my_object, b, false); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc_rep_set_text_string(my_object, c, "three"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + oc_rep_set_text_string(my_object, c, c_value.c_str()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_object(root, my_object); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -918,7 +1007,7 @@ TEST_F(TestRepWithPool, OCRepSetGetObject) /* read the values from the oc_rep_t */ oc_rep_t *my_object_out = nullptr; EXPECT_TRUE(oc_rep_get_object(rep.get(), "my_object", &my_object_out)); - ASSERT_TRUE(my_object_out != nullptr); + ASSERT_NE(nullptr, my_object_out); int64_t a_out; EXPECT_TRUE(oc_rep_get_int(my_object_out, "a", &a_out)); EXPECT_EQ(1, a_out); @@ -928,8 +1017,17 @@ TEST_F(TestRepWithPool, OCRepSetGetObject) char *c_out = nullptr; size_t c_out_size = 0; EXPECT_TRUE(oc_rep_get_string(my_object_out, "c", &c_out, &c_out_size)); - EXPECT_EQ(5, c_out_size); - EXPECT_STREQ("three", c_out); + EXPECT_EQ(c_value.length(), c_out_size); + EXPECT_STREQ(c_value.c_str(), c_out); + + /* Error handling */ + EXPECT_FALSE(oc_rep_get_object(nullptr, "my_object", &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), nullptr, &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "", &my_object_out)); + EXPECT_FALSE(oc_rep_get_object( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &my_object_out)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "my_object", nullptr)); + EXPECT_FALSE(oc_rep_get_object(rep.get(), "not_a_key", &my_object_out)); std::string json = R"({"my_object":{"a":1,"b":false,"c":"three"}})"; CheckJson(rep.get(), json, false); @@ -1004,13 +1102,13 @@ TEST_F(TestRepWithPool, OCRepSetGetEmptyObjectArray) */ /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_open_array(root, emptyObj); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_array(root, emptyObj); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -1032,41 +1130,41 @@ TEST_F(TestRepWithPool, OCRepSetGetObjectArray) */ /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_array(root, space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_start_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, name, "Dave Bowman"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, job, "astronaut"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_end_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_start_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, name, "Frank Poole"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, job, "astronaut"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_end_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_start_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, name, "Hal 9000"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(space_2001, job, "AI computer"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_end_item(space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_array(root, space_2001); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -1118,6 +1216,17 @@ TEST_F(TestRepWithPool, OCRepSetGetObjectArray) EXPECT_EQ(strlen("AI computer"), job_out_size); EXPECT_STREQ("AI computer", job_out); + /* Error handling */ + EXPECT_FALSE(oc_rep_get_object_array(nullptr, "space_2001", &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), nullptr, &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), "", &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), + &space_2001_out)); + EXPECT_FALSE(oc_rep_get_object_array(rep.get(), "space_2001", nullptr)); + EXPECT_FALSE( + oc_rep_get_object_array(rep.get(), "not_a_key", &space_2001_out)); + std::string json = R"({"space_2001":[{"name":"Dave Bowman","job":"astronaut"},)" R"({"name":"Frank Poole","job":"astronaut"},)" @@ -1154,21 +1263,21 @@ TEST_F(TestRepWithPool, OCRepAddGetByteStringArray) /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_array(root, barray); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_byte_string(barray, ba1.data(), ba1.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_byte_string(barray, ba2.data(), ba2.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_byte_string(barray, ba3.data(), ba3.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_byte_string(barray, ba4.data(), ba4.size()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_array(root, barray); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -1181,10 +1290,16 @@ TEST_F(TestRepWithPool, OCRepAddGetByteStringArray) &barray_len)); ASSERT_EQ(4, barray_len); + /* Error handling */ EXPECT_FALSE( oc_rep_get_byte_string_array(nullptr, "barray", &barray_out, &barray_len)); EXPECT_FALSE( oc_rep_get_byte_string_array(rep.get(), nullptr, &barray_out, &barray_len)); + EXPECT_FALSE( + oc_rep_get_byte_string_array(rep.get(), "", &barray_out, &barray_len)); + EXPECT_FALSE(oc_rep_get_byte_string_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), &barray_out, + &barray_len)); EXPECT_FALSE( oc_rep_get_byte_string_array(rep.get(), "barray", nullptr, &barray_len)); EXPECT_FALSE( @@ -1231,12 +1346,12 @@ TEST_F(TestRepWithPool, OCRepSetGetEmptyStringArray) */ /* add values to root object */ oc_rep_begin_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_string_array_t emptyStr{}; oc_rep_set_string_array(root, emptyStr, emptyStr); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); @@ -1275,11 +1390,11 @@ TEST_F(TestRepWithPool, OCRepSetGetStringArray) EXPECT_TRUE(oc_string_array_add_item(quotes, STR3.c_str())); /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_string_array(root, quotes, quotes); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_free_string_array("es); /* convert CborEncoder to oc_rep_t */ @@ -1287,8 +1402,8 @@ TEST_F(TestRepWithPool, OCRepSetGetStringArray) ASSERT_NE(nullptr, rep.get()); /* read the values from the oc_rep_t */ - oc_string_array_t quotes_out; - size_t quotes_len; + oc_string_array_t quotes_out{}; + size_t quotes_len = 0; EXPECT_TRUE( oc_rep_get_string_array(rep.get(), "quotes", "es_out, "es_len)); ASSERT_EQ(4, quotes_len); @@ -1298,6 +1413,11 @@ TEST_F(TestRepWithPool, OCRepSetGetStringArray) oc_rep_get_string_array(nullptr, "quotes", "es_out, "es_len)); EXPECT_FALSE( oc_rep_get_string_array(rep.get(), nullptr, "es_out, "es_len)); + EXPECT_FALSE( + oc_rep_get_string_array(rep.get(), "", "es_out, "es_len)); + EXPECT_FALSE(oc_rep_get_string_array( + rep.get(), std::string(OC_MAX_STRING_LENGTH, 'k').c_str(), "es_out, + "es_len)); EXPECT_FALSE( oc_rep_get_string_array(rep.get(), "quotes", nullptr, "es_len)); EXPECT_FALSE( @@ -1356,43 +1476,33 @@ TEST_F(TestRepWithPool, OCRepAddGetStringArray) /* add values to root object */ oc_rep_start_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_open_array(root, quotes); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_text_string(quotes, STR0.c_str()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_text_string(quotes, STR1.c_str()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_text_string(quotes, STR2.c_str()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_add_text_string(quotes, STR3.c_str()); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_array(root, quotes); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_root_object(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); /* convert CborEncoder to oc_rep_t */ oc::oc_rep_unique_ptr rep = ParsePayload(); ASSERT_NE(nullptr, rep.get()); /* read the values from the oc_rep_t */ - oc_string_array_t quotes_out; - size_t quotes_len; + oc_string_array_t quotes_out{}; + size_t quotes_len = 0; EXPECT_TRUE( oc_rep_get_string_array(rep.get(), "quotes", "es_out, "es_len)); ASSERT_EQ(4, quotes_len); - /* Error handling */ - EXPECT_FALSE( - oc_rep_get_string_array(nullptr, "quotes", "es_out, "es_len)); - EXPECT_FALSE( - oc_rep_get_string_array(rep.get(), nullptr, "es_out, "es_len)); - EXPECT_FALSE( - oc_rep_get_string_array(rep.get(), "quotes", nullptr, "es_len)); - EXPECT_FALSE( - oc_rep_get_string_array(rep.get(), "quotes", "es_out, nullptr)); - EXPECT_EQ(STR0.length(), oc_string_array_get_item_size(quotes_out, 0)); EXPECT_STREQ(STR0.c_str(), oc_string_array_get_item(quotes_out, 0)); EXPECT_EQ(STR1.length(), oc_string_array_get_item_size(quotes_out, 1)); @@ -1403,11 +1513,11 @@ TEST_F(TestRepWithPool, OCRepAddGetStringArray) EXPECT_STREQ(STR3.c_str(), oc_string_array_get_item(quotes_out, 3)); // clang-format off - std::string json = "{\"quotes\":" - "[\"" + STR0 + "\"," - "\"" + STR1 + "\"," - "\"" + STR2 + "\"," - "\"" + STR3 + "\"]}"; + std::string json = R"({"quotes":)" + R"([")" + STR0 + R"(",)" + R"(")" + STR1 + R"(",)" + R"(")" + STR2 + R"(",)" + R"(")" + STR3 + R"("]})"; // clang-format on CheckJson(rep.get(), json, false); // clang-format off @@ -1431,31 +1541,31 @@ TEST_F(TestRepWithPool, OCRepRootArrayObject) */ /* add values to root object */ oc_rep_start_links_array(); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_start_item(links); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(links, href, "/light/1"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_object(links, rep); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_boolean(rep, state, true); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_object(links, rep); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_end_item(links); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_start_item(links); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_text_string(links, href, "/count/1"); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_object(links, rep); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_set_int(rep, count, 100); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_close_object(links, rep); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_object_array_end_item(links); - EXPECT_EQ(CborNoError, oc_rep_get_cbor_errno()); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); oc_rep_end_links_array(); /* convert CborEncoder to oc_rep_t */ @@ -1469,6 +1579,7 @@ TEST_F(TestRepWithPool, OCRepRootArrayObject) EXPECT_EQ(OC_REP_OBJECT, rep->type); oc_rep_t *links = rep.get(); ASSERT_TRUE(links != nullptr); + char *href_out = nullptr; size_t href_out_size = 0; oc_rep_t *rep_out = nullptr; @@ -1518,3 +1629,54 @@ TEST_F(TestRepWithPool, OCRepRootArrayObject) "]\n"; CheckJson(rep.get(), pretty_json, true); } + +#if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) + +class TestRepWithServer : public testing::Test { +public: + static void SetUpTestCase() + { + oc_set_con_res_announced(true); + ASSERT_TRUE(oc::TestDevice::StartServer()); +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + ASSERT_TRUE(oc::SetAccessInRFOTM(OCF_CON, kDeviceID, false, + OC_PERM_RETRIEVE | OC_PERM_UPDATE)); +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + } + + static void TearDownTestCase() + { + oc::TestDevice::StopServer(); + } +}; + +TEST_F(TestRepWithServer, EncodePostPayload) +{ + constexpr std::string_view kNewName = "IoTivity Test Server"; + + auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); + ASSERT_TRUE(epOpt.has_value()); + auto ep = std::move(*epOpt); + + auto post_handler = [](oc_client_response_t *data) { + EXPECT_EQ(OC_STATUS_CHANGED, data->code); + oc::TestDevice::Terminate(); + OC_DBG("POST payload: %s", oc::RepPool::GetJson(data->payload).data()); + *static_cast(data->user_data) = true; + }; + + bool invoked = false; + oc_init_post(OC_CON_URI, &ep, nullptr, post_handler, HIGH_QOS, &invoked); + + oc_rep_start_root_object(); + oc_rep_set_text_string_v1(root, n, kNewName.data(), kNewName.length()); + oc_rep_end_root_object(); + + auto timeout = 1s; + ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); + + oc::TestDevice::PoolEventsMsV1(timeout, true); + ASSERT_TRUE(invoked); +} + +#endif /* !OC_SECURITY || OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ diff --git a/apps/cloud_server.c b/apps/cloud_server.c index 346e21d362..469c2228d3 100644 --- a/apps/cloud_server.c +++ b/apps/cloud_server.c @@ -1118,6 +1118,10 @@ simulate_tpm_pk_free_key(size_t device, const unsigned char *key, size_t keylen) #define OPT_TIME "time" #define OPT_SET_SYSTEM_TIME "set-system-time" +#ifdef OC_JSON_ENCODER +#define OPT_JSON_ENCODER "json-encoder" +#endif /* OC_JSON_ENCODER */ + #define OPT_ARG_DEVICE_NAME OPT_DEVICE_NAME #define OPT_ARG_CLOUD_AUTH_CODE OPT_CLOUD_AUTH_CODE #define OPT_ARG_CLOUD_CIS OPT_CLOUD_CIS @@ -1179,6 +1183,10 @@ printhelp(const char *exec_path) OC_PRINTF(" -x | --%-26s IPv6 TLS port (use -1 to disable it)\n", OPT_LISTEN_TLS_PORT " "); #endif /* OC_SECURITY */ +#ifdef OC_JSON_ENCODER + OC_PRINTF(" -j | --%-26s use JSON encoder to encode message payloads\n", + OPT_JSON_ENCODER); +#endif /* OC_JSON_ENCODER */ OC_PRINTF("ARGUMENTS:\n"); OC_PRINTF(" %-33s device name (optional, default: cloud_server)\n", OPT_ARG_DEVICE_NAME); @@ -1280,6 +1288,9 @@ parse_options(int argc, char *argv[], parse_options_result_t *parsed_options) { OPT_LISTEN_DTLS_PORT, required_argument, NULL, 'w' }, { OPT_LISTEN_TLS_PORT, required_argument, NULL, 'x' }, #endif /* OC_SECURITY */ +#ifdef OC_JSON_ENCODER + { OPT_JSON_ENCODER, no_argument, NULL, 'j' }, +#endif /* OC_JSON_ENCODER */ { NULL, 0, NULL, 0 }, }; @@ -1504,6 +1515,11 @@ parse_options(int argc, char *argv[], parse_options_result_t *parsed_options) break; } #endif /* OC_SECURITY */ +#ifdef OC_JSON_ENCODER + case 'j': + oc_rep_encoder_set_type(OC_REP_JSON_ENCODER); + break; +#endif /* OC_JSON_ENCODER */ default: OC_PRINTF("invalid option(%s)\n", argv[optind]); return false; diff --git a/docker/run.sh b/docker/run.sh index ec829b28e8..8d564b23ca 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -18,6 +18,7 @@ for ((i=0;i<$NUM_DEVICES;i++)); do export ASAN_OPTIONS="debug=true:atexit=true:check_initialization_order=true:detect_stack_use_after_return=true:alloc_dealloc_mismatch=true:detect_invalid_pointer_pairs=2:strict_string_checks=true:log_path=/tmp/${i}.asan.log:verify_asan_link_order=0" # abort on first tsan problem found export TSAN_OPTIONS="halt_on_error=1:abort_on_error=true:log_path=/tmp/${i}.tsan.log:suppressions=/tmp/tsan.suppressions" + export UBSAN_OPTIONS="print_stacktrace=1" export LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 ${PREFIX_EXEC} /iotivity-lite/port/linux/service $@ > /tmp/$i.log 2>&1 & pids+=($!) diff --git a/include/oc_rep.h b/include/oc_rep.h index beb8679c2b..340b7045ff 100644 --- a/include/oc_rep.h +++ b/include/oc_rep.h @@ -159,8 +159,6 @@ void oc_rep_encode_raw(const uint8_t *data, size_t len); * * @param encoder Internal Iotivity-lite encoder to store the value * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_null(CborEncoder *encoder); @@ -170,8 +168,6 @@ CborError oc_rep_encode_null(CborEncoder *encoder); * @param encoder Internal Iotivity-lite encoder to store the value * @param value value to encode * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_boolean(CborEncoder *encoder, bool value); @@ -181,8 +177,6 @@ CborError oc_rep_encode_boolean(CborEncoder *encoder, bool value); * @param encoder Internal Iotivity-lite encoder to store the value * @param value value to encode * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_int(CborEncoder *encoder, int64_t value); @@ -192,8 +186,6 @@ CborError oc_rep_encode_int(CborEncoder *encoder, int64_t value); * @param encoder Internal Iotivity-lite encoder to store the value * @param value value to encode * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_uint(CborEncoder *encoder, uint64_t value); @@ -205,8 +197,6 @@ CborError oc_rep_encode_uint(CborEncoder *encoder, uint64_t value); * CborDoubleType) * @param value value to encode * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); @@ -217,8 +207,6 @@ CborError oc_rep_encode_floating_point(CborEncoder *encoder, CborType fpType, * @param encoder Internal Iotivity-lite encoder to store the value * @param value value to encode * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_double(CborEncoder *encoder, double value); @@ -229,8 +217,6 @@ CborError oc_rep_encode_double(CborEncoder *encoder, double value); * @param string C-string to encode * @param length length of the C-string * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_text_string(CborEncoder *encoder, const char *string, size_t length); @@ -242,8 +228,6 @@ CborError oc_rep_encode_text_string(CborEncoder *encoder, const char *string, * @param string byte string to encode * @param length length of the byte string * @return CborError encoding error - * - * @see oc_rep_encoder_init */ CborError oc_rep_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); @@ -258,8 +242,6 @@ CborError oc_rep_encode_byte_string(CborEncoder *encoder, const uint8_t *string, * @return CborError encoding error * * @note Must be closed by oc_rep_encoder_close_container - * - * @see oc_rep_encoder_init */ CborError oc_rep_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length); @@ -273,8 +255,6 @@ CborError oc_rep_encoder_create_array(CborEncoder *encoder, * @return CborError encoding error * * @note Must be closed by oc_rep_encoder_close_container - * - * @see oc_rep_encoder_init */ CborError oc_rep_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); @@ -327,7 +307,7 @@ CborError oc_rep_encoder_close_container(CborEncoder *encoder, */ #define oc_rep_set_double(object, key, value) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_double(&object##_map, value); \ } while (0) @@ -352,7 +332,7 @@ CborError oc_rep_encoder_close_container(CborEncoder *encoder, */ #define oc_rep_set_int(object, key, value) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_int(&object##_map, value); \ } while (0) @@ -379,7 +359,7 @@ CborError oc_rep_encoder_close_container(CborEncoder *encoder, */ #define oc_rep_set_uint(object, key, value) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_uint(&object##_map, value); \ } while (0) @@ -404,7 +384,7 @@ CborError oc_rep_encoder_close_container(CborEncoder *encoder, */ #define oc_rep_set_boolean(object, key, value) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_boolean(&object##_map, value); \ } while (0) @@ -461,7 +441,7 @@ value */ */ #define oc_rep_set_byte_string(object, key, value, length) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_byte_string(&object##_map, value, length); \ } while (0) @@ -486,7 +466,7 @@ value */ */ #define oc_rep_set_null(object, key) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ g_err |= oc_rep_encode_null(&object##_map); \ } while (0) @@ -857,7 +837,7 @@ the value */ * @see oc_rep_close_array */ #define oc_rep_open_array(parent, key) \ - g_err |= oc_rep_encode_text_string(&parent##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&parent##_map, #key, sizeof(#key) - 1); \ oc_rep_begin_array(&parent##_map, key) /** @@ -983,7 +963,7 @@ the value */ * @see oc_rep_close_object */ #define oc_rep_open_object(parent, key) \ - g_err |= oc_rep_encode_text_string(&parent##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&parent##_map, #key, sizeof(#key) - 1); \ oc_rep_begin_object(&parent##_map, key) /** @@ -1020,7 +1000,7 @@ the value */ */ #define oc_rep_set_int_array(object, key, values, length) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ CborEncoder key##_value_array; \ memset(&key##_value_array, 0, sizeof(key##_value_array)); \ g_err |= \ @@ -1059,13 +1039,12 @@ the value */ */ #define oc_rep_set_bool_array(object, key, values, length) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ CborEncoder key##_value_array; \ memset(&key##_value_array, 0, sizeof(key##_value_array)); \ g_err |= \ oc_rep_encoder_create_array(&object##_map, &key##_value_array, length); \ - int i; \ - for (i = 0; i < (length); i++) { \ + for (size_t i = 0; i < (length); i++) { \ g_err |= oc_rep_encode_boolean(&key##_value_array, (values)[i]); \ } \ g_err |= \ @@ -1099,15 +1078,13 @@ the value */ */ #define oc_rep_set_double_array(object, key, values, length) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ CborEncoder key##_value_array; \ memset(&key##_value_array, 0, sizeof(key##_value_array)); \ g_err |= \ oc_rep_encoder_create_array(&object##_map, &key##_value_array, length); \ - int i; \ - for (i = 0; i < (length); i++) { \ - g_err |= oc_rep_encode_floating_point(&key##_value_array, \ - CborDoubleType, &(values)[i]); \ + for (size_t i = 0; i < (length); i++) { \ + g_err |= oc_rep_encode_double(&key##_value_array, (values)[i]); \ } \ g_err |= \ oc_rep_encoder_close_container(&object##_map, &key##_value_array); \ @@ -1162,7 +1139,7 @@ the value */ */ #define oc_rep_set_string_array(object, key, values) \ do { \ - g_err |= oc_rep_encode_text_string(&object##_map, #key, strlen(#key)); \ + g_err |= oc_rep_encode_text_string(&object##_map, #key, sizeof(#key) - 1); \ CborEncoder key##_value_array; \ memset(&key##_value_array, 0, sizeof(key##_value_array)); \ g_err |= oc_rep_encoder_create_array(&object##_map, &key##_value_array, \ @@ -1232,9 +1209,7 @@ int oc_parse_rep(const uint8_t *payload, size_t payload_size, void oc_free_rep(oc_rep_t *rep); -#ifdef OC_HAS_FEATURE_PUSH oc_rep_t *oc_alloc_rep(void); -#endif /** * Check for a null value from an `oc_rep_t` @@ -1648,6 +1623,29 @@ OC_API size_t oc_rep_to_json(const oc_rep_t *rep, char *buf, size_t buf_size, bool pretty_print); +typedef enum oc_rep_encoder_type_t { + OC_REP_CBOR_ENCODER = 0 /* default encoder */, +#ifdef OC_JSON_ENCODER + OC_REP_JSON_ENCODER = 1, +#endif /* OC_JSON_ENCODER */ +} oc_rep_encoder_type_t; + +/** + * @brief Set the encoder type used to encode the response payloads. + * + * @param encoder_type encoder type + */ +OC_API +void oc_rep_encoder_set_type(oc_rep_encoder_type_t encoder_type); + +/** + * @brief Get the encoder type used to encode the response payloads. + * + * @return encoder type + */ +OC_API +oc_rep_encoder_type_t oc_rep_encoder_get_type(void); + #ifdef __cplusplus } #endif diff --git a/include/oc_ri.h b/include/oc_ri.h index 3b57aab05b..9837262396 100644 --- a/include/oc_ri.h +++ b/include/oc_ri.h @@ -110,7 +110,6 @@ typedef enum { * @brief payload content formats * * https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#rd-parameters - * */ typedef enum { TEXT_PLAIN = 0, ///< text/plain @@ -153,11 +152,14 @@ typedef enum { APPLICATION_CRATTRS = 285, ///< application/csrattrs APPLICATION_PKCS10 = 286, ///< application/pkcs10 APPLICATION_PKIX_CERT = 287, ///< application/pkix-cert + APPLICATION_TD_JSON = 432, ///< application/td+json APPLICATION_VND_OCF_CBOR = 10000, ///< application/vnd.ocf+cbor APPLICATION_OSCORE = 10001, ///< application/oscore APPLICATION_VND_OMA_LWM2M_TLV = 11542, ///< application/vnd.oma.lwm2m+tlv APPLICATION_VND_OMA_LWM2M_JSON = 11543, ///< application/vnd.oma.lwm2m+json - APPLICATION_VND_OMA_LWM2M_CBOR = 11544 ///< application/vnd.oma.lwm2m+cbor + APPLICATION_VND_OMA_LWM2M_CBOR = 11544, ///< application/vnd.oma.lwm2m+cbor + + APPLICATION_NOT_DEFINED = 0xFFFF, ///< not defined } oc_content_format_t; /** diff --git a/include/oc_swupdate.h b/include/oc_swupdate.h index 06655fbcb8..9d6ebfaea4 100644 --- a/include/oc_swupdate.h +++ b/include/oc_swupdate.h @@ -189,7 +189,7 @@ typedef bool (*oc_swupdate_on_validate_update_error_fn_t)( OC_API bool oc_swupdate_validate_update( size_t device, const oc_rep_t *rep, - oc_swupdate_on_validate_update_error_fn_t on_error, void *data) OC_NONNULL(2); + oc_swupdate_on_validate_update_error_fn_t on_error, void *data); #ifdef __cplusplus } diff --git a/messaging/coap/coap.c b/messaging/coap/coap.c index 904a799e11..35fe5d3f4b 100644 --- a/messaging/coap/coap.c +++ b/messaging/coap/coap.c @@ -678,6 +678,30 @@ coap_parse_signal_options(coap_packet_t *packet, unsigned int option_number, } #endif /* OC_TCP */ +static bool +coap_parse_is_valid_content_format_option(int64_t content_format) +{ + return content_format == APPLICATION_VND_OCF_CBOR +#ifdef OC_SPEC_VER_OIC + || content_format == APPLICATION_CBOR +#endif /* OC_SPEC_VER_OIC */ +#ifdef OC_JSON_ENCODER + || content_format == APPLICATION_JSON || + content_format == APPLICATION_TD_JSON +#endif /* OC_JSON_ENCODER */ + ; +} + +static bool +coap_parse_is_valid_accept_option(int64_t accept) +{ + return coap_parse_is_valid_content_format_option(accept) +#ifdef OC_WKCORE + || accept == APPLICATION_LINK_FORMAT +#endif /* OC_SPEC_VER_OIC */ + ; +} + static coap_status_t coap_oscore_parse_inner_option(coap_packet_t *packet, unsigned int option_number, uint8_t *option, @@ -687,11 +711,7 @@ coap_oscore_parse_inner_option(coap_packet_t *packet, case COAP_OPTION_CONTENT_FORMAT: { int64_t content_format = coap_parse_int_option(option, option_length); COAP_DBG(" Content-Format [%" PRId64 "]", content_format); - if (content_format != APPLICATION_VND_OCF_CBOR -#ifdef OC_SPEC_VER_OIC - && content_format != APPLICATION_CBOR -#endif /* OC_SPEC_VER_OIC */ - ) { + if (!coap_parse_is_valid_content_format_option(content_format)) { return UNSUPPORTED_MEDIA_TYPE_4_15; } packet->content_format = (uint16_t)content_format; @@ -712,17 +732,7 @@ coap_oscore_parse_inner_option(coap_packet_t *packet, case COAP_OPTION_ACCEPT: { int64_t accept = coap_parse_int_option(option, option_length); COAP_DBG(" Accept [%" PRId64 "]", accept); - if (accept != APPLICATION_VND_OCF_CBOR -#ifdef OC_SPEC_VER_OIC - && accept != APPLICATION_CBOR -#endif /* OC_SPEC_VER_OIC */ -#ifdef OC_WKCORE - && accept != APPLICATION_LINK_FORMAT -#endif /* OC_SPEC_VER_OIC */ -#ifdef OC_CBOR - && accept != APPLICATION_CBOR -#endif /* OC_SPEC_VER_OIC */ - ) { + if (!coap_parse_is_valid_accept_option(accept)) { return NOT_ACCEPTABLE_4_06; } packet->accept = (uint16_t)accept; diff --git a/messaging/coap/coap_options.c b/messaging/coap/coap_options.c index 272e5a2dbe..9e82466097 100644 --- a/messaging/coap/coap_options.c +++ b/messaging/coap/coap_options.c @@ -77,7 +77,8 @@ coap_options_set_content_format(coap_packet_t *packet, } bool -coap_options_get_accept(const coap_packet_t *packet, uint16_t *accept) +coap_options_get_accept(const coap_packet_t *packet, + oc_content_format_t *accept) { if (!IS_OPTION(packet, COAP_OPTION_ACCEPT)) { return false; @@ -87,9 +88,10 @@ coap_options_get_accept(const coap_packet_t *packet, uint16_t *accept) } void -coap_options_set_accept(coap_packet_t *packet, uint16_t accept) +coap_options_set_accept(coap_packet_t *packet, oc_content_format_t accept) { - packet->accept = accept; + assert(accept <= UINT16_MAX); + packet->accept = (uint16_t)accept; SET_OPTION(packet, COAP_OPTION_ACCEPT); } diff --git a/messaging/coap/coap_options.h b/messaging/coap/coap_options.h index 4f506a012b..0c2ce65374 100644 --- a/messaging/coap/coap_options.h +++ b/messaging/coap/coap_options.h @@ -117,8 +117,8 @@ void coap_options_set_content_format(coap_packet_t *packet, * @param packet packet to read (cannot be NULL) * @param[out] accept output parameter for the Accept value (cannot be NULL) */ -bool coap_options_get_accept(const coap_packet_t *packet, uint16_t *accept) - OC_NONNULL(); +bool coap_options_get_accept(const coap_packet_t *packet, + oc_content_format_t *accept) OC_NONNULL(); /** * @brief Set the Accept option value. @@ -126,7 +126,7 @@ bool coap_options_get_accept(const coap_packet_t *packet, uint16_t *accept) * The CoAP Accept option can be used to indicate which Content-Format is * acceptable to the client. */ -void coap_options_set_accept(coap_packet_t *packet, uint16_t accept) +void coap_options_set_accept(coap_packet_t *packet, oc_content_format_t accept) OC_NONNULL(); /** diff --git a/messaging/coap/engine.c b/messaging/coap/engine.c index 26ccb9aa6f..8d03d6f53f 100644 --- a/messaging/coap/engine.c +++ b/messaging/coap/engine.c @@ -439,7 +439,11 @@ coap_receive_blockwise_block2(coap_receive_ctx_t *ctx, const char *href, ctx->transaction->mid = ctx->response->mid; coap_options_set_accept(ctx->response, APPLICATION_VND_OCF_CBOR); } - coap_options_set_content_format(ctx->response, APPLICATION_VND_OCF_CBOR); + oc_content_format_t cf = APPLICATION_VND_OCF_CBOR; + if (response_state->base.content_format > 0) { + cf = response_state->base.content_format; + } + coap_options_set_content_format(ctx->response, cf); coap_set_payload(ctx->response, payload, payload_size); coap_options_set_block2(ctx->response, ctx->block2.num, more, ctx->block2.size, 0); @@ -679,6 +683,7 @@ coap_receive_set_response_by_handler(coap_receive_ctx_t *ctx, return ctx->response->code; } + ctx->response_buffer->content_format = ctx->response->content_format; #ifdef OC_BLOCK_WISE #ifdef OC_TCP if ((endpoint->flags & TCP) != 0) { diff --git a/messaging/coap/observe.c b/messaging/coap/observe.c index a379f4263e..c4f7468278 100644 --- a/messaging/coap/observe.c +++ b/messaging/coap/observe.c @@ -622,6 +622,7 @@ coap_prepare_notification_blockwise(coap_packet_t *notification, response->response_buffer->response_length); bwt_state->base.payload_size = (uint32_t)response->response_buffer->response_length; + bwt_state->base.content_format = response->response_buffer->content_format; uint32_t payload_size = 0; void *payload = oc_blockwise_dispatch_block(&bwt_state->base, 0, obs->block2_size, &payload_size); @@ -841,6 +842,7 @@ coap_notify_collection(oc_collection_t *collection, memset(&response_buffer, 0, sizeof(response_buffer)); response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; response.response_buffer = &response_buffer; request.response = &response; request.request_payload = NULL; @@ -919,6 +921,7 @@ coap_notify_collections(const oc_resource_t *resource) response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; response.response_buffer = &response_buffer; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; request.response = &response; request.request_payload = NULL; request.method = OC_GET; @@ -1150,7 +1153,9 @@ coap_notify_observers_internal(oc_resource_t *resource, memset(&response_buffer, 0, sizeof(response_buffer)); response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; response.response_buffer = &response_buffer; + num = coap_iterate_observers(resource, &response, endpoint, true); #ifdef OC_DYNAMIC_ALLOCATION @@ -1187,6 +1192,7 @@ notify_resource_defaults_observer(oc_resource_t *resource, memset(&response_buffer, 0, sizeof(response_buffer)); response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; response.response_buffer = &response_buffer; /* iterate over observers */ for (coap_observer_t *obs = (coap_observer_t *)oc_list_head(g_observers_list); @@ -1350,6 +1356,7 @@ coap_process_discovery_batch_observers(void) batch_observer_get_resource_uri(batch_obs)); response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; while (batch_obs != NULL) { #ifdef OC_BLOCK_WISE @@ -1534,6 +1541,7 @@ notify_discovery_observers(oc_resource_t *resource) memset(&response_buffer, 0, sizeof(response_buffer)); response_buffer.buffer = buffer; response_buffer.buffer_size = OC_MIN_OBSERVE_SIZE; + response_buffer.content_format = APPLICATION_VND_OCF_CBOR; response.response_buffer = &response_buffer; int num = 0; diff --git a/messaging/coap/unittest/optionstest.cpp b/messaging/coap/unittest/optionstest.cpp index dcf9144839..5878934ab3 100644 --- a/messaging/coap/unittest/optionstest.cpp +++ b/messaging/coap/unittest/optionstest.cpp @@ -30,9 +30,9 @@ TEST_F(TestOptions, GetContentFormat) { coap_packet_t packet{}; EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_CONTENT_FORMAT)); - auto content_format = static_cast(-1); + auto content_format = APPLICATION_NOT_DEFINED; EXPECT_FALSE(coap_options_get_content_format(&packet, &content_format)); - EXPECT_EQ(static_cast(-1), content_format); + EXPECT_EQ(APPLICATION_NOT_DEFINED, content_format); } TEST_F(TestOptions, SetContentFormat) @@ -41,25 +41,25 @@ TEST_F(TestOptions, SetContentFormat) EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_CONTENT_FORMAT)); coap_options_set_content_format(&packet, APPLICATION_CBOR); EXPECT_NE(0, IS_OPTION(&packet, COAP_OPTION_CONTENT_FORMAT)); - auto content_format = static_cast(-1); + auto content_format = APPLICATION_NOT_DEFINED; EXPECT_TRUE(coap_options_get_content_format(&packet, &content_format)); EXPECT_EQ(APPLICATION_CBOR, content_format); // unset Content-Format UNSET_OPTION(&packet, COAP_OPTION_CONTENT_FORMAT); EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_CONTENT_FORMAT)); - content_format = static_cast(-1); + content_format = APPLICATION_NOT_DEFINED; EXPECT_FALSE(coap_options_get_content_format(&packet, &content_format)); - EXPECT_EQ(static_cast(-1), content_format); + EXPECT_EQ(APPLICATION_NOT_DEFINED, content_format); } TEST_F(TestOptions, GetAccept) { coap_packet_t packet{}; EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_ACCEPT)); - auto accept = static_cast(-1); + auto accept = APPLICATION_NOT_DEFINED; EXPECT_FALSE(coap_options_get_accept(&packet, &accept)); - EXPECT_EQ(static_cast(-1), accept); + EXPECT_EQ(APPLICATION_NOT_DEFINED, accept); } TEST_F(TestOptions, SetAccept) @@ -68,16 +68,16 @@ TEST_F(TestOptions, SetAccept) EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_ACCEPT)); coap_options_set_accept(&packet, APPLICATION_CBOR); EXPECT_NE(0, IS_OPTION(&packet, COAP_OPTION_ACCEPT)); - auto accept = static_cast(-1); + auto accept = APPLICATION_NOT_DEFINED; EXPECT_TRUE(coap_options_get_accept(&packet, &accept)); EXPECT_EQ(APPLICATION_CBOR, accept); // unset Accept UNSET_OPTION(&packet, COAP_OPTION_ACCEPT); EXPECT_EQ(0, IS_OPTION(&packet, COAP_OPTION_ACCEPT)); - accept = static_cast(-1); + accept = APPLICATION_NOT_DEFINED; EXPECT_FALSE(coap_options_get_accept(&packet, &accept)); - EXPECT_EQ(static_cast(-1), accept); + EXPECT_EQ(APPLICATION_NOT_DEFINED, accept); } TEST_F(TestOptions, GetMaxAge) diff --git a/port/android/Makefile b/port/android/Makefile index 0f83d56d0f..a24a9bc6a6 100644 --- a/port/android/Makefile +++ b/port/android/Makefile @@ -110,6 +110,7 @@ JAVA ?= 1 export INTROSPECTION ?= 1 export IDD ?= 1 export ETAG ?= 0 +export JSON_ENCODER ?= 0 ROOT_DIR = ../.. SWIG_DIR = $(ROOT_DIR)/swig @@ -149,6 +150,9 @@ SRC_UTIL:=$(filter-out %_mem_trace.c,$(wildcard ../../util/*.c)) else SRC_UTIL:=$(wildcard ../../util/*.c) endif +ifeq ($(JSON_ENCODER),1) +SRC_UTIL+=$(wildcard ../../util/jsmn/*.c) +endif SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_PORT_COMMON} ${SRC_UTIL} SRC_API:=$(wildcard ../../api/*.c) ifneq ($(INTROSPECTION),1) @@ -157,6 +161,9 @@ endif ifneq ($(ETAG),1) SRC_API:=$(filter-out %oc_etag.c,${SRC_API}) endif +ifneq ($(JSON_ENCODER),1) +SRC_API:=$(filter-out %oc_rep_decode_json.c %oc_rep_encode_json.c,${SRC_API}) +endif SRC:=${SRC_API} $(wildcard ../../messaging/coap/*.c ../../port/android/*.c) SRC_CLIENT:=$(wildcard ../../api/client/*.c) SRC_CLOUD:=$(wildcard ../../api/cloud/*.c) @@ -197,7 +204,9 @@ else OBJ_CLOUD = endif OBJ_CLIENT_SERVER=$(addprefix ${OBJDIR}/client_server/,$(notdir $(SRC:.c=.o) $(SRC_CLIENT:.c=.o))) -VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/:../../port/common/:../../port/common/posix/:../../util/: +VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/: +VPATH+=../../port/common/:../../port/common/posix/: +VPATH+=../../util/:../../util/jsmn/: VPATH+=../../deps/tinycbor/src/:../../deps/mbedtls/library: LIBS?= -lm -llog @@ -295,6 +304,10 @@ ifeq ($(ETAG),1) EXTRA_CFLAGS += -DOC_ETAG endif +ifeq ($(JSON_ENCODER),1) + EXTRA_CFLAGS += -DOC_JSON_ENCODER +endif + CFLAGS += $(EXTRA_CFLAGS) CONSTRAINED_LIBS = libiotivity-lite-server.a libiotivity-lite-client.a \ diff --git a/port/esp32/main/CMakeLists.txt b/port/esp32/main/CMakeLists.txt index 8182b645ed..0c6517a4ed 100644 --- a/port/esp32/main/CMakeLists.txt +++ b/port/esp32/main/CMakeLists.txt @@ -49,6 +49,7 @@ set(sources ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_network_events.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_query.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_rep.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_rep_decode.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_rep_encode.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_rep_to_json.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/oc_resource.c diff --git a/port/linux/Makefile b/port/linux/Makefile index 24838669b2..a3b8216c4c 100644 --- a/port/linux/Makefile +++ b/port/linux/Makefile @@ -16,6 +16,7 @@ export OSCORE ?= 1 INTROSPECTION ?= 1 export IDD ?= 1 export ETAG ?= 0 +export JSON_ENCODER ?= 0 DESTDIR ?= /usr/local install_bin_dir?=${DESTDIR}/opt/iotivity-lite/bin/ prefix = $(DESTDIR) @@ -121,6 +122,9 @@ SRC_UTIL:=$(filter-out %_mem_trace.c,$(wildcard ../../util/*.c)) else SRC_UTIL:=$(wildcard ../../util/*.c) endif +ifeq ($(JSON_ENCODER),1) +SRC_UTIL+=$(wildcard ../../util/jsmn/*.c) +endif SRC_COMMON:=${CBOR} ${CTIMESTAMP} ${SRC_PORT_COMMON} ${SRC_UTIL} SRC_API:=$(wildcard ../../api/*.c) ifneq ($(INTROSPECTION),1) @@ -129,6 +133,9 @@ endif ifneq ($(ETAG),1) SRC_API:=$(filter-out %oc_etag.c,${SRC_API}) endif +ifneq ($(JSON_ENCODER),1) +SRC_API:=$(filter-out %oc_rep_decode_json.c %oc_rep_encode_json.c,${SRC_API}) +endif SRC:=${SRC_API} $(wildcard ../../messaging/coap/*.c ../../port/linux/*.c) SRC_CLIENT:=$(wildcard ../../api/client/*.c) SRC_CLOUD:=$(wildcard ../../api/cloud/*.c) @@ -167,7 +174,10 @@ OBJ_PYTHON=$(addprefix obj/python/,$(notdir $(SRC_PYTHON:.c=.o))) # $(info OBJ_PYTHON is $(OBJ_PYTHON)) # $(info SRC_PYTHON is $(SRC_PYTHON)) -VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/:../../port/common/:../../port/common/posix/:../../python/:../../util/: +VPATH=../../api/:../../api/client/:../../api/cloud/:../../api/c-timestamp:../../messaging/coap/: +VPATH+=../../port/common/:../../port/common/posix/: +VPATH+=../../python/: +VPATH+=../../util/:../../util/jsmn/: VPATH+=../../deps/tinycbor/src/:../../deps/mbedtls/library: LIBS?= -lm -pthread -lrt @@ -306,6 +316,10 @@ ifeq ($(ETAG),1) EXTRA_CFLAGS += -DOC_ETAG endif +ifeq ($(JSON_ENCODER),1) + EXTRA_CFLAGS += -DOC_JSON_ENCODER +endif + # DPP-baesd Streamlined Onboarding applications SO_DPP_SAMPLES = speaker_server speaker_client dpp_diplomat SO_DPP_OBJ = obj/ocf_dpp.o diff --git a/security/oc_acl.c b/security/oc_acl.c index d5f5c03eac..4eec1153bf 100644 --- a/security/oc_acl.c +++ b/security/oc_acl.c @@ -18,6 +18,7 @@ #ifdef OC_SECURITY +#include "api/oc_core_res_internal.h" #include "api/oc_discovery_internal.h" #include "api/oc_helpers_internal.h" #include "api/oc_ri_internal.h" @@ -373,9 +374,10 @@ oc_sec_check_acl_on_get(const oc_resource_t *resource, bool is_otm) /* Retrieve requests to "/oic/res", "/oic/d" and "/oic/p" shall be granted. */ if (is_otm && - ((uri_len == 8 && + ((uri_len == OC_CHAR_ARRAY_LEN(OCF_RES_URI) && memcmp(uri, OCF_RES_URI, OC_CHAR_ARRAY_LEN(OCF_RES_URI)) == 0) || - (uri_len == 6 && memcmp(uri, "/oic/d", 6) == 0) || + (uri_len == OC_CHAR_ARRAY_LEN(OCF_D_URI) && + memcmp(uri, OCF_D_URI, OC_CHAR_ARRAY_LEN(OCF_D_URI)) == 0) || (uri_len == 6 && memcmp(uri, "/oic/p", 6) == 0))) { return true; } @@ -1308,7 +1310,7 @@ bool oc_sec_acl_add_bootstrap_acl(size_t device) { bool ret = oc_sec_acl_anon_connection(device, OCF_RES_URI, OC_PERM_RETRIEVE); - ret = oc_sec_acl_anon_connection(device, "/oic/d", OC_PERM_RETRIEVE) && ret; + ret = oc_sec_acl_anon_connection(device, OCF_D_URI, OC_PERM_RETRIEVE) && ret; ret = oc_sec_acl_anon_connection(device, "/oic/p", OC_PERM_RETRIEVE) && ret; #ifdef OC_WKCORE ret = diff --git a/security/oc_cred.c b/security/oc_cred.c index 239595a6ad..e0b7c10588 100644 --- a/security/oc_cred.c +++ b/security/oc_cred.c @@ -1182,6 +1182,9 @@ oc_cred_usage_from_string(const char *str, size_t str_len) oc_sec_credusage_t oc_cred_parse_credusage(const oc_string_t *credusage_string) { + if (oc_string_len(*credusage_string) == 0) { + return OC_CREDUSAGE_NULL; + } return oc_cred_usage_from_string(oc_string(*credusage_string), oc_string_len(*credusage_string)); } @@ -1328,6 +1331,9 @@ oc_cred_encoding_from_string(const char *str, size_t str_len) oc_sec_encoding_t oc_cred_parse_encoding(const oc_string_t *encoding_string) { + if (oc_string_len(*encoding_string) == 0) { + return OC_ENCODING_UNSUPPORTED; + } return oc_cred_encoding_from_string(oc_string(*encoding_string), oc_string_len(*encoding_string)); } diff --git a/security/oc_oscore_crypto.c b/security/oc_oscore_crypto.c index 1458f3dc7c..5f080ac677 100644 --- a/security/oc_oscore_crypto.c +++ b/security/oc_oscore_crypto.c @@ -111,7 +111,9 @@ HKDF_Expand(const uint8_t *prk, const uint8_t *info, uint8_t info_len, uint8_t okm_buffer[HKDF_OUTPUT_MAXLEN]; /* Iteration T(1) */ - memcpy(iter_buffer, info, info_len); + if (info_len > 0) { + memcpy(iter_buffer, info, info_len); + } iter_buffer[info_len] = 0x01; /* HMAC_SHA256() returns an output of size HMAC_SHA256_HASHLEN */ HMAC_SHA256(prk, HMAC_SHA256_HASHLEN, iter_buffer, info_len + 1, @@ -121,7 +123,9 @@ HKDF_Expand(const uint8_t *prk, const uint8_t *info, uint8_t info_len, for (uint8_t i = 1; i < N; i++) { memcpy(iter_buffer, &okm_buffer[(size_t)(i - 1) * HMAC_SHA256_HASHLEN], HMAC_SHA256_HASHLEN); - memcpy(&iter_buffer[HMAC_SHA256_HASHLEN], info, info_len); + if (info_len > 0) { + memcpy(&iter_buffer[HMAC_SHA256_HASHLEN], info, info_len); + } iter_buffer[HMAC_SHA256_HASHLEN + info_len] = i + 1; HMAC_SHA256(prk, HMAC_SHA256_HASHLEN, iter_buffer, HMAC_SHA256_HASHLEN + info_len + 1, diff --git a/security/oc_security.c b/security/oc_security.c index ab948a62a3..5bfe484f92 100644 --- a/security/oc_security.c +++ b/security/oc_security.c @@ -102,7 +102,7 @@ oc_sec_self_own(size_t device) oc_sec_sdi_t *sdi = oc_sec_sdi_get(device); const oc_device_info_t *self = oc_core_get_device_info(device); oc_gen_uuid(&sdi->uuid); - oc_new_string(&sdi->name, oc_string(self->name), oc_string_len(self->name)); + oc_set_string(&sdi->name, oc_string(self->name), oc_string_len(self->name)); sdi->priv = false; oc_sec_dump_pstat(device); diff --git a/swig/Makefile b/swig/Makefile index 6fefdfc907..b7bc49d97f 100644 --- a/swig/Makefile +++ b/swig/Makefile @@ -107,6 +107,11 @@ ifeq ($(ETAG),1) SWIG_CFLAG += -DOC_ETAG endif +ifeq ($(JSON_ENCODER),1) + EXTRA_FLAG += -DOC_JSON_ENCODER + SWIG_CFLAG += -DOC_JSON_ENCODER +endif + SRC = oc_api oc_buffer_settings oc_clock oc_cloud oc_collection oc_connectivity oc_core_res oc_cred \ oc_endpoint oc_enums oc_etag oc_introspection oc_link oc_obt oc_pki oc_random oc_rep oc_session_events \ oc_swupdate oc_storage oc_uuid diff --git a/swig/swig_interfaces/oc_rep.i b/swig/swig_interfaces/oc_rep.i index 9747418bcc..b33b00cbea 100644 --- a/swig/swig_interfaces/oc_rep.i +++ b/swig/swig_interfaces/oc_rep.i @@ -1867,6 +1867,10 @@ char *jni_rep_to_json(oc_rep_t *rep, bool prettyPrint) } %} +%ignore oc_rep_encoder_type_t; +%ignore oc_rep_encoder_set_type; +%ignore oc_rep_encoder_get_type; + #define OC_API #define OC_DEPRECATED(...) #define OC_NONNULL(...) diff --git a/tests/gtest/RepPool.cpp b/tests/gtest/RepPool.cpp index 865296fd0c..7f605a2d54 100644 --- a/tests/gtest/RepPool.cpp +++ b/tests/gtest/RepPool.cpp @@ -16,6 +16,7 @@ * ****************************************************************************/ +#include "api/oc_rep_internal.h" #include "RepPool.h" #include @@ -27,7 +28,7 @@ RepPool::RepPool(size_t size) { #ifdef OC_DYNAMIC_ALLOCATION oc_rep_new_realloc_v1(&buffer_, 0, size); -#else +#else /* !OC_DYNAMIC_ALLOCATION */ buffer_.resize(size); oc_rep_new_v1(buffer_.data(), buffer_.size()); memset(rep_objects_alloc_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); @@ -47,7 +48,7 @@ RepPool::Clear() { #ifdef OC_DYNAMIC_ALLOCATION oc_rep_new_realloc_v1(&buffer_, 0, size_); -#else +#else /* !OC_DYNAMIC_ALLOCATION */ buffer_.resize(size_); oc_rep_new_v1(buffer_.data(), buffer_.size()); memset(rep_objects_alloc_, 0, OC_MAX_NUM_REP_OBJECTS * sizeof(char)); diff --git a/tests/gtest/Utility.h b/tests/gtest/Utility.h index 400cc6d1d7..48e885cb40 100644 --- a/tests/gtest/Utility.h +++ b/tests/gtest/Utility.h @@ -28,4 +28,35 @@ namespace oc { /** Convert oc_string_array_t to a vector */ std::vector GetVector(const oc_string_array_t &array); +template +std::vector +GetVector(const std::string &str, bool includeTerminator = false) +{ + std::vector arr; + arr.resize(str.length()); + for (size_t i = 0; i < str.length(); ++i) { + arr[i] = static_cast(str[i]); + } + if (includeTerminator) { + arr.push_back(static_cast('\0')); + } + return arr; +} + +template +std::string +GetString(From *arr, size_t arrSize) +{ + std::string str{}; + str.resize(arrSize); + for (size_t i = 0; i < arrSize; ++i) { + str[i] = static_cast(arr[i]); + } + // add null-terminator if not present + if (arrSize > 0 && arr[arrSize - 1] != '\0') { + str.push_back('\0'); + } + return str; +} + } // namespace oc diff --git a/util/jsmn/jsmn.c b/util/jsmn/jsmn.c new file mode 100644 index 0000000000..f735664540 --- /dev/null +++ b/util/jsmn/jsmn.c @@ -0,0 +1,316 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "jsmn_internal.h" + +#ifdef OC_JSON_ENCODER + +#include +#include +#include +#include + +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t * +jsmn_init_token(jsmntok_t *tok) +{ + tok->type = JSMN_UNDEFINED; + tok->start = tok->end = -1; + 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; +} + +/** + * Fills next available token with JSON primitive. + */ +static int +jsmn_parse_primitive(jsmn_parser_t *parser, const char *js, const size_t len, + jsmn_parsed_token_cb_t cb, void *data) +{ + assert(js[parser->pos] != '\0'); + jsmntok_t token; + unsigned start = parser->pos; + for (; parser->pos < len; parser->pos++) { + switch (js[parser->pos]) { + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': + 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; + } + } + +found: + + jsmn_init_token(&token); + jsmn_fill_token(&token, JSMN_PRIMITIVE, (int)start, (int)parser->pos); + if (!parser->parsing_container && cb != NULL && !cb(&token, js, data)) { + return JSMN_ERROR_INVAL; + } + parser->pos--; + return 0; +} + +static int +jsmn_parse_hex_char(jsmn_parser_t *parser, const char *js, const size_t len, + uint8_t max_digits) +{ + parser->pos++; + for (int i = 0; + i < max_digits && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if (!isxdigit(js[parser->pos])) { + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + return 0; +} + +static int +jsmn_parse_escaped_char(jsmn_parser_t *parser, const char *js, const size_t len) +{ + if (js[parser->pos] != '\\' || parser->pos + 1 >= len) { + return 0; + } + + /* Backslash: Quoted symbol expected */ + 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': + if (jsmn_parse_hex_char(parser, js, len, 4) < 0) { + return JSMN_ERROR_INVAL; + } + break; + /* Unexpected symbol */ + default: + return JSMN_ERROR_INVAL; + } + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int +jsmn_parse_string(jsmn_parser_t *parser, const char *js, const size_t len, + jsmn_parsed_token_cb_t cb, void *data) +{ + unsigned start = parser->pos; + + /* Skip starting quote */ + parser->pos++; + + jsmntok_t token; + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + /* Quote: end of string */ + if (js[parser->pos] == '\"') { + jsmn_init_token(&token); + jsmn_fill_token(&token, JSMN_STRING, (int)start + 1, (int)parser->pos); + if (!parser->parsing_container && cb != NULL && !cb(&token, js, data)) { + return JSMN_ERROR_INVAL; + } + return 0; + } + if (jsmn_parse_escaped_char(parser, js, len) < 0) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +static void +jsmn_open_container(jsmn_parser_t *parser, jsmntok_t *token, bool is_object) +{ + if (!parser->parsing_container) { + token->start = (int)(parser->pos + 1); + token->type = is_object ? JSMN_OBJECT : JSMN_ARRAY; + parser->parsing_container = true; + } + parser->depth++; +} + +static int +jsmn_close_container(jsmn_parser_t *parser, jsmntok_t *token, const char *js, + jsmn_parsed_token_cb_t cb, void *data, bool is_object) +{ + if (!parser->parsing_container) { + return JSMN_ERROR_INVAL; + } + parser->depth--; + if (parser->depth == 0) { + parser->parsing_container = false; + } + if (parser->parsing_container) { + return 0; + } + if (token->type != (is_object ? JSMN_OBJECT : JSMN_ARRAY)) { + return JSMN_ERROR_INVAL; + } + token->end = (int)parser->pos; + if (cb != NULL && !cb(token, js, data)) { + return JSMN_ERROR_INVAL; + } + return 1; +} + +static int +jsmn_parse_next_char(jsmn_parser_t *parser, jsmntok_t *token, const char *js, + const size_t len, jsmn_parsed_token_cb_t cb, void *data) +{ + int count = 0; + char c = js[parser->pos]; + switch (c) { + case '{': + case '[': + jsmn_open_container(parser, token, c == '{'); + break; + case '}': + case ']': { + int r = jsmn_close_container(parser, token, js, cb, data, c == '}'); + if (r < 0) { + return r; + } + count += r; + break; + } + case '\"': { + int r = jsmn_parse_string(parser, js, len, cb, data); + if (r < 0) { + return r; + } + if (!parser->parsing_container) { + count++; + } + break; + } + case '\t': + case '\r': + case '\n': + case ' ': + case ':': + case ',': + break; + /* In non-strict mode every unquoted value is a primitive */ + default: { + int r = jsmn_parse_primitive(parser, js, len, cb, data); + if (r < 0) { + return r; + } + if (!parser->parsing_container) { + count++; + } + break; + } + } + + return count; +} + +int +jsmn_parse(jsmn_parser_t *parser, const char *js, const size_t len, + jsmn_parsed_token_cb_t cb, void *data) +{ + jsmntok_t token; + jsmn_init_token(&token); + unsigned count = 0; + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + int r = jsmn_parse_next_char(parser, &token, js, len, cb, data); + if (r < 0) { + return r; + } + count += r; + } + + if (parser->depth > 0) { + return JSMN_ERROR_PART; + } + + return (int)count; +} + +void +jsmn_init(jsmn_parser_t *parser) +{ + parser->pos = 0; + parser->depth = 0; + parser->parsing_container = false; +} + +#endif /* OC_JSON_ENCODER */ diff --git a/util/jsmn/jsmn_internal.h b/util/jsmn/jsmn_internal.h new file mode 100644 index 0000000000..786f897903 --- /dev/null +++ b/util/jsmn/jsmn_internal.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef JSMN_INTERNAL_H +#define JSMN_INTERNAL_H + +#ifdef OC_JSON_ENCODER + +#include "util/oc_compiler.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#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 << 0, + JSMN_ARRAY = 1 << 2, + JSMN_STRING = 1 << 3, + JSMN_PRIMITIVE = 1 << 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; +} 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 pos; /* offset in the JSON string */ + unsigned depth; /* nesting depth for arrays/objects */ + bool parsing_container; /* true if we are parsing a container */ +} jsmn_parser_t; + +/** Create JSON parser over an array of tokens */ +void jsmn_init(jsmn_parser_t *parser) OC_NONNULL(); + +typedef bool (*jsmn_parsed_token_cb_t)(const jsmntok_t *token, const char *js, + void *data); +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing a single JSON object. + */ +int jsmn_parse(jsmn_parser_t *parser, const char *js, const size_t len, + jsmn_parsed_token_cb_t cb, void *data) OC_NONNULL(1); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_JSON_ENCODER */ + +#endif /* JSMN_INTERNAL_H */ diff --git a/util/oc_secure_string_internal.h b/util/oc_secure_string_internal.h index cee084eebd..fa04c2dc45 100644 --- a/util/oc_secure_string_internal.h +++ b/util/oc_secure_string_internal.h @@ -27,6 +27,12 @@ extern "C" { #endif +/** Maximal allowed length (including null-terminator) of C-strings, strings + * with greater length are considered invalid. */ +#ifndef OC_MAX_STRING_LENGTH +#define OC_MAX_STRING_LENGTH (4096) +#endif /* !OC_MAX_STRING_LENGTH */ + /** * @brief Get the number of characters in the string, not including the * terminating null character. diff --git a/util/unittest/jsmntest.cpp b/util/unittest/jsmntest.cpp new file mode 100644 index 0000000000..a3c3aa05ec --- /dev/null +++ b/util/unittest/jsmntest.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgd.dev s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"), + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +#include "util/oc_features.h" + +#ifdef OC_JSON_ENCODER + +#include "util/jsmn/jsmn_internal.h" + +#include +#include +#include +#include + +TEST(TestJsmn, ParseEmpty) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + EXPECT_EQ(0, jsmn_parse(&parser, nullptr, 0, nullptr, nullptr)); + + jsmn_init(&parser); + EXPECT_EQ(0, jsmn_parse(&parser, "", 0, nullptr, nullptr)); + + std::vector empty(2, '\0'); + jsmn_init(&parser); + EXPECT_EQ(0, + jsmn_parse(&parser, empty.data(), empty.size(), nullptr, nullptr)); +} + +TEST(TestJsmn, ParseInteger) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + std::string json = "1337"; + std::vector tokens{}; + ASSERT_EQ(1, jsmn_parse( + &parser, json.c_str(), json.length(), + [](const jsmntok_t *token, const char *, void *data) { + auto &tokens = *static_cast *>(data); + tokens.push_back(*token); + return true; + }, + &tokens)); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_PRIMITIVE, tokens[0].type); +} + +TEST(TestJsmn, ParseIntegerNoTokens) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + std::string json = "1337"; + EXPECT_EQ(1, + jsmn_parse(&parser, json.c_str(), json.length(), nullptr, nullptr)); +} + +static void +parseTokens(const std::string &json, std::vector &tokens) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + ASSERT_LT(0, jsmn_parse( + &parser, json.c_str(), json.length(), + [](const jsmntok_t *token, const char *, void *data) { + auto &tokens = *static_cast *>(data); + tokens.push_back(*token); + return true; + }, + &tokens)) + << "parsing of " << json << " should succeed"; +} + +static void +parseString(const std::string &str) +{ + std::string json = "\"" + str + "\""; + std::vector tokens{}; + parseTokens(json, tokens); + ASSERT_EQ(1, tokens.size()) + << "parsing of " << json << " should result in 1 token"; + ASSERT_EQ(JSMN_STRING, tokens[0].type) + << "parsing of " << json << " should result in a string token"; + std::string token_str(json.c_str() + tokens[0].start, + tokens[0].end - tokens[0].start); + EXPECT_STREQ(str.c_str(), token_str.c_str()) + << "parsing of " << json << " should resulted in " << token_str; +} + +// Parse string +TEST(TestJsmn, ParseString) +{ + parseString(""); + parseString("Hello World!"); + + parseString(R"(\"\/\\\b\f\r\n\t)"); + + parseString(R"(\u0123)"); + parseString(R"(\u4567)"); + parseString(R"(\u89ab)"); + parseString(R"(\ucdef)"); + parseString(R"(\uABCD)"); + parseString(R"(\uEF01)"); +} + +TEST(TestJsmn, ParseStringNoTokens) +{ + std::string json = R"("Hello World!")"; + jsmn_parser_t parser; + jsmn_init(&parser); + EXPECT_EQ(1, + jsmn_parse(&parser, json.c_str(), json.length(), nullptr, nullptr)); +} + +static void +parseJsonFail(const std::string &json) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + ASSERT_GT(0, + jsmn_parse(&parser, json.c_str(), json.length(), nullptr, nullptr)) + << "parsing of " << json << " should fail"; +} + +static void +parseVectorFail(const std::vector &json) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + std::string json_printable(json.data(), json.size()); + ASSERT_GT(0, jsmn_parse(&parser, json.data(), json.size(), nullptr, nullptr)) + << "parsing of " << json_printable << " should fail"; +} + +TEST(TestJsmn, ParseString_Fail) +{ + // missing ending quote + parseJsonFail(R"(")"); + parseJsonFail(R"("\)"); + // escaped ending quote + parseJsonFail(R"("\")"); + // invalid escape sequence + parseJsonFail(R"("\l")"); + + // invalid unicode escape sequence + parseJsonFail(R"("\u")"); + parseJsonFail(R"("\u0")"); + parseJsonFail(R"("\u01")"); + parseJsonFail(R"("\u012")"); + parseJsonFail(R"("\u012g")"); + + // invalid strings + parseVectorFail({ '"', '\0', '\0', '\0', '\0', '\0' }); + parseVectorFail({ '"', '\\', 'u' }); + parseVectorFail({ '"', '\\', 'u', '\0' }); + + parseJsonFail("[}"); + parseJsonFail("{]"); +} + +static void +parseCharFail(char c) +{ + jsmn_parser_t parser; + jsmn_init(&parser); + ASSERT_GT(0, jsmn_parse(&parser, &c, 1, nullptr, nullptr)) + << "parsing of " << (int)c << " should fail"; +} + +TEST(TestJsmn, ParseArray) +{ + std::vector tokens{}; + parseTokens("[]", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_ARRAY, tokens[0].type); + + tokens = {}; + parseTokens("[[]]", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_ARRAY, tokens[0].type); + + tokens = {}; + parseTokens("[true, false, false]", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_ARRAY, tokens[0].type); + + tokens = {}; + parseTokens("[1,2,3]", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_ARRAY, tokens[0].type); + + tokens = {}; + parseTokens(R"(["a", "b", "c"])", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_ARRAY, tokens[0].type); +} + +TEST(TestJsmn, ParseArrayNoTokens) +{ + std::string json = "[123, 456, 789]"; + jsmn_parser_t parser; + jsmn_init(&parser); + EXPECT_LT(0, + jsmn_parse(&parser, json.c_str(), json.length(), nullptr, nullptr)); +} + +TEST(TestJsmn, ParseArrayFail) +{ + parseJsonFail("["); + parseJsonFail("]"); +} + +TEST(TestJsmn, ParseObject) +{ + std::vector tokens{}; + parseTokens("{}", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_OBJECT, tokens[0].type); + + tokens = {}; + parseTokens(R"({"k1": 123, "k2": "abc", "k3": [], "k4":{}})", tokens); + ASSERT_EQ(1, tokens.size()); + EXPECT_EQ(JSMN_OBJECT, tokens[0].type); +} + +TEST(TestJsmn, ParseObjectNoTokens) +{ + std::string json = R"({"k1": 123, "k2": "abc"})"; + jsmn_parser_t parser; + jsmn_init(&parser); + EXPECT_LT(0, + jsmn_parse(&parser, json.c_str(), json.length(), nullptr, nullptr)); +} + +TEST(TestJsmn, ParseObjectFail) +{ + parseJsonFail("{"); + parseJsonFail("}"); +} + +TEST(TestJsmn, ParseFail) +{ + for (int i = 1; i <= UCHAR_MAX; ++i) { + if (std::isprint(i) || isspace(i)) { + continue; + } + parseCharFail(static_cast(i)); + } + + auto parse_cb_fail = [](const jsmntok_t *, const char *, void *) { + return false; + }; + + jsmn_parser_t parser; + jsmn_init(&parser); + std::string json = "1"; + EXPECT_GT(0, jsmn_parse(&parser, json.c_str(), json.length(), parse_cb_fail, + nullptr)); + + jsmn_init(&parser); + json = R"(a multi word sentence)"; + EXPECT_GT(0, jsmn_parse(&parser, json.c_str(), json.length(), parse_cb_fail, + nullptr)); + + jsmn_init(&parser); + json = R"({"key": "value"})"; + EXPECT_GT(0, jsmn_parse(&parser, json.c_str(), json.length(), parse_cb_fail, + nullptr)); + + jsmn_init(&parser); + json = R"([1, 2, 3])"; + EXPECT_GT(0, jsmn_parse(&parser, json.c_str(), json.length(), parse_cb_fail, + nullptr)); +} + +#endif // OC_JSON_ENCODER