From 94687a14f0c5aa8da8db278291aff799397730c5 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Thu, 21 Mar 2024 19:15:00 +0100 Subject: [PATCH 1/2] util: extend oc_endpoint_addresses_t by custom on change callback --- .github/workflows/ctt-test.yml | 2 +- .../workflows/plgd-device-test-with-cfg.yml | 7 +- .github/workflows/plgd-hub-test-with-cfg.yml | 7 +- .github/workflows/sonar-cloud-analysis.yml | 17 +- .github/workflows/unit-test-with-cfg.yml | 7 +- api/cloud/oc_cloud.c | 118 +++++--- api/cloud/oc_cloud_context.c | 45 ++- api/cloud/oc_cloud_context_internal.h | 29 +- api/cloud/oc_cloud_endpoints.c | 79 +++++ api/cloud/oc_cloud_endpoints_internal.h | 14 + api/cloud/oc_cloud_internal.h | 16 +- api/cloud/oc_cloud_manager.c | 123 +++++--- api/cloud/oc_cloud_manager_internal.h | 24 ++ api/cloud/oc_cloud_rd.c | 4 +- api/cloud/oc_cloud_resource.c | 12 +- api/cloud/oc_cloud_store.c | 72 ++--- api/cloud/rd_client.c | 6 +- api/cloud/unittest/cloud_context_test.cpp | 38 +++ api/cloud/unittest/cloud_manager_test.cpp | 137 +++++++-- api/cloud/unittest/cloud_resource_test.cpp | 38 ++- api/cloud/unittest/cloud_test.cpp | 281 ++++++++++++++---- api/oc_rep.c | 7 +- api/oc_rep_internal.h | 4 - api/oc_ri_server.c | 2 +- api/oc_storage.c | 2 + api/oc_swupdate.c | 2 + api/plgd/plgd_time.c | 6 + include/oc_cloud.h | 4 +- include/oc_rep.h | 17 ++ messaging/coap/coap.c | 4 + messaging/coap/observe.c | 2 + messaging/coap/transactions.c | 2 + port/linux/ipadapter.c | 2 + port/windows/network_addresses.c | 2 + security/oc_acl.c | 4 + security/oc_certs.c | 2 + security/oc_pstat.c | 8 +- security/oc_tls.c | 16 + swig/swig_interfaces/oc_cloud.i | 5 + swig/swig_interfaces/oc_endpoint_address.i | 10 +- swig/swig_interfaces/oc_rep.i | 2 + util/oc_endpoint_address.c | 78 +++-- util/oc_endpoint_address_internal.h | 29 +- util/unittest/endpoint_address_test.cpp | 26 +- 44 files changed, 1014 insertions(+), 298 deletions(-) diff --git a/.github/workflows/ctt-test.yml b/.github/workflows/ctt-test.yml index 1b77ed8f8c..ace85547df 100644 --- a/.github/workflows/ctt-test.yml +++ b/.github/workflows/ctt-test.yml @@ -28,7 +28,7 @@ jobs: - name: Archive results if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logs path: results/*.octt diff --git a/.github/workflows/plgd-device-test-with-cfg.yml b/.github/workflows/plgd-device-test-with-cfg.yml index 3549a172d6..e433c812b0 100644 --- a/.github/workflows/plgd-device-test-with-cfg.yml +++ b/.github/workflows/plgd-device-test-with-cfg.yml @@ -109,12 +109,13 @@ jobs: docker run --rm --network=host -v `pwd`/${{ env.CERT_PATH }}:/pki_certs ${{ env.TEST_CLOUD_SERVER_IMAGE }} \ -test.parallel 1 -test.v - - name: Get output file name + - name: Generate file name and artifact name if: ${{ inputs.coverage }} id: coverage run: | SUFFIX=`echo "-DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ inputs.build_args }} ${{ inputs.name }} -DBUILD_TESTING=ON" | sha1sum | cut -f 1 -d ' '` echo "filename=coverage-plgd-device-${SUFFIX}.json" >> $GITHUB_OUTPUT + echo "artifact=plgd-device-${SUFFIX}-coverage" >> $GITHUB_OUTPUT - name: Gather coverage data if: ${{ inputs.coverage }} @@ -129,9 +130,9 @@ jobs: - name: Upload coverage data if: ${{ inputs.coverage }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: plgd-device-coverage + name: ${{ steps.coverage.outputs.artifact }} path: data/coverage/${{ steps.coverage.outputs.filename }} if-no-files-found: error retention-days: 1 diff --git a/.github/workflows/plgd-hub-test-with-cfg.yml b/.github/workflows/plgd-hub-test-with-cfg.yml index 4de85f19a8..e33881d812 100644 --- a/.github/workflows/plgd-hub-test-with-cfg.yml +++ b/.github/workflows/plgd-hub-test-with-cfg.yml @@ -103,12 +103,13 @@ jobs: - name: Run plgd hub tests image run: docker run --rm --network=host ${{ inputs.hub_args }} ${{ env.TEST_CLOUD_SERVER_IMAGE }} - - name: Get output file name + - name: Generate file name and artifact name if: ${{ inputs.coverage }} id: coverage run: | SUFFIX=`echo "-DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ inputs.build_args }} ${{ inputs.args }} ${{ inputs.docker_args }} ${{ inputs.hub_args }} ${{ inputs.name }} -DBUILD_TESTING=ON" | sha1sum | cut -f 1 -d ' '` echo "filename=coverage-plgd-hub-${SUFFIX}.json" >> $GITHUB_OUTPUT + echo "artifact=plgd-hub-${SUFFIX}-coverage" >> $GITHUB_OUTPUT - name: Gather coverage data if: ${{ inputs.coverage }} @@ -122,9 +123,9 @@ jobs: - name: Upload coverage data if: ${{ inputs.coverage }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: plgd-hub-coverage + name: ${{ steps.coverage.outputs.artifact }} path: data/coverage/${{ steps.coverage.outputs.filename }} if-no-files-found: error retention-days: 1 diff --git a/.github/workflows/sonar-cloud-analysis.yml b/.github/workflows/sonar-cloud-analysis.yml index 91e63c8168..f9578bb8f0 100644 --- a/.github/workflows/sonar-cloud-analysis.yml +++ b/.github/workflows/sonar-cloud-analysis.yml @@ -27,16 +27,16 @@ jobs: include: # cloud (ipv4+tcp) on, collection create on, push on, rfotm on - 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 + # security off, ipv4 on, collection create on, push 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, 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" + # ipv6 dns on, oscore off, rep realloc on, json encoder on, introspection IDD off + - build_args: "-DOC_DNS_LOOKUP_IPV6_ENABLED=ON -DOC_OSCORE_ENABLED=OFF -DOC_REPRESENTATION_REALLOC_ENCODING_ENABLED=ON -DOC_JSON_ENCODER_ENABLED=ON -DOC_IDD_API_ENABLED=OFF" # cloud (ipv4+tcp) on, dynamic allocation off, rfotm on, push off (because it forces dynamic allocation) - build_args: "-DOC_CLOUD_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), 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" + # security off, cloud (ipv4+tcp), dynamic allocation off, push off (because it forces dynamic allocation) + - build_args: "-DOC_SECURITY_ENABLED=OFF -DOC_CLOUD_ENABLED=ON -DOC_DYNAMIC_ALLOCATION_ENABLED=OFF -DOC_PUSH_ENABLED=OFF" uses: ./.github/workflows/unit-test-with-cfg.yml with: @@ -113,9 +113,10 @@ jobs: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build --verbose --target client-server-static --target all - name: Get coverage from all tests job - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - path: tools/ + merge-multiple: true + path: tools/coverage/ - name: Install gcovr run: | @@ -126,7 +127,7 @@ jobs: run: | cd tools # ls -lR . - gcovr --add-tracefile "unit-test-coverage/*coverage*.json" --add-tracefile "plgd-device-coverage/*coverage*.json" --add-tracefile "plgd-hub-coverage/*coverage*.json" --sonarqube --output "coverage.xml" --verbose + gcovr --add-tracefile "coverage/*coverage*.json" --sonarqube --output "coverage.xml" --verbose - name: Run sonar-scanner env: diff --git a/.github/workflows/unit-test-with-cfg.yml b/.github/workflows/unit-test-with-cfg.yml index 5197f9b770..0f2abbc42e 100644 --- a/.github/workflows/unit-test-with-cfg.yml +++ b/.github/workflows/unit-test-with-cfg.yml @@ -119,12 +119,13 @@ jobs: cd build ctest --verbose --label-regex "oc-unittest" - - name: Get output file name + - name: Generate file name and artifact name if: ${{ inputs.coverage }} id: coverage run: | SUFFIX=`echo "-DCMAKE_BUILD_TYPE=${{ inputs.build_type }} ${{ steps.cmake_flags.outputs.compiler }} ${{ inputs.build_args }} -DBUILD_TESTING=ON" | sha1sum | cut -f 1 -d ' '` echo "filename=coverage-unix-${SUFFIX}.json" >> $GITHUB_OUTPUT + echo "artifact=unit-test-${SUFFIX}-coverage" >> $GITHUB_OUTPUT - name: Collect coverage data if: ${{ inputs.coverage }} @@ -135,9 +136,9 @@ jobs: - name: Upload coverage data if: ${{ inputs.coverage }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: unit-test-coverage + name: ${{ steps.coverage.outputs.artifact }} path: tools/${{ steps.coverage.outputs.filename }} if-no-files-found: error retention-days: 1 diff --git a/api/cloud/oc_cloud.c b/api/cloud/oc_cloud.c index 4a84b5f322..04345e8ac5 100644 --- a/api/cloud/oc_cloud.c +++ b/api/cloud/oc_cloud.c @@ -78,6 +78,9 @@ start_manager(void *user_data) oc_free_endpoint(ctx->cloud_ep); ctx->cloud_ep = oc_new_endpoint(); ctx->store.status &= ~OC_CLOUD_LOGGED_IN; + ctx->store.cps = (ctx->store.status & OC_CLOUD_REGISTERED) != 0 + ? OC_CPS_REGISTERED + : OC_CPS_READYTOREGISTER; cloud_manager_start(ctx); cloud_manager_cb(ctx); return OC_EVENT_DONE; @@ -92,8 +95,7 @@ cloud_manager_restart(oc_cloud_context_t *ctx) } cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); - oc_remove_delayed_callback(ctx, start_manager); - oc_set_delayed_callback(ctx, start_manager, 0); + oc_reset_delayed_callback(ctx, start_manager, 0); } static oc_event_callback_retval_t @@ -146,6 +148,14 @@ oc_cloud_close_endpoint(const oc_endpoint_t *ep) } } +void +oc_cloud_reset_endpoint(oc_cloud_context_t *ctx) +{ + oc_cloud_close_endpoint(ctx->cloud_ep); + memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); + ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; +} + int cloud_reset(size_t device, bool force, bool sync, uint16_t timeout) { @@ -170,7 +180,7 @@ cloud_reset(size_t device, bool force, bool sync, uint16_t timeout) return 0; } -void +bool cloud_set_cloudconf(oc_cloud_context_t *ctx, const oc_cloud_conf_update_t *data) { assert(ctx != NULL); @@ -181,20 +191,17 @@ cloud_set_cloudconf(oc_cloud_context_t *ctx, const oc_cloud_conf_update_t *data) if (!oc_string_is_null_or_empty(data->auth_provider)) { oc_copy_string(&ctx->store.auth_provider, data->auth_provider); } - if (!oc_string_is_null_or_empty(data->ci_server)) { - // cannot call oc_endpoint_addresses_reinit, because the deinit might - // deallocate oc_string_t values and relocate memory, thus invalidating the - // oc_string_view - oc_endpoint_addresses_deinit(&ctx->store.ci_servers); - // oc_cloud_endpoint_addresses_init only allocates, so the oc_string_view_t - // is valid - oc_string_view_t cis = oc_string_view2(data->ci_server); - if (!oc_cloud_endpoint_addresses_init( - &ctx->store.ci_servers, ctx->store.ci_servers.on_selected_change, - ctx->store.ci_servers.on_selected_change_data, cis, data->sid)) { - OC_WRN("Failed to reinitialize cloud server endpoints"); - } + if (!oc_string_is_null_or_empty(data->ci_server) || + data->ci_servers != NULL) { + return oc_cloud_endpoint_addresses_set( + &ctx->store.ci_servers, data->ci_server, data->sid, + (oc_endpoint_addresses_rep_t){ + .uri_key = OC_STRING_VIEW(OC_ENDPOINT_ADDRESS_URI), + .uuid_key = OC_STRING_VIEW(OC_ENDPOINT_ADDRESS_ID), + .servers = data->ci_servers, + }); } + return true; } static oc_string_t @@ -225,7 +232,7 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, oc_uuid_t sid = OCF_COAPCLOUDCONF_DEFAULT_SID; if (server_id_len > 0 && oc_str_to_uuid_v1(server_id, server_id_len, &sid) != OC_UUID_ID_SIZE) { - OC_ERR("invalid sid(%s)", server_id); + OC_CLOUD_ERR("invalid sid(%s)", server_id); return -1; } size_t auth_provider_len = oc_strnlen_s(auth_provider, OC_MAX_STRING_LENGTH); @@ -233,10 +240,9 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, return -1; } - oc_cloud_close_endpoint(ctx->cloud_ep); - memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); - ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + oc_cloud_reset_endpoint(ctx); oc_cloud_store_reinitialize(&ctx->store); + oc_cloud_registration_context_deinit(&ctx->registration_ctx); cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); @@ -247,6 +253,7 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, .access_token = &at, .ci_server = &s, .sid = sid, + .ci_servers = NULL, }; const oc_string_t ap = @@ -255,7 +262,9 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, data.auth_provider = ≈ } - cloud_set_cloudconf(ctx, &data); + if (!cloud_set_cloudconf(ctx, &data)) { + OC_CLOUD_WRN("update of the cloud configuration encountered an error"); + } cloud_rd_reset_context(ctx); ctx->store.status = OC_CLOUD_INITIALIZED; @@ -264,6 +273,8 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, oc_cloud_store_dump_async(&ctx->store); if (ctx->cloud_manager) { + oc_cloud_registration_context_init(&ctx->registration_ctx, + &ctx->store.ci_servers); oc_cloud_manager_restart(ctx); } return 0; @@ -285,19 +296,22 @@ oc_cloud_update_by_resource(oc_cloud_context_t *ctx, // if deregistering or other cloud API was active then closing of the endpoint // triggers the handler with timeout error, which ensures that/ the operation // is interrupted - oc_cloud_close_endpoint(ctx->cloud_ep); - memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); - ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + oc_cloud_reset_endpoint(ctx); oc_cloud_store_reinitialize(&ctx->store); + oc_cloud_registration_context_deinit(&ctx->registration_ctx); cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); - cloud_set_cloudconf(ctx, data); + if (!cloud_set_cloudconf(ctx, data)) { + OC_CLOUD_WRN("update of the cloud configuration encountered an error"); + } cloud_rd_reset_context(ctx); ctx->store.status = OC_CLOUD_INITIALIZED; ctx->store.cps = OC_CPS_READYTOREGISTER; if (ctx->cloud_manager) { + oc_cloud_registration_context_init(&ctx->registration_ctx, + &ctx->store.ci_servers); oc_cloud_manager_restart(ctx); } } @@ -435,6 +449,8 @@ oc_cloud_manager_start(oc_cloud_context_t *ctx, oc_cloud_cb_t cb, void *data) cloud_manager_start(ctx); ctx->cloud_manager = true; + oc_cloud_registration_context_init(&ctx->registration_ctx, + &ctx->store.ci_servers); #ifdef OC_SESSION_EVENTS oc_remove_session_event_callback_v1(cloud_ep_session_event_handler, ctx, false); @@ -469,26 +485,58 @@ oc_cloud_manager_stop(oc_cloud_context_t *ctx) cloud_rd_reset_context(ctx); cloud_manager_stop(ctx); oc_cloud_store_reinitialize(&ctx->store); - oc_cloud_close_endpoint(ctx->cloud_ep); - memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); - ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + oc_cloud_reset_endpoint(ctx); + oc_cloud_registration_context_deinit(&ctx->registration_ctx); ctx->cloud_manager = false; return 0; } +void +cloud_deinit_devices(size_t devices) +{ + for (size_t device = 0; device < devices; ++device) { + cloud_context_deinit(oc_cloud_get_context(device)); + } +} + +static bool +cloud_init_device(size_t device) +{ + if (cloud_context_init(device) == NULL) { + return false; + } + + if (oc_cloud_add_resource(oc_core_get_resource_by_index(OCF_D, device)) != + 0 || + oc_cloud_add_resource(oc_core_get_resource_by_index(OCF_P, 0)) != 0) { + cloud_context_deinit(oc_cloud_get_context(device)); + return false; + } + return true; +} + +bool +cloud_init_devices(size_t devices) +{ + for (size_t device = 0; device < devices; ++device) { + if (!cloud_init_device(device)) { + cloud_deinit_devices(device); + return false; + } + } + return true; +} + bool oc_cloud_init(void) { if (!oc_ri_on_delete_resource_add_callback(oc_cloud_delete_resource)) { return false; } - for (size_t device = 0; device < oc_core_get_num_devices(); ++device) { - if (cloud_context_init(device) == NULL) { - return false; - } - oc_cloud_add_resource(oc_core_get_resource_by_index(OCF_P, 0)); - oc_cloud_add_resource(oc_core_get_resource_by_index(OCF_D, device)); + if (!cloud_init_devices(oc_core_get_num_devices())) { + oc_ri_on_delete_resource_remove_callback(oc_cloud_delete_resource); + return false; } return true; } @@ -508,7 +556,7 @@ oc_cloud_shutdown(void) false); #endif /* OC_SESSION_EVENTS */ cloud_context_deinit(ctx); - OC_CLOUD_DBG("cloud_shutdown for %d", (int)device); + OC_CLOUD_DBG("cloud_shutdown for %zu", device); } oc_ri_on_delete_resource_remove_callback(oc_cloud_delete_resource); } diff --git a/api/cloud/oc_cloud_context.c b/api/cloud/oc_cloud_context.c index 86dbd49b1a..ba5101ca04 100644 --- a/api/cloud/oc_cloud_context.c +++ b/api/cloud/oc_cloud_context.c @@ -60,10 +60,13 @@ need_to_reinitialize_cloud_storage(const oc_cloud_context_t *ctx) return cloud_is_deregistering(ctx); } -static void -cloud_on_server_change(void *data) +void +cloud_context_on_server_change(void *data) { - const oc_cloud_context_t *ctx = (oc_cloud_context_t *)data; + oc_cloud_context_t *ctx = (oc_cloud_context_t *)data; + if (ctx->cloud_manager) { + ctx->registration_ctx.server_changed = true; + } oc_cloud_store_dump_async(&ctx->store); } @@ -94,7 +97,7 @@ cloud_context_init(size_t device) ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; ctx->cloud_ep = oc_new_endpoint(); ctx->selected_identity_cred_id = -1; - oc_cloud_store_initialize(&ctx->store, cloud_on_server_change, ctx); + oc_cloud_store_initialize(&ctx->store, cloud_context_on_server_change, ctx); oc_cloud_store_load(&ctx->store); ctx->store.status &= ~(OC_CLOUD_LOGGED_IN | OC_CLOUD_TOKEN_EXPIRY | OC_CLOUD_REFRESHED_TOKEN | @@ -113,6 +116,10 @@ cloud_context_init(size_t device) void cloud_context_deinit(oc_cloud_context_t *ctx) { + if (ctx == NULL) { + return; + } + oc_cloud_registration_context_deinit(&ctx->registration_ctx); cloud_rd_deinit(ctx); // In the case of a factory reset, the cloud data may remain on the device // when the device is shut down during de-registration. @@ -233,10 +240,9 @@ oc_cloud_context_clear(oc_cloud_context_t *ctx, bool dump_async) { assert(ctx != NULL); + oc_cloud_registration_context_deinit(&ctx->registration_ctx); cloud_rd_reset_context(ctx); - oc_cloud_close_endpoint(ctx->cloud_ep); - memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); - ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + oc_cloud_reset_endpoint(ctx); cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); oc_cloud_store_reinitialize(&ctx->store); @@ -294,6 +300,31 @@ oc_cloud_get_identity_cert_chain(const oc_cloud_context_t *ctx) return ctx->selected_identity_cred_id; } +void +oc_cloud_registration_context_init(oc_cloud_registration_context_t *regctx, + const oc_endpoint_addresses_t *servers) +{ + oc_cloud_registration_context_deinit(regctx); + oc_copy_string(®ctx->initial_server, + oc_endpoint_addresses_selected_uri(servers)); + // limit the number of server changes to the number of servers at startup + // minus one (since the initial address is already used) + size_t server_count = oc_endpoint_addresses_size(servers); + assert(server_count <= UINT8_MAX); + regctx->remaining_server_changes = + (uint8_t)(oc_endpoint_addresses_selected(servers) != NULL ? server_count - 1 + : 0); + regctx->server_changed = false; +} + +void +oc_cloud_registration_context_deinit(oc_cloud_registration_context_t *regctx) +{ + oc_free_string(®ctx->initial_server); + regctx->remaining_server_changes = 0; + regctx->server_changed = false; +} + void oc_cloud_set_keepalive( oc_cloud_context_t *ctx, diff --git a/api/cloud/oc_cloud_context_internal.h b/api/cloud/oc_cloud_context_internal.h index 078a100495..0de854fbbc 100644 --- a/api/cloud/oc_cloud_context_internal.h +++ b/api/cloud/oc_cloud_context_internal.h @@ -62,6 +62,16 @@ typedef struct /** Reset the retry counters */ void cloud_retry_reset(oc_cloud_retry_t *retry) OC_NONNULL(1); +/** When retrying the registration step, all available servers should be used + * once and after that retrying should be stopped. */ +typedef struct +{ + oc_string_t initial_server; ///< server address when cloud manager is started; + uint8_t remaining_server_changes; ///< remaining number of server changes + ///< allowed before cloud manager is stopped + bool server_changed; +} oc_cloud_registration_context_t; + struct oc_cloud_context_t { struct oc_cloud_context_t *next; @@ -83,6 +93,8 @@ struct oc_cloud_context_t oc_link_t *rd_published_resources; /**< Resource links already published */ oc_link_t *rd_delete_resources; /**< Resource links to delete */ + oc_cloud_registration_context_t registration_ctx; /**< Registration context */ + int selected_identity_cred_id; /**< Selected identity cert chain. -1(default) means any*/ oc_cloud_error_t last_error; @@ -104,9 +116,9 @@ oc_cloud_context_t *cloud_context_init(size_t device); /** * @brief Deinitialize and deallocate cloud context * - * @param ctx context to deinitialize (cannot be NULL) + * @param ctx context to deinitialize */ -void cloud_context_deinit(oc_cloud_context_t *ctx) OC_NONNULL(); +void cloud_context_deinit(oc_cloud_context_t *ctx); /// @brief Count number of allocated contexts size_t cloud_context_size(void); @@ -159,6 +171,19 @@ void cloud_context_clear_access_token(oc_cloud_context_t *ctx) OC_NONNULL(); bool cloud_context_has_refresh_token(const oc_cloud_context_t *ctx) OC_NONNULL(); +/** @brief Callback invoked by ctx::store::ci_servers when the selected cloud + * server is changed */ +void cloud_context_on_server_change(void *data) OC_NONNULL(); + +/** @brief Initialize the registration context */ +void oc_cloud_registration_context_init(oc_cloud_registration_context_t *regctx, + const oc_endpoint_addresses_t *servers) + OC_NONNULL(); + +/** @brief Deinitialize the registration context */ +void oc_cloud_registration_context_deinit( + oc_cloud_registration_context_t *regctx) OC_NONNULL(); + #ifdef __cplusplus } #endif diff --git a/api/cloud/oc_cloud_endpoints.c b/api/cloud/oc_cloud_endpoints.c index 0aba4a5311..26e1b9c9c1 100644 --- a/api/cloud/oc_cloud_endpoints.c +++ b/api/cloud/oc_cloud_endpoints.c @@ -22,6 +22,8 @@ #ifdef OC_CLOUD #include "api/cloud/oc_cloud_endpoints_internal.h" +#include "api/cloud/oc_cloud_log_internal.h" +#include "api/cloud/oc_cloud_resource_internal.h" #include "util/oc_endpoint_address_internal.h" #include "util/oc_memb.h" @@ -45,4 +47,81 @@ oc_cloud_endpoint_addresses_init( oc_endpoint_address_make_view_with_uuid(default_uri, default_id)); } +bool +oc_cloud_endpoint_addresses_set(oc_endpoint_addresses_t *ea, + const oc_string_t *selected_uri, + oc_uuid_t selected_uuid, + oc_endpoint_addresses_rep_t srep) +{ + // cannot call oc_endpoint_addresses_reinit, because the deinit might + // deallocate oc_string_t values and relocate memory, thus invalidating the + // oc_string_view created from selected_uri + oc_endpoint_addresses_deinit(ea); + oc_string_view_t cis = oc_string_view2(selected_uri); + // oc_cloud_endpoint_addresses_init only allocates, so the oc_string_view_t + // will remain valid + if (!oc_cloud_endpoint_addresses_init(ea, ea->on_selected_change.cb, + ea->on_selected_change.cb_data, cis, + selected_uuid)) { + OC_CLOUD_WRN("Failed to reinitialize cloud server endpoints"); + return false; + } + +#if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START + char selected_id[OC_UUID_LEN] = { 0 }; + oc_uuid_to_str(&selected_uuid, selected_id, OC_UUID_LEN); + OC_CLOUD_DBG("reinitialized cloud endpoint addresses, selected cloud (uri: " + "%s, sid: %s)", + selected_uri != NULL ? oc_string(*selected_uri) : "NULL", + selected_id); + // GCOVR_EXCL_STOP +#endif /* OC_DBG_IS_ENABLED */ + if (srep.servers == NULL) { + return true; + } + + assert(srep.servers->type == OC_REP_OBJECT); + for (const oc_rep_t *server = srep.servers; server != NULL; + server = server->next) { + const oc_rep_t *rep = + oc_rep_get_by_type_and_key(server->value.object, OC_REP_STRING, + srep.uri_key.data, srep.uri_key.length); + if (rep == NULL) { + OC_CLOUD_ERR("cloud server uri missing"); + continue; + } + oc_string_view_t uri = oc_string_view2(&rep->value.string); + + rep = oc_rep_get_by_type_and_key(server->value.object, OC_REP_STRING, + srep.uuid_key.data, srep.uuid_key.length); + if (rep == NULL) { + OC_CLOUD_ERR("cloud server id missing"); + continue; + } + oc_string_view_t sid = oc_string_view2(&rep->value.string); + oc_uuid_t uuid; + if (oc_str_to_uuid_v1(sid.data, sid.length, &uuid) < 0) { + OC_CLOUD_ERR("invalid cloud sid(%s)", sid.data); + continue; + } + + if (oc_endpoint_addresses_contains(ea, uri)) { + OC_CLOUD_DBG("cloud endpoint address already exists (uri: %s)", uri.data); + continue; + } + + if (!oc_endpoint_addresses_add( + ea, oc_endpoint_address_make_view_with_uuid(uri, uuid))) { + OC_CLOUD_ERR("failed to add cloud endpoint address (uri: %s, sid: %s)", + uri.data, sid.data); + return false; + } + OC_CLOUD_DBG("added cloud endpoint address (uri: %s, sid: %s)", uri.data, + sid.data); + } + + return true; +} + #endif /* OC_CLOUD */ diff --git a/api/cloud/oc_cloud_endpoints_internal.h b/api/cloud/oc_cloud_endpoints_internal.h index c786f38a8c..36591597b7 100644 --- a/api/cloud/oc_cloud_endpoints_internal.h +++ b/api/cloud/oc_cloud_endpoints_internal.h @@ -20,6 +20,7 @@ #define OC_CLOUD_ENDPOINTS_INTERNAL_H #include "api/oc_helpers_internal.h" +#include "oc_rep.h" #include "oc_uuid.h" #include "util/oc_endpoint_address_internal.h" #include "util/oc_compiler.h" @@ -43,6 +44,19 @@ bool oc_cloud_endpoint_addresses_init( void *on_selected_change_data, oc_string_view_t default_uri, oc_uuid_t default_id) OC_NONNULL(1); +typedef struct +{ + oc_string_view_t uri_key; + oc_string_view_t uuid_key; + const oc_rep_t *servers; +} oc_endpoint_addresses_rep_t; + +/** Reinitialize cloud server endpoint addresses from a payload */ +bool oc_cloud_endpoint_addresses_set(oc_endpoint_addresses_t *ea, + const oc_string_t *selected_uri, + oc_uuid_t selected_uuid, + oc_endpoint_addresses_rep_t srep); + #ifdef __cplusplus } #endif diff --git a/api/cloud/oc_cloud_internal.h b/api/cloud/oc_cloud_internal.h index abbeecb375..26ce52bf04 100644 --- a/api/cloud/oc_cloud_internal.h +++ b/api/cloud/oc_cloud_internal.h @@ -24,6 +24,7 @@ #include "api/oc_helpers_internal.h" #include "oc_api.h" #include "oc_cloud.h" +#include "oc_rep.h" #include "oc_uuid.h" #include "util/oc_compiler.h" @@ -55,14 +56,24 @@ typedef struct const oc_string_t *ci_server; /**< Cloud Interface Server URL which an a enrollee is going to registered. */ oc_uuid_t sid; /**< OCF Cloud Identity as defined in OCF CNC 2.0 Spec. */ + const oc_rep_t *ci_servers; /**< List of Cloud Interface Servers. */ } oc_cloud_conf_update_t; +/** Initialize cloud data for devices */ +bool cloud_init_devices(size_t devices); + +/** Deinitialize cloud data for devices */ +void cloud_deinit_devices(size_t devices); + /** Set cloud endpoint from currently selected cloud server address */ bool oc_cloud_set_endpoint(oc_cloud_context_t *ctx) OC_NONNULL(); /** Close connection to currently selected cloud server address */ void oc_cloud_close_endpoint(const oc_endpoint_t *ep) OC_NONNULL(); +/** Close connection and set endpoint to initial state */ +void oc_cloud_reset_endpoint(oc_cloud_context_t *ctx) OC_NONNULL(); + /** Initialize cloud data for devices */ bool oc_cloud_init(void); @@ -104,8 +115,11 @@ int cloud_reset(size_t device, bool force, bool sync, uint16_t timeout); * * @param ctx cloud context (cannot be NULL) * @param data configuration update (cannot be NULL) + * + * @return true on success + * @return false on failure */ -void cloud_set_cloudconf(oc_cloud_context_t *ctx, +bool cloud_set_cloudconf(oc_cloud_context_t *ctx, const oc_cloud_conf_update_t *data) OC_NONNULL(); /** Update cloud based on update data */ diff --git a/api/cloud/oc_cloud_manager.c b/api/cloud/oc_cloud_manager.c index 09bf9b80af..0d77873ac5 100644 --- a/api/cloud/oc_cloud_manager.c +++ b/api/cloud/oc_cloud_manager.c @@ -256,28 +256,6 @@ cloud_start_process(oc_cloud_context_t *ctx) _oc_signal_event_loop(); } -static uint64_t -refresh_token_expires_in_ms(int64_t expires_in_ms) -{ - if (expires_in_ms <= 0) { - return 0; - } - - if (expires_in_ms > (int64_t)MILLISECONDS_PER_HOUR) { - // if time is more than 1h then set expires to (expires_in_ms - 10min). - return (uint64_t)(expires_in_ms - (int64_t)(10 * MILLISECONDS_PER_MINUTE)); - } - if (expires_in_ms > (int64_t)(4 * MILLISECONDS_PER_MINUTE)) { - // if time is more than 240sec then set expires to (expires_in_ms - 2min). - return (uint64_t)(expires_in_ms - (int64_t)(2 * MILLISECONDS_PER_MINUTE)); - } - if (expires_in_ms > (int64_t)(20 * MILLISECONDS_PER_SECOND)) { - // if time is more than 20sec then set expires to (expires_in_ms - 10sec). - return (uint64_t)(expires_in_ms - (int64_t)(10 * MILLISECONDS_PER_SECOND)); - } - return (uint64_t)expires_in_ms; -} - static bool cloud_is_connection_error_or_timeout(oc_status_t code) { @@ -302,7 +280,7 @@ manager_payload_get_string_property(const oc_rep_t *payload, oc_string_view_t key) { const oc_rep_t *rep = - oc_rep_get(payload, OC_REP_STRING, key.data, key.length); + oc_rep_get_by_type_and_key(payload, OC_REP_STRING, key.data, key.length); if (rep == NULL || oc_string_is_empty(&rep->value.string)) { return NULL; } @@ -356,8 +334,9 @@ cloud_manager_handle_redirect_response(oc_cloud_context_t *ctx, assert(ctx != NULL); assert(payload != NULL); - const oc_rep_t *redirect = oc_rep_get(payload, OC_REP_STRING, REDIRECTURI_KEY, - OC_CHAR_ARRAY_LEN(REDIRECTURI_KEY)); + const oc_rep_t *redirect = + oc_rep_get_by_type_and_key(payload, OC_REP_STRING, REDIRECTURI_KEY, + OC_CHAR_ARRAY_LEN(REDIRECTURI_KEY)); if (redirect == NULL) { return false; } @@ -387,7 +366,7 @@ cloud_manager_handle_redirect_response(oc_cloud_context_t *ctx, oc_endpoint_addresses_add(&ctx->store.ci_servers, oc_endpoint_address_make_view_with_uuid( oc_string_view2(redirecturi), sid)) == NULL) { - OC_ERR("failed to add server to the list"); + OC_CLOUD_ERR("failed to add server to the list"); return false; } @@ -401,9 +380,7 @@ cloud_manager_handle_redirect_response(oc_cloud_context_t *ctx, oc_string_view2(redirecturi)); if (ctx->cloud_ep != NULL) { - oc_cloud_close_endpoint(ctx->cloud_ep); - memset(ctx->cloud_ep, 0, sizeof(oc_endpoint_t)); - ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; + oc_cloud_reset_endpoint(ctx); } return true; } @@ -462,6 +439,27 @@ oc_cloud_register_handler(oc_client_response_t *data) ctx->store.status &= ~(OC_CLOUD_FAILURE | OC_CLOUD_TOKEN_EXPIRY); } +bool +cloud_manager_register_check_retry_with_changed_server( + const oc_cloud_context_t *ctx) +{ + if (ctx->registration_ctx.remaining_server_changes == 0) { + OC_CLOUD_DBG("No more servers to try"); + return false; + } + + if (!ctx->registration_ctx.server_changed) { + OC_CLOUD_DBG("Server has not changed, retrying skipped"); + return false; + } + + // if the initial server is not in the list of endpoints -> the list has been + // modified, so we stop retrying + return oc_endpoint_addresses_contains( + &ctx->store.ci_servers, + oc_string_view2(&ctx->registration_ctx.initial_server)); +} + static void cloud_manager_register_handler(oc_client_response_t *data) { @@ -490,12 +488,26 @@ cloud_manager_register_handler(oc_client_response_t *data) finish: oc_reset_delayed_callback(ctx, cloud_manager_callback_handler_async, 0); - if (retry && cloud_schedule_retry(ctx, OC_CLOUD_ACTION_REGISTER, - cloud_manager_register_async)) { - OC_CLOUD_DBG("Registration failed with error(%d), retrying", (int)err); - // While retrying, keep last error (clec) to CLOUD_OK - cloud_set_cps_and_last_error(ctx, OC_CPS_REGISTERING, CLOUD_OK); - return; + if (retry) { + if (cloud_schedule_retry(ctx, OC_CLOUD_ACTION_REGISTER, + cloud_manager_register_async)) { + OC_CLOUD_DBG("Registration failed with error(%d), retrying", (int)err); + } else if (cloud_manager_register_check_retry_with_changed_server(ctx)) { + --ctx->registration_ctx.remaining_server_changes; + ctx->registration_ctx.server_changed = false; + OC_CLOUD_DBG("Registration failed with error(%d), retrying by " + "reconnecting to another server (remaining server changes " + "attempts: %d)", + (int)err, ctx->registration_ctx.remaining_server_changes); + oc_reset_delayed_callback(ctx, cloud_manager_reconnect_async, 0); + } else { + retry = false; + } + if (retry) { + // While retrying, keep last error (clec) to CLOUD_OK + cloud_set_cps_and_last_error(ctx, OC_CPS_REGISTERING, CLOUD_OK); + return; + } } cloud_set_cps_and_last_error(ctx, cps, err); } @@ -663,6 +675,26 @@ on_keepalive_response(oc_cloud_context_t *ctx, bool response_received, return true; } +uint64_t +cloud_manager_calculate_refresh_token_expiration(uint64_t expires_in_ms) +{ + assert(expires_in_ms > 0); + + if (expires_in_ms > (uint64_t)MILLISECONDS_PER_HOUR) { + // if time is more than 1h then set expires to (expires_in_ms - 10min). + return expires_in_ms - (uint64_t)(10 * MILLISECONDS_PER_MINUTE); + } + if (expires_in_ms > (int64_t)(4 * MILLISECONDS_PER_MINUTE)) { + // if time is more than 240sec then set expires to (expires_in_ms - 2min). + return expires_in_ms - (uint64_t)(2 * MILLISECONDS_PER_MINUTE); + } + if (expires_in_ms > (int64_t)(20 * MILLISECONDS_PER_SECOND)) { + // if time is more than 20sec then set expires to (expires_in_ms - 10sec). + return expires_in_ms - (uint64_t)(10 * MILLISECONDS_PER_SECOND); + } + return expires_in_ms; +} + static void cloud_manager_login_handler(oc_client_response_t *data) { @@ -686,8 +718,8 @@ cloud_manager_login_handler(oc_client_response_t *data) if (ctx->store.expires_in > 0) { oc_reset_delayed_callback_ms( ctx, cloud_manager_refresh_token_async, - refresh_token_expires_in_ms(ctx->store.expires_in * - MILLISECONDS_PER_SECOND)); + cloud_manager_calculate_refresh_token_expiration( + (uint64_t)(ctx->store.expires_in * MILLISECONDS_PER_SECOND))); } oc_reset_delayed_callback(ctx, cloud_manager_callback_handler_async, 0); return; @@ -918,13 +950,15 @@ cloud_manager_refresh_token_handler(oc_client_response_t *data) return; } cloud_set_last_error(ctx, CLOUD_OK); - oc_reset_delayed_callback(ctx, cloud_manager_callback_handler_async, 0); - return; - } else if (err == CLOUD_ERROR_UNAUTHORIZED) { + goto finish; + } + if (err == CLOUD_ERROR_UNAUTHORIZED) { OC_CLOUD_ERR("Refreshing of access token failed with error(%d)", (int)err); cloud_set_cps_and_last_error(ctx, OC_CPS_FAILED, err); cloud_context_clear(ctx); - } else if (err == CLOUD_ERROR_CONNECT) { + goto finish; + } + if (err == CLOUD_ERROR_CONNECT) { if ((ctx->store.status & OC_CLOUD_REGISTERED) != 0) { if (!cloud_schedule_retry(ctx, OC_CLOUD_ACTION_REFRESH_TOKEN, cloud_manager_refresh_token_async)) { @@ -941,11 +975,12 @@ cloud_manager_refresh_token_handler(oc_client_response_t *data) (int)err); cloud_set_last_error(ctx, CLOUD_OK); } - } else { - OC_CLOUD_ERR("refreshing of access token failed with error(%d)", (int)err); - cloud_set_cps_and_last_error(ctx, OC_CPS_FAILED, err); + goto finish; } + OC_CLOUD_ERR("refreshing of access token failed with error(%d)", (int)err); + cloud_set_cps_and_last_error(ctx, OC_CPS_FAILED, err); +finish: oc_reset_delayed_callback(ctx, cloud_manager_callback_handler_async, 0); } diff --git a/api/cloud/oc_cloud_manager_internal.h b/api/cloud/oc_cloud_manager_internal.h index 3067080b28..50e31dee07 100644 --- a/api/cloud/oc_cloud_manager_internal.h +++ b/api/cloud/oc_cloud_manager_internal.h @@ -64,6 +64,19 @@ bool cloud_manager_handle_redirect_response(oc_cloud_context_t *ctx, const oc_rep_t *payload) OC_NONNULL(); +/** + * @brief Calculate the expiration time of the refresh token. + * + * The expiration time is used to determine when the refresh token should be + * refreshed. It should be less than the actual expiration time of the token, so + * it can be refreshed before it really expires. + * + * @param expires_in_ms expires_in value in milliseconds (must be >0) + * @return uint64_t expiration time in milliseconds + */ +uint64_t cloud_manager_calculate_refresh_token_expiration( + uint64_t expires_in_ms); + /** * @brief Parse refresh token response retrieved from the server and store the * data to cloud context. @@ -91,6 +104,17 @@ void cloud_manager_start(oc_cloud_context_t *ctx) OC_NONNULL(); */ void cloud_manager_stop(const oc_cloud_context_t *ctx) OC_NONNULL(); +/** + * @brief Check if retry of the cloud registration step with changed server + * should be attempted + * + * @param ctx cloud context (cannot be NULL) + * @return true retry should be attempted + * @return false otherwise + */ +bool cloud_manager_register_check_retry_with_changed_server( + const oc_cloud_context_t *ctx) OC_NONNULL(); + void oc_cloud_register_handler(oc_client_response_t *data) OC_NONNULL(); void oc_cloud_login_handler(oc_client_response_t *data) OC_NONNULL(); void oc_cloud_refresh_token_handler(oc_client_response_t *data) OC_NONNULL(); diff --git a/api/cloud/oc_cloud_rd.c b/api/cloud/oc_cloud_rd.c index 59ec46dc06..6df1bf6386 100644 --- a/api/cloud/oc_cloud_rd.c +++ b/api/cloud/oc_cloud_rd.c @@ -274,10 +274,12 @@ cloud_delete_resources(oc_cloud_context_t *ctx) } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START for (const oc_link_t *link = partition.not_deleted; link != NULL; link = link->next) { OC_CLOUD_DBG("link(ins=%" PRId64 ") not unpublished", link->ins); } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ ctx->rd_delete_resources = partition.not_deleted; rd_link_free(&partition.deleted); @@ -369,7 +371,7 @@ oc_cloud_publish_resources(size_t device) { oc_cloud_context_t *ctx = oc_cloud_get_context(device); if (ctx == NULL) { - OC_ERR("cannot publish resource: invalid device(%zu)", device); + OC_CLOUD_ERR("cannot publish resource: invalid device(%zu)", device); return -1; } publish_published_resources(ctx); diff --git a/api/cloud/oc_cloud_resource.c b/api/cloud/oc_cloud_resource.c index 5bcbb08457..5951772fee 100644 --- a/api/cloud/oc_cloud_resource.c +++ b/api/cloud/oc_cloud_resource.c @@ -126,7 +126,7 @@ resource_payload_get_string_property(const oc_rep_t *payload, oc_string_view_t key, bool cannotBeEmpty) { const oc_rep_t *rep = - oc_rep_get(payload, OC_REP_STRING, key.data, key.length); + oc_rep_get_by_type_and_key(payload, OC_REP_STRING, key.data, key.length); if (rep == NULL || (cannotBeEmpty && oc_string_is_empty(&rep->value.string))) { return NULL; @@ -157,11 +157,19 @@ cloud_update_from_request(oc_cloud_context_t *ctx, const oc_request_t *request) if (sid != NULL) { oc_string_view_t sidv = oc_string_view2(sid); if (oc_str_to_uuid_v1(sidv.data, sidv.length, &data.sid) < 0) { - OC_ERR("failed parsing sid(%s)", sidv.data); + OC_CLOUD_ERR("failed parsing sid(%s)", sidv.data); return false; } } + const oc_rep_t *ci_servers = oc_rep_get_by_type_and_key( + request->request_payload, OC_REP_OBJECT_ARRAY, + OCF_COAPCLOUDCONF_PROP_CISERVERS, + OC_CHAR_ARRAY_LEN(OCF_COAPCLOUDCONF_PROP_CISERVERS)); + if (ci_servers != NULL) { + data.ci_servers = ci_servers->value.object_array; + } + if (data.ci_server != NULL && (oc_string_is_empty(data.ci_server) || (data.access_token != NULL && sid != NULL))) { oc_cloud_update_by_resource(ctx, &data); diff --git a/api/cloud/oc_cloud_store.c b/api/cloud/oc_cloud_store.c index 81f0f4a8af..a0b1954d05 100644 --- a/api/cloud/oc_cloud_store.c +++ b/api/cloud/oc_cloud_store.c @@ -121,7 +121,7 @@ oc_cloud_store_dump(const oc_cloud_store_t *store) long ret = oc_storage_data_save(OC_CLOUD_STORE_NAME, store->device, store_encode_cloud, store); if (ret <= 0) { - OC_ERR("cannot dump cloud to storage: error(%ld)", ret); + OC_CLOUD_ERR("cannot dump cloud to storage: error(%ld)", ret); return false; } return true; @@ -245,58 +245,24 @@ cloud_store_set_servers(oc_cloud_store_t *store, const oc_string_t *selected_uri, const oc_string_t *selected_id, const oc_rep_t *servers) { - oc_uuid_t uuid = OCF_COAPCLOUDCONF_DEFAULT_SID; + + oc_uuid_t selected_uuid = OCF_COAPCLOUDCONF_DEFAULT_SID; if (selected_id != NULL) { oc_string_view_t selected_idv = oc_string_view2(selected_id); - if (oc_str_to_uuid_v1(selected_idv.data, selected_idv.length, &uuid) != - OC_UUID_ID_SIZE) { - return false; - } - } - if (!oc_endpoint_addresses_reinit(&store->ci_servers, - oc_endpoint_address_make_view_with_uuid( - oc_string_view2(selected_uri), uuid))) { - return false; - } - - if (servers == NULL) { - return true; - } - - for (const oc_rep_t *server = servers; server != NULL; - server = server->next) { - const oc_rep_t *rep = - oc_rep_get(server->value.object, OC_REP_STRING, CLOUD_ENDPOINT_URI, - OC_CHAR_ARRAY_LEN(CLOUD_ENDPOINT_URI)); - if (rep == NULL) { - OC_ERR("cloud server uri missing"); - continue; - } - oc_string_view_t uri = oc_string_view2(&rep->value.string); - - rep = oc_rep_get(server->value.object, OC_REP_STRING, CLOUD_ENDPOINT_ID, - OC_CHAR_ARRAY_LEN(CLOUD_ENDPOINT_ID)); - if (rep == NULL) { - OC_ERR("cloud server id missing"); - continue; - } - oc_string_view_t sid = oc_string_view2(&rep->value.string); - if (oc_str_to_uuid_v1(sid.data, sid.length, &uuid) < 0) { - continue; - } - - if (oc_endpoint_addresses_contains(&store->ci_servers, uri)) { - continue; - } - - if (!oc_endpoint_addresses_add( - &store->ci_servers, - oc_endpoint_address_make_view_with_uuid(uri, uuid))) { + if (oc_str_to_uuid_v1(selected_idv.data, selected_idv.length, + &selected_uuid) != OC_UUID_ID_SIZE) { + OC_CLOUD_ERR("invalid selected cloud sid"); return false; } } - return true; + return oc_cloud_endpoint_addresses_set( + &store->ci_servers, selected_uri, selected_uuid, + (oc_endpoint_addresses_rep_t){ + .uri_key = OC_STRING_VIEW(CLOUD_ENDPOINT_URI), + .uuid_key = OC_STRING_VIEW(CLOUD_ENDPOINT_ID), + .servers = servers, + }); } bool @@ -338,7 +304,7 @@ oc_cloud_store_decode(const oc_rep_t *rep, oc_cloud_store_t *store) // copy data to store if ((csd.ci_server != NULL || csd.ci_servers != NULL) && !cloud_store_set_servers(store, csd.ci_server, csd.sid, csd.ci_servers)) { - OC_WRN("failed to set cloud servers from storage"); + OC_CLOUD_WRN("failed to set cloud servers from storage"); } if (csd.auth_provider != NULL) { @@ -366,7 +332,7 @@ store_decode_cloud(const oc_rep_t *rep, size_t device, void *data) (void)device; oc_cloud_store_t *store = (oc_cloud_store_t *)data; if (!oc_cloud_store_decode(rep, store)) { - OC_ERR("cannot load cloud: cannot decode representation"); + OC_CLOUD_ERR("cannot load cloud: cannot decode representation"); return -1; } return 0; @@ -377,19 +343,19 @@ oc_cloud_store_load(oc_cloud_store_t *store) { if (oc_storage_data_load(OC_CLOUD_STORE_NAME, store->device, store_decode_cloud, store) <= 0) { - OC_DBG("failed to load cloud from storage"); + OC_CLOUD_DBG("failed to load cloud from storage"); oc_cloud_store_reinitialize(store); return false; } - OC_DBG("cloud loaded from storage"); + OC_CLOUD_DBG("cloud loaded from storage"); return true; } void oc_cloud_store_reinitialize(oc_cloud_store_t *store) { - oc_cloud_store_initialize(store, store->ci_servers.on_selected_change, - store->ci_servers.on_selected_change_data); + oc_cloud_store_initialize(store, store->ci_servers.on_selected_change.cb, + store->ci_servers.on_selected_change.cb_data); } void diff --git a/api/cloud/rd_client.c b/api/cloud/rd_client.c index 2ffc00aa9a..46696b5fe0 100644 --- a/api/cloud/rd_client.c +++ b/api/cloud/rd_client.c @@ -158,7 +158,7 @@ rd_prepare_write_buffer(oc_write_buffer_t *wb, char *buffer, size_t buffer_size, // enough room to write the "di={id}" part if (buffer_size <= /*di=*/3 + id.length) { - OC_ERR("buffer too small"); + OC_CLOUD_ERR("buffer too small"); return false; } wb->buffer = buffer; @@ -212,10 +212,10 @@ rd_delete_send_packet(const oc_endpoint_t *endpoint, oc_string_view_t query, void *data) { rd_delete_packet_t *pkt = (rd_delete_packet_t *)data; - OC_DBG("Unpublishing links (query=%s)", query.data); + OC_CLOUD_DBG("Unpublishing links (query=%s)", query.data); if (!oc_do_delete(OC_RSRVD_RD_URI, endpoint, query.data, pkt->handler, pkt->qos, pkt->user_data)) { - OC_ERR("failed to unpublish links (query=%s)", query.data); + OC_CLOUD_ERR("failed to unpublish links (query=%s)", query.data); return false; } return true; diff --git a/api/cloud/unittest/cloud_context_test.cpp b/api/cloud/unittest/cloud_context_test.cpp index cb573ed538..931f0ebc5e 100644 --- a/api/cloud/unittest/cloud_context_test.cpp +++ b/api/cloud/unittest/cloud_context_test.cpp @@ -20,8 +20,10 @@ #include "api/oc_helpers_internal.h" #include "api/oc_runtime_internal.h" #include "oc_uuid.h" +#include "util/oc_features.h" #include +#include class TestCloudContext : public testing::Test { public: @@ -30,6 +32,42 @@ class TestCloudContext : public testing::Test { static void TearDownTestCase() { oc_runtime_shutdown(); } }; +#ifndef OC_SECURITY + +// insecure build to avoid pstat checks, which need valid device indexes +TEST_F(TestCloudContext, Init) +{ +#ifdef OC_DYNAMIC_ALLOCATION + size_t num_devices = 3; +#else /* !OC_DYNAMIC_ALLOCATION */ + size_t num_devices = OC_MAX_NUM_DEVICES; +#endif /* OC_DYNAMIC_ALLOCATION */ + + std::vector contexts{}; + for (size_t i = 0; i < num_devices; ++i) { + oc_cloud_context_t *ctx = cloud_context_init(i); + ASSERT_NE(nullptr, ctx); + EXPECT_EQ(i, oc_cloud_get_device(ctx)); + contexts.push_back(ctx); + } + +#ifndef OC_DYNAMIC_ALLOCATION + oc_cloud_context_t *ctx = cloud_context_init(num_devices); + ASSERT_EQ(nullptr, ctx); +#endif /* !OC_DYNAMIC_ALLOCATION */ + + for (auto ctx : contexts) { + cloud_context_deinit(ctx); + } +} + +#endif /* !OC_SECURITY */ + +TEST_F(TestCloudContext, Deinit) +{ + cloud_context_deinit(nullptr); +} + TEST_F(TestCloudContext, OnStatusChange) { oc_cloud_context_t ctx{}; diff --git a/api/cloud/unittest/cloud_manager_test.cpp b/api/cloud/unittest/cloud_manager_test.cpp index dae83ebbc8..2924f0386e 100644 --- a/api/cloud/unittest/cloud_manager_test.cpp +++ b/api/cloud/unittest/cloud_manager_test.cpp @@ -45,6 +45,7 @@ using namespace std::chrono_literals; // cannot use lower value than 1s because oc_cloud_schedule_action_t::timeout is // in seconds static constexpr auto kTimeout = 1s; +static constexpr auto kTestServer = OC_STRING_VIEW("coap://224.0.1.187:5683"); class TestCloudManager : public testing::Test { public: @@ -66,15 +67,16 @@ class TestCloudManager : public testing::Test { memset(&m_context, 0, sizeof(m_context)); m_context.cloud_ep = oc_new_endpoint(); memset(m_context.cloud_ep, 0, sizeof(oc_endpoint_t)); - oc_cloud_store_initialize(&m_context.store, nullptr, nullptr); - oc_string_view_t ep = OC_STRING_VIEW("coap://224.0.1.187:5683"); + oc_cloud_store_initialize(&m_context.store, cloud_context_on_server_change, + &m_context); oc_uuid_t sid; oc_gen_uuid(&sid); - ASSERT_NE(nullptr, oc_endpoint_addresses_add( - &m_context.store.ci_servers, - oc_endpoint_address_make_view_with_uuid(ep, sid))); - ASSERT_TRUE( - oc_endpoint_addresses_select_by_uri(&m_context.store.ci_servers, ep)); + ASSERT_NE(nullptr, + oc_endpoint_addresses_add( + &m_context.store.ci_servers, + oc_endpoint_address_make_view_with_uuid(kTestServer, sid))); + ASSERT_TRUE(oc_endpoint_addresses_select_by_uri(&m_context.store.ci_servers, + kTestServer)); std::string uid = "501"; oc_set_string(&m_context.store.uid, uid.c_str(), uid.length()); std::string token = "access_token"; @@ -86,6 +88,7 @@ class TestCloudManager : public testing::Test { void TearDown() override { + oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); oc_cloud_manager_set_retry_timeouts(nullptr, 0); oc_free_endpoint(m_context.cloud_ep); @@ -106,6 +109,16 @@ class TestCloudManager : public testing::Test { } }; +TEST_F(TestCloudManager, oc_cloud_manager_start_fail) +{ + EXPECT_EQ(-1, oc_cloud_manager_start(nullptr, nullptr, nullptr)); +} + +TEST_F(TestCloudManager, oc_cloud_manager_stop_fail) +{ + EXPECT_EQ(-1, oc_cloud_manager_stop(nullptr)); +} + TEST_F(TestCloudManager, oc_cloud_manager_is_started) { EXPECT_FALSE(oc_cloud_manager_is_started(&m_context)); @@ -132,8 +145,6 @@ TEST_F(TestCloudManager, cloud_manager_start_initialized_schedule_turnoff) EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_ERROR_CONNECT, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED, m_context.store.status); - - oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); } TEST_F(TestCloudManager, @@ -169,8 +180,6 @@ TEST_F(TestCloudManager, EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_ERROR_CONNECT, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED, m_context.store.status); - - oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); } TEST_F(TestCloudManager, cloud_manager_start_initialized_without_retry_f) @@ -203,8 +212,6 @@ TEST_F(TestCloudManager, cloud_manager_start_initialized_without_retry_f) EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_ERROR_CONNECT, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED, m_context.store.status); - - oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); } TEST_F(TestCloudManager, cloud_manager_start_initialized_f) @@ -257,8 +264,6 @@ TEST_F(TestCloudManager, cloud_manager_start_registered_without_retry_and_uid_f) EXPECT_EQ(CLOUD_ERROR_CONNECT, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED | OC_CLOUD_FAILURE, m_context.store.status); - - oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); } TEST_F(TestCloudManager, cloud_manager_start_registered_f) @@ -311,8 +316,6 @@ TEST_F(TestCloudManager, EXPECT_EQ(CLOUD_ERROR_REFRESH_ACCESS_TOKEN, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED | OC_CLOUD_FAILURE, m_context.store.status); - - oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); } TEST_F(TestCloudManager, cloud_manager_start_with_refresh_token_f) @@ -349,20 +352,78 @@ TEST_F(TestCloudManager, cloud_manager_select_next_server_on_retry) ASSERT_NE(nullptr, ep); ASSERT_FALSE( oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, uri)); + // default cloud server (127.0.0.1), kTestServer and uri + ASSERT_EQ(3, oc_endpoint_addresses_size(&m_context.store.ci_servers)); - // When + ASSERT_TRUE(oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, + kTestServer)); m_context.store.status = OC_CLOUD_INITIALIZED; m_context.store.cps = OC_CPS_READYTOREGISTER; - cloud_manager_start(&m_context); + ASSERT_EQ(0, oc_cloud_manager_start(&m_context, nullptr, nullptr)); // by default: first retry should happen timeout + jitter // ([timeout/2..timeout]) (see default_schedule_action) - oc::TestDevice::PoolEventsMsV1( - /*timeout*/ kTimeout + /*max possible jitter*/ kTimeout, true); - cloud_manager_stop(&m_context); + auto interval = (/* 3 servers to try */ 3) * + (/*timeout*/ kTimeout + /*max possible jitter*/ kTimeout); + oc::TestDevice::PoolEventsMsV1(interval, true); + + // the retries should loop all servers and loop back to the original + EXPECT_TRUE(oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, + kTestServer)); + ASSERT_EQ(0, oc_cloud_manager_stop(&m_context)); +} + +TEST_F(TestCloudManager, cloud_manager_select_next_server_on_custom_retry) +{ + struct schedule_action_t + { + oc_cloud_context_t *ctx; + bool epChanged; + }; + schedule_action_t scheduleAction = { &m_context, false }; + oc_cloud_set_schedule_action( + &m_context, + [](oc_cloud_action_t action, uint8_t retry, uint64_t *delay, + uint16_t *timeout, void *data) -> bool { + if (action == OC_CLOUD_ACTION_REGISTER && retry == 0) { + *delay = 0; + *timeout = kTimeout.count(); + return true; + } + auto *sact = static_cast(data); + if (!sact->epChanged) { + oc_endpoint_addresses_select_next(&sact->ctx->store.ci_servers); + sact->epChanged = true; + } + return false; + }, + &scheduleAction); + + oc_string_view_t uri = OC_STRING_VIEW("coap://13.3.7.187:5683"); + oc_uuid_t sid; + oc_gen_uuid(&sid); + auto *ep = oc_endpoint_addresses_add( + &m_context.store.ci_servers, + oc_endpoint_address_make_view_with_uuid(uri, sid)); + ASSERT_NE(nullptr, ep); + ASSERT_FALSE( + oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, uri)); + ASSERT_EQ(3, oc_endpoint_addresses_size(&m_context.store.ci_servers)); + + ASSERT_TRUE(oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, + kTestServer)); + m_context.store.status = OC_CLOUD_INITIALIZED; + m_context.store.cps = OC_CPS_READYTOREGISTER; + ASSERT_EQ(0, oc_cloud_manager_start(&m_context, nullptr, nullptr)); + + // 2 servers should be tried -> after the first server is tried, the schedule + // action invokes oc_endpoint_addresses_select_next but only once; after that + // the retry should stop because the server selection was not changed + auto interval = (/* 2 servers to try */ 2) * (/*timeout*/ kTimeout); + oc::TestDevice::PoolEventsMsV1(interval, true); - // Then EXPECT_TRUE( oc_endpoint_addresses_is_selected(&m_context.store.ci_servers, uri)); + ASSERT_EQ(0, oc_cloud_manager_stop(&m_context)); } #endif /* !OC_SECURITY */ @@ -448,6 +509,36 @@ TestCloudManagerData::GetPayload(std::optional access_token, return rep; } +TEST_F(TestCloudManagerData, cloud_manager_calculate_refresh_token_expiration) +{ + // long internal (>1hour) -> refresh schedule 10mins before expiration + std::chrono::milliseconds expires_in = 2h; + auto expires_in_ms = + cloud_manager_calculate_refresh_token_expiration(expires_in.count()); + EXPECT_LT(0, expires_in_ms); + EXPECT_GT(expires_in.count(), expires_in_ms); + + // middle internal (>4mins) -> refresh schedule 2mins before expiration + expires_in = 5min; + expires_in_ms = + cloud_manager_calculate_refresh_token_expiration(expires_in.count()); + EXPECT_LT(0, expires_in_ms); + EXPECT_GT(expires_in.count(), expires_in_ms); + + // short internal (>20s) -> refresh schedule 10secs before expiration + expires_in = 1min; + expires_in_ms = + cloud_manager_calculate_refresh_token_expiration(expires_in.count()); + EXPECT_LT(0, expires_in_ms); + EXPECT_GT(expires_in.count(), expires_in_ms); + + // immediate expiration (<=20s) + expires_in = 10s; + expires_in_ms = + cloud_manager_calculate_refresh_token_expiration(expires_in.count()); + EXPECT_EQ(expires_in.count(), expires_in_ms); +} + TEST_F(TestCloudManagerData, cloud_manager_parse_register_data_invalid) { // { diff --git a/api/cloud/unittest/cloud_resource_test.cpp b/api/cloud/unittest/cloud_resource_test.cpp index 3c73686226..d59dfaa109 100644 --- a/api/cloud/unittest/cloud_resource_test.cpp +++ b/api/cloud/unittest/cloud_resource_test.cpp @@ -57,15 +57,15 @@ struct CloudResourceData std::vector result{}; for (const oc_rep_t *server = servers; server != nullptr; server = server->next) { - const oc_rep_t *rep = oc_rep_get(server->value.object, OC_REP_STRING, - "uri", OC_CHAR_ARRAY_LEN("uri")); + const oc_rep_t *rep = oc_rep_get_by_type_and_key( + server->value.object, OC_REP_STRING, "uri", OC_CHAR_ARRAY_LEN("uri")); if (rep == nullptr) { continue; } std::string uri = oc_string(rep->value.string); - rep = oc_rep_get(server->value.object, OC_REP_STRING, "id", - OC_CHAR_ARRAY_LEN("id")); + rep = oc_rep_get_by_type_and_key(server->value.object, OC_REP_STRING, + "id", OC_CHAR_ARRAY_LEN("id")); if (rep == nullptr) { continue; } @@ -379,6 +379,36 @@ TEST_F(TestCloudResourceWithServer, PostRequest_FailInvalidState) cloud_context_clear(ctx); } +TEST_F(TestCloudResourceWithServer, PostRequest_MultipleServers) +{ + auto encode = []() { + oc_rep_begin_root_object(); + oc_rep_set_text_string(root, cis, "coap://mock.plgd.dev"); + oc_rep_set_text_string(root, at, "access_token"); + oc_rep_set_text_string(root, sid, "00000000-0000-0000-0000-000000000000"); + std::string_view key{ "x.org.iotivity.servers" }; + oc_rep_encode_text_string(oc_rep_object(root), key.data(), key.length()); + oc_rep_begin_array(oc_rep_object(root), servers); + oc_rep_object_array_begin_item(servers); + oc_rep_set_text_string(servers, uri, "coaps://plgd.dev"); + oc_rep_set_text_string(servers, id, "00000000-0000-0000-0000-000000000000"); + oc_rep_object_array_end_item(servers); + oc_rep_end_array(oc_rep_object(root), servers); + oc_rep_end_root_object(); + }; + postRequest(encode); + + const auto *addresses = &oc_cloud_get_context(kDeviceID)->store.ci_servers; + ASSERT_EQ(2, oc_endpoint_addresses_size(addresses)); + EXPECT_TRUE(oc_endpoint_addresses_contains( + addresses, OC_STRING_VIEW("coap://mock.plgd.dev"))); + EXPECT_TRUE(oc_endpoint_addresses_contains( + addresses, OC_STRING_VIEW("coaps://plgd.dev"))); + // the server from the sid property should be selected + EXPECT_TRUE(oc_endpoint_addresses_is_selected( + addresses, OC_STRING_VIEW("coap://mock.plgd.dev"))); +} + TEST_F(TestCloudResourceWithServer, PostRequest_Deregister) { auto *ctx = oc_cloud_get_context(kDeviceID); diff --git a/api/cloud/unittest/cloud_test.cpp b/api/cloud/unittest/cloud_test.cpp index fa7482bed1..4500317b0b 100644 --- a/api/cloud/unittest/cloud_test.cpp +++ b/api/cloud/unittest/cloud_test.cpp @@ -22,26 +22,174 @@ #include "api/cloud/oc_cloud_endpoints_internal.h" #include "api/cloud/oc_cloud_internal.h" #include "api/cloud/oc_cloud_resource_internal.h" +#include "api/cloud/oc_cloud_schedule_internal.h" +#include "api/oc_core_res_internal.h" +#include "api/oc_ri_internal.h" +#include "api/oc_runtime_internal.h" #include "messaging/coap/conf.h" #include "oc_api.h" #include "oc_cloud.h" #include "oc_uuid.h" +#include "port/oc_network_event_handler_internal.h" #include "tests/gtest/Device.h" +#include "tests/gtest/RepPool.h" #include "util/oc_secure_string_internal.h" +#include "util/oc_features.h" +#include #include #include #include +#include #ifdef OC_SECURITY #include "security/oc_pstat_internal.h" -#endif +#include "security/oc_svr_internal.h" +#endif /* OC_SECURITY */ + +#ifdef OC_HAS_FEATURE_PUSH +#include "api/oc_push_internal.h" +#endif /* OC_HAS_FEATURE_PUSH */ using namespace std::chrono_literals; static constexpr size_t kDeviceID{ 0 }; class TestCloud : public testing::Test { +public: + void SetUp() override { oc_runtime_init(); } + + void TearDown() override { oc_runtime_shutdown(); } +}; + +TEST_F(TestCloud, cloud_is_connection_error_code) +{ + std::set connectionErrors{ + OC_STATUS_SERVICE_UNAVAILABLE, + OC_STATUS_GATEWAY_TIMEOUT, + OC_CONNECTION_CLOSED, + OC_TRANSACTION_TIMEOUT, + }; + for (int i = OC_STATUS_OK; i <= OC_CANCELLED; ++i) { + bool contains = connectionErrors.count(static_cast(i)) == 1; + EXPECT_EQ(contains, + cloud_is_connection_error_code(static_cast(i))); + } +} + +TEST_F(TestCloud, oc_cloud_set_retry_timeouts) +{ + std::vector tooManyTimeouts(OC_CLOUD_RETRY_TIMEOUTS_SIZE + 1, 0); + EXPECT_FALSE(oc_cloud_set_retry_timeouts(tooManyTimeouts.data(), + tooManyTimeouts.size())); + + std::vector invalidTimeouts{ 1, 42, /*invalid*/ 0, 13 }; + EXPECT_FALSE(oc_cloud_set_retry_timeouts(invalidTimeouts.data(), + invalidTimeouts.size())); + + // restore default values by using NULL + EXPECT_TRUE(oc_cloud_set_retry_timeouts(nullptr, 0)); +} + +TEST_F(TestCloud, oc_cloud_get_retry_timeouts) +{ + std::array timeouts{ 1, 42, 13 }; + ASSERT_TRUE(oc_cloud_set_retry_timeouts(timeouts.data(), timeouts.size())); + + std::array tooSmall{}; + EXPECT_EQ(-1, oc_cloud_get_retry_timeouts(tooSmall.data(), tooSmall.size())); + + std::vector result{}; + result.resize(timeouts.size()); + EXPECT_EQ(timeouts.size(), + oc_cloud_get_retry_timeouts(result.data(), result.size())); + for (size_t i = 0; i < timeouts.size(); ++i) { + EXPECT_EQ(timeouts[i], result[i]); + } + + EXPECT_TRUE(oc_cloud_set_retry_timeouts(nullptr, 0)); + result.resize(OC_CLOUD_RETRY_TIMEOUTS_SIZE); + EXPECT_EQ(OC_CLOUD_RETRY_TIMEOUTS_SIZE, + oc_cloud_get_retry_timeouts(result.data(), result.size())); +} + +TEST_F(TestCloud, oc_cloud_init_fail) +{ + ASSERT_TRUE(oc_cloud_init()); + // multiple initialization is not allowed + EXPECT_FALSE(oc_cloud_init()); + + oc_cloud_shutdown(); +} + +TEST_F(TestCloud, oc_cloud_shutdown_fail) +{ + oc_network_event_handler_mutex_init(); + oc_ri_init(); + oc_core_init(); + ASSERT_EQ(0, oc_init_platform("OCFTest", nullptr, nullptr)); + ASSERT_EQ( + 0, oc_add_device( + oc::DefaultDevice.uri.c_str(), oc::DefaultDevice.rt.c_str(), + oc::DefaultDevice.name.c_str(), oc::DefaultDevice.spec_version.c_str(), + oc::DefaultDevice.data_model_version.c_str(), nullptr, nullptr)); +#ifdef OC_SECURITY + oc_sec_svr_create(); +#endif /* OC_SECURITY */ + ASSERT_TRUE(oc_cloud_init()); + + oc_cloud_shutdown(); + // multiple shutdown is not allowed, but the app should not crash + oc_cloud_shutdown(); + +#ifdef OC_SECURITY + oc_sec_svr_free(); +#endif /* OC_SECURITY */ +#ifdef OC_HAS_FEATURE_PUSH + oc_push_free(); +#endif /* OC_HAS_FEATURE_PUSH */ + oc_connectivity_shutdown(/*device*/ 0); + oc_core_shutdown(); + oc_ri_shutdown(); + oc_network_event_handler_mutex_destroy(); +} + +#if !defined(OC_SECURITY) +// we need static allocation to ensure that allocation fails +// we need insecure build, because for secure build we need to have a valid +// pstat, which is not the case here +TEST_F(TestCloud, cloud_init_devices) +{ + EXPECT_FALSE(cloud_init_devices(1)); + + oc_ri_init(); + oc_core_init(); + // add device 0, but platform resource is missing + ASSERT_EQ( + 0, oc_add_device( + oc::DefaultDevice.uri.c_str(), oc::DefaultDevice.rt.c_str(), + oc::DefaultDevice.name.c_str(), oc::DefaultDevice.spec_version.c_str(), + oc::DefaultDevice.data_model_version.c_str(), nullptr, nullptr)); + EXPECT_FALSE(cloud_init_devices(1)); + + ASSERT_EQ(0, oc_init_platform("OCFTest", nullptr, nullptr)); + EXPECT_TRUE(cloud_init_devices(1)); + cloud_deinit_devices(1); + + EXPECT_FALSE(cloud_init_devices(2)); + + oc_cloud_shutdown(); +#ifdef OC_HAS_FEATURE_PUSH + oc_push_free(); +#endif /* OC_HAS_FEATURE_PUSH */ + oc_connectivity_shutdown(/*device*/ 0); + oc_core_shutdown(); + oc_ri_shutdown(); +} + +#endif /* !OC_SECURITY */ + +class TestCloudWithServer : public testing::Test { public: static void SetUpTestCase() { ASSERT_TRUE(oc::TestDevice::StartServer()); } @@ -56,13 +204,13 @@ class TestCloud : public testing::Test { } }; -TEST_F(TestCloud, oc_cloud_get_context) +TEST_F(TestCloudWithServer, oc_cloud_get_context) { EXPECT_NE(nullptr, oc_cloud_get_context(kDeviceID)); EXPECT_EQ(nullptr, oc_cloud_get_context(42)); } -TEST_F(TestCloud, set_published_resources_ttl) +TEST_F(TestCloudWithServer, set_published_resources_ttl) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -74,7 +222,7 @@ TEST_F(TestCloud, set_published_resources_ttl) oc_cloud_set_published_resources_ttl(ctx, default_ttl); } -TEST_F(TestCloud, cloud_status) +TEST_F(TestCloudWithServer, cloud_status) { oc_cloud_status_t status; memset(&status, 0, sizeof(status)); @@ -85,7 +233,7 @@ TEST_F(TestCloud, cloud_status) EXPECT_EQ(ctx->store.status, status); } -TEST_F(TestCloud, cloud_set_last_error) +TEST_F(TestCloudWithServer, cloud_set_last_error) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -94,13 +242,13 @@ TEST_F(TestCloud, cloud_set_last_error) ASSERT_EQ(CLOUD_ERROR_RESPONSE, ctx->last_error); } -TEST_F(TestCloud, oc_cloud_update_by_resource) +TEST_F(TestCloudWithServer, oc_cloud_update_by_resource) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); ctx->store.status = OC_CLOUD_FAILURE; - oc_cloud_conf_update_t data; + oc_cloud_conf_update_t data{}; auto access_token = OC_STRING_LOCAL("access_token"); data.access_token = &access_token; auto auth_provider = OC_STRING_LOCAL("auth_provider"); @@ -109,6 +257,43 @@ TEST_F(TestCloud, oc_cloud_update_by_resource) data.ci_server = &ci_server; auto sid = OC_STRING_LOCAL("12345678-1234-5678-1234-567812345678"); oc_str_to_uuid(oc_string(sid), &data.sid); + + oc::RepPool pool{}; + oc_rep_start_root_object(); + std::string key{ "x.org.iotivity.servers" }; + oc_rep_encode_text_string(oc_rep_object(root), key.c_str(), key.length()); + oc_rep_begin_array(oc_rep_object(root), servers); + oc_rep_object_array_begin_item(servers); + // missing uri -> item skipped + oc_rep_set_text_string(servers, id, "00000000-0000-0000-0000-000000000000"); + oc_rep_object_array_end_item(servers); + oc_rep_object_array_begin_item(servers); + // missing id -> item skipped + oc_rep_set_text_string(servers, uri, "coaps://plgd.dev"); + oc_rep_object_array_end_item(servers); + oc_rep_object_array_begin_item(servers); + // invalid id -> item skipped + oc_rep_set_text_string(servers, uri, "coaps://plgd.dev"); + oc_rep_set_text_string(servers, id, "invalid"); + oc_rep_object_array_end_item(servers); + // valid + oc_rep_object_array_begin_item(servers); + oc_rep_set_text_string(servers, uri, "coaps://plgd.dev"); + oc_rep_set_text_string(servers, id, "00000000-0000-0000-0000-000000000000"); + oc_rep_object_array_end_item(servers); + // duplicate -> item skipped + oc_rep_object_array_begin_item(servers); + oc_rep_set_text_string(servers, uri, "coaps://plgd.dev"); + oc_rep_set_text_string(servers, id, "00000000-0000-0000-0000-000000000000"); + oc_rep_object_array_end_item(servers); + oc_rep_end_array(oc_rep_object(root), servers); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + auto ci_servers_rep = pool.ParsePayload(); + auto *ci_servers = oc_rep_get_by_type_and_key( + ci_servers_rep.get(), OC_REP_OBJECT_ARRAY, key.c_str(), key.length()); + ASSERT_NE(nullptr, ci_servers); + data.ci_servers = ci_servers->value.object_array; oc_cloud_update_by_resource(ctx, &data); EXPECT_STREQ(oc_string(*data.access_token), @@ -122,7 +307,7 @@ TEST_F(TestCloud, oc_cloud_update_by_resource) EXPECT_EQ(OC_CLOUD_INITIALIZED, ctx->store.status); } -TEST_F(TestCloud, oc_cloud_provision_conf_resource) +TEST_F(TestCloudWithServer, oc_cloud_provision_conf_resource) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -178,7 +363,7 @@ TEST_F(TestCloud, oc_cloud_provision_conf_resource) EXPECT_EQ(OC_CLOUD_INITIALIZED, ctx->store.status); } -TEST_F(TestCloud, oc_cloud_action_to_str) +TEST_F(TestCloudWithServer, oc_cloud_action_to_str) { std::string v; v.assign(oc_cloud_action_to_str(OC_CLOUD_ACTION_REGISTER)); @@ -215,7 +400,7 @@ provisionCloud(oc_cloud_context_t *ctx, const std::string &uid = {}) } } -TEST_F(TestCloud, oc_cloud_register_already_registered) +TEST_F(TestCloudWithServer, oc_cloud_register_already_registered) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -233,7 +418,7 @@ TEST_F(TestCloud, oc_cloud_register_already_registered) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_register_fail_invalid_input) +TEST_F(TestCloudWithServer, oc_cloud_register_fail_invalid_input) { EXPECT_EQ(-1, oc_cloud_register(nullptr, nullptr, nullptr)); @@ -242,7 +427,7 @@ TEST_F(TestCloud, oc_cloud_register_fail_invalid_input) ASSERT_EQ(-1, oc_cloud_register(ctx, nullptr, nullptr)); } -TEST_F(TestCloud, oc_cloud_register_fail_invalid_status) +TEST_F(TestCloudWithServer, oc_cloud_register_fail_invalid_status) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -257,7 +442,7 @@ TEST_F(TestCloud, oc_cloud_register_fail_invalid_status) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_register_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_register_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -271,7 +456,7 @@ TEST_F(TestCloud, oc_cloud_register_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_register) +TEST_F(TestCloudWithServer, oc_cloud_do_register) { setRFNOP(); @@ -294,7 +479,7 @@ TEST_F(TestCloud, oc_cloud_do_register) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_login_already_logged_in) +TEST_F(TestCloudWithServer, oc_cloud_login_already_logged_in) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -312,7 +497,7 @@ TEST_F(TestCloud, oc_cloud_login_already_logged_in) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_login_fail_invalid_input) +TEST_F(TestCloudWithServer, oc_cloud_login_fail_invalid_input) { EXPECT_EQ(-1, oc_cloud_login(nullptr, nullptr, nullptr)); @@ -321,7 +506,7 @@ TEST_F(TestCloud, oc_cloud_login_fail_invalid_input) ASSERT_EQ(-1, oc_cloud_login(ctx, nullptr, nullptr)); } -TEST_F(TestCloud, oc_cloud_login_fail_invalid_status) +TEST_F(TestCloudWithServer, oc_cloud_login_fail_invalid_status) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -335,7 +520,7 @@ TEST_F(TestCloud, oc_cloud_login_fail_invalid_status) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_login_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_login_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -350,7 +535,7 @@ TEST_F(TestCloud, oc_cloud_login_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_login) +TEST_F(TestCloudWithServer, oc_cloud_do_login) { setRFNOP(); @@ -375,7 +560,7 @@ TEST_F(TestCloud, oc_cloud_do_login) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_input) +TEST_F(TestCloudWithServer, oc_cloud_refresh_token_fail_invalid_input) { EXPECT_EQ(-1, oc_cloud_refresh_token(nullptr, nullptr, nullptr)); @@ -384,7 +569,7 @@ TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_input) ASSERT_EQ(-1, oc_cloud_refresh_token(ctx, nullptr, nullptr)); } -TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_status) +TEST_F(TestCloudWithServer, oc_cloud_refresh_token_fail_invalid_status) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -398,7 +583,7 @@ TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_status) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_refresh_token_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -413,7 +598,7 @@ TEST_F(TestCloud, oc_cloud_refresh_token_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_refresh_token) +TEST_F(TestCloudWithServer, oc_cloud_do_refresh_token) { setRFNOP(); @@ -442,7 +627,7 @@ TEST_F(TestCloud, oc_cloud_do_refresh_token) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_logout_fail_invalid_input) +TEST_F(TestCloudWithServer, oc_cloud_logout_fail_invalid_input) { EXPECT_EQ(-1, oc_cloud_logout(nullptr, nullptr, nullptr)); @@ -451,7 +636,7 @@ TEST_F(TestCloud, oc_cloud_logout_fail_invalid_input) ASSERT_EQ(-1, oc_cloud_logout(ctx, nullptr, nullptr)); } -TEST_F(TestCloud, oc_cloud_logout_fail_invalid_status) +TEST_F(TestCloudWithServer, oc_cloud_logout_fail_invalid_status) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -465,7 +650,7 @@ TEST_F(TestCloud, oc_cloud_logout_fail_invalid_status) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_logout_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_logout_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -480,7 +665,7 @@ TEST_F(TestCloud, oc_cloud_logout_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_logout) +TEST_F(TestCloudWithServer, oc_cloud_do_logout) { setRFNOP(); @@ -505,7 +690,7 @@ TEST_F(TestCloud, oc_cloud_do_logout) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_input) +TEST_F(TestCloudWithServer, oc_cloud_deregister_fail_invalid_input) { EXPECT_EQ(-1, oc_cloud_deregister(nullptr, nullptr, nullptr)); @@ -514,7 +699,7 @@ TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_input) ASSERT_EQ(-1, oc_cloud_deregister(ctx, nullptr, nullptr)); } -TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_status) +TEST_F(TestCloudWithServer, oc_cloud_deregister_fail_invalid_status) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -528,7 +713,7 @@ TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_status) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_deregister_fail_already_deregistering) +TEST_F(TestCloudWithServer, oc_cloud_deregister_fail_already_deregistering) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -546,7 +731,7 @@ TEST_F(TestCloud, oc_cloud_deregister_fail_already_deregistering) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_deregister_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -562,7 +747,7 @@ TEST_F(TestCloud, oc_cloud_deregister_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_deregister_with_short_access_token) +TEST_F(TestCloudWithServer, oc_cloud_do_deregister_with_short_access_token) { setRFNOP(); @@ -590,7 +775,8 @@ TEST_F(TestCloud, oc_cloud_do_deregister_with_short_access_token) #ifdef OC_DYNAMIC_ALLOCATION -TEST_F(TestCloud, oc_cloud_deregister_fail_not_logged_in_long_access_token) +TEST_F(TestCloudWithServer, + oc_cloud_deregister_fail_not_logged_in_long_access_token) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -609,7 +795,7 @@ TEST_F(TestCloud, oc_cloud_deregister_fail_not_logged_in_long_access_token) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_deregister_logged_in) +TEST_F(TestCloudWithServer, oc_cloud_do_deregister_logged_in) { setRFNOP(); @@ -636,7 +822,7 @@ TEST_F(TestCloud, oc_cloud_do_deregister_logged_in) EXPECT_TRUE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_deregister_with_refresh_token) +TEST_F(TestCloudWithServer, oc_cloud_do_deregister_with_refresh_token) { setRFNOP(); @@ -664,7 +850,8 @@ TEST_F(TestCloud, oc_cloud_do_deregister_with_refresh_token) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_deregister_with_refresh_token_fail_invalid_server) +TEST_F(TestCloudWithServer, + oc_cloud_deregister_with_refresh_token_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -689,7 +876,7 @@ TEST_F(TestCloud, oc_cloud_deregister_with_refresh_token_fail_invalid_server) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_do_deregister_with_login) +TEST_F(TestCloudWithServer, oc_cloud_do_deregister_with_login) { setRFNOP(); @@ -714,7 +901,7 @@ TEST_F(TestCloud, oc_cloud_do_deregister_with_login) EXPECT_FALSE(cbk_called); } -TEST_F(TestCloud, oc_cloud_deregister_with_login_fail_invalid_server) +TEST_F(TestCloudWithServer, oc_cloud_deregister_with_login_fail_invalid_server) { oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); ASSERT_NE(nullptr, ctx); @@ -748,11 +935,9 @@ TEST_F(TestCloud, oc_cloud_deregister_with_login_fail_invalid_server) EXPECT_FALSE(cbk_called); } -#endif /* OC_DYNAMIC_ALLOCATION */ - // TODO: async deregister steps with mocked cloud -TEST_F(TestCloud, EndpointAPI) +TEST_F(TestCloudWithServer, EndpointAPI) { oc_cloud_context_t *ctx = cloud_context_init(/*device*/ 0); ASSERT_NE(nullptr, ctx); @@ -782,14 +967,12 @@ TEST_F(TestCloud, EndpointAPI) auto *ep2 = oc_cloud_add_server_address(ctx, uri2.c_str(), uri2.length(), uid2); ASSERT_NE(nullptr, ep2); -#ifdef OC_DYNAMIC_ALLOCATION std::string uri3 = "/uri/3"; oc_uuid_t uid3; oc_gen_uuid(&uid3); auto *ep3 = oc_cloud_add_server_address(ctx, uri3.c_str(), uri3.length(), uid3); ASSERT_NE(nullptr, ep3); -#endif /* OC_DYNAMIC_ALLOCATION */ // first item added to empty list should be selected EXPECT_EQ(ep1, oc_cloud_selected_server_address(ctx)); @@ -816,12 +999,8 @@ TEST_F(TestCloud, EndpointAPI) }, &uris); -#ifdef OC_DYNAMIC_ALLOCATION ASSERT_EQ(2, uris.size()); EXPECT_NE(uris.end(), uris.find(uri3)); -#else /* !OC_DYNAMIC_ALLOCATION */ - ASSERT_EQ(1, uris.size()); -#endif /* OC_DYNAMIC_ALLOCATION */ EXPECT_NE(uris.end(), uris.find(uri2)); EXPECT_EQ(uris.end(), uris.find(uri1)); @@ -840,15 +1019,9 @@ TEST_F(TestCloud, EndpointAPI) ASSERT_NE(nullptr, toSelect); EXPECT_TRUE(oc_cloud_select_server_address(ctx, toSelect)); -#ifdef OC_DYNAMIC_ALLOCATION EXPECT_EQ(ep3, oc_cloud_selected_server_address(ctx)); EXPECT_STREQ(uri3.c_str(), oc_string(*oc_cloud_get_server_uri(ctx))); EXPECT_TRUE(oc_uuid_is_equal(uid3, *oc_cloud_get_server_id(ctx))); -#else /* !OC_DYNAMIC_ALLOCATION */ - EXPECT_EQ(ep2, oc_cloud_selected_server_address(ctx)); - EXPECT_STREQ(uri2.c_str(), oc_string(*oc_cloud_get_server_uri(ctx))); - EXPECT_TRUE(oc_uuid_is_equal(uid2, *oc_cloud_get_server_id(ctx))); -#endif /* OC_DYNAMIC_ALLOCATION */ oc_uuid_t uid; oc_gen_uuid(&uid); @@ -860,3 +1033,5 @@ TEST_F(TestCloud, EndpointAPI) cloud_context_deinit(ctx); } + +#endif /* OC_DYNAMIC_ALLOCATION */ diff --git a/api/oc_rep.c b/api/oc_rep.c index e9f34ff4f2..eb599cb931 100644 --- a/api/oc_rep.c +++ b/api/oc_rep.c @@ -147,8 +147,8 @@ oc_free_rep(oc_rep_t *rep) } const oc_rep_t * -oc_rep_get(const oc_rep_t *rep, oc_rep_value_type_t type, const char *key, - size_t key_len) +oc_rep_get_by_type_and_key(const oc_rep_t *rep, oc_rep_value_type_t type, + const char *key, size_t key_len) { if (key_len == 0 || key_len >= OC_MAX_STRING_LENGTH) { OC_ERR("Error of input parameters: invalid key"); @@ -181,7 +181,8 @@ oc_rep_get_value(const oc_rep_t *rep, oc_rep_value_type_t type, const char *key, OC_ERR("Error of input parameters: invalid value"); return false; } - const oc_rep_t *rep_value = oc_rep_get(rep, type, key, key_len); + const oc_rep_t *rep_value = + oc_rep_get_by_type_and_key(rep, type, key, key_len); if (rep_value == NULL) { return false; } diff --git a/api/oc_rep_internal.h b/api/oc_rep_internal.h index 1cbb28f83f..30d1add606 100644 --- a/api/oc_rep_internal.h +++ b/api/oc_rep_internal.h @@ -67,10 +67,6 @@ typedef struct oc_rep_t *rep; } oc_rep_parse_result_t; -/** @brief Get the value of a property by name. */ -const oc_rep_t *oc_rep_get(const oc_rep_t *rep, oc_rep_value_type_t type, - const char *key, size_t key_len) OC_NONNULL(3); - /** * @brief Decode the payload into a oc_rep_t object using the global decoder. * diff --git a/api/oc_ri_server.c b/api/oc_ri_server.c index 35bb1c57ff..7ed34f67f9 100644 --- a/api/oc_ri_server.c +++ b/api/oc_ri_server.c @@ -34,7 +34,7 @@ bool oc_ri_on_delete_resource_add_callback(oc_ri_delete_resource_cb_t cb) { if (oc_ri_on_delete_resource_find_callback(cb) != NULL) { - OC_WRN("delete resource callback already exists"); + OC_ERR("delete resource callback already exists"); return false; } oc_ri_on_delete_resource_t *item = oc_memb_alloc(&g_on_delete_resource_cb_s); diff --git a/api/oc_storage.c b/api/oc_storage.c index f13c1cc1bd..dc0e8e2c87 100644 --- a/api/oc_storage.c +++ b/api/oc_storage.c @@ -213,8 +213,10 @@ oc_storage_data_save(const char *name, size_t device, goto error; } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START OC_DBG("oc_storage: encoded \"%s\" size %d", name, size); storage_print_data(sb.buffer, size); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ char svr_tag[OC_STORAGE_SVR_TAG_MAX]; diff --git a/api/oc_swupdate.c b/api/oc_swupdate.c index 66139b3bdb..fcfea8338c 100644 --- a/api/oc_swupdate.c +++ b/api/oc_swupdate.c @@ -273,10 +273,12 @@ oc_swupdate_action_schedule(size_t device, oc_clock_time_t schedule_at) assert(!oc_swupdate_action_is_scheduled(device)); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START #define RFC3339_BUFFER_SIZE 64 char scheduled_ts[RFC3339_BUFFER_SIZE] = { 0 }; oc_clock_encode_time_rfc3339(schedule_at, scheduled_ts, sizeof(scheduled_ts)); OC_DBG("swupdate: update scheduled at %s", scheduled_ts); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ oc_clock_time_t now = oc_clock_time(); diff --git a/api/plgd/plgd_time.c b/api/plgd/plgd_time.c index 1815f530f6..acfebd26b3 100644 --- a/api/plgd/plgd_time.c +++ b/api/plgd/plgd_time.c @@ -154,10 +154,12 @@ dev_time_set_time(oc_clock_time_t lst, bool dump, bool notify) } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char lst_ts[64] = { 0 }; oc_clock_encode_time_rfc3339(lst, lst_ts, sizeof(lst_ts)); uint64_t ut_s = (uint64_t)((double)updateTime / (double)OC_CLOCK_SECOND); OC_DBG("plgd-time: %s (update: %" PRIu64 "s)", lst_ts, ut_s); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ plgd_time_set(lst, updateTime, dump, notify); @@ -207,6 +209,7 @@ dev_plgd_time(plgd_time_t pt) oc_clock_time_t ptime = (pt.store.last_synced_time + elapsed); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START #define RFC3339_BUFFER_SIZE 64 double to_micros = (10000000 / (double)OC_CLOCK_SECOND); char lst_ts[RFC3339_BUFFER_SIZE] = { 0 }; @@ -226,6 +229,7 @@ dev_plgd_time(plgd_time_t pt) long diff = (long)((double)(time - ptime) / (double)OC_CLOCK_SECOND); OC_DBG("calculated plgd-time: %s, system time: %s, diff: %lds", pt_ts, ts, diff); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ return ptime; } @@ -246,8 +250,10 @@ void plgd_time_set_status(plgd_time_status_t status) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START const char *status_str = plgd_time_status_to_str(status); OC_DBG("plgd-time status: %s", status_str != NULL ? status_str : "NULL"); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ g_oc_plgd_time.status = status; } diff --git a/include/oc_cloud.h b/include/oc_cloud.h index 7a60536476..63f9a5d711 100644 --- a/include/oc_cloud.h +++ b/include/oc_cloud.h @@ -138,9 +138,7 @@ typedef bool (*oc_cloud_schedule_action_cb_t)(oc_cloud_action_t action, uint16_t *timeout, void *user_data) OC_NONNULL(3, 4); -/** - * @brief Get cloud context for device. - */ +/** @brief Get cloud context for device. */ OC_API oc_cloud_context_t *oc_cloud_get_context(size_t device); diff --git a/include/oc_rep.h b/include/oc_rep.h index a05cfb24fc..cb32fe09d8 100644 --- a/include/oc_rep.h +++ b/include/oc_rep.h @@ -1568,6 +1568,23 @@ OC_API bool oc_rep_get_object_array(const oc_rep_t *rep, const char *key, oc_rep_t **value); +/** + * @brief Get the value of a property by type and key. + * + * @param rep oc_rep_t to read the value from + * @param type the type of the value to read + * @param key the key for the value + * @param key_len the length of \p key + * + * @return the value of the property if found + * @return NULL otherwise + */ +OC_API +const oc_rep_t *oc_rep_get_by_type_and_key(const oc_rep_t *rep, + oc_rep_value_type_t type, + const char *key, size_t key_len) + OC_NONNULL(3); + /** * Tab character(s) used for oc_rep_to_json function when doing pretty_print */ diff --git a/messaging/coap/coap.c b/messaging/coap/coap.c index 58dcd13292..e7978810b0 100644 --- a/messaging/coap/coap.c +++ b/messaging/coap/coap.c @@ -499,11 +499,13 @@ coap_serialize_options(const coap_packet_t *packet, uint8_t *option_array, size_t option_length = 0; #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (option != NULL) { COAP_DBG("Serializing options at %p", (void *)option); } else { COAP_DBG("Calculating size of options"); } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ #ifdef OC_TCP @@ -721,11 +723,13 @@ coap_oscore_parse_inner_option(coap_packet_t *packet, packet->etag_len = (uint8_t)MIN(COAP_ETAG_LEN, option_length); memcpy(packet->etag, option, packet->etag_len); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char buf[32]; size_t buf_size = OC_ARRAY_SIZE(buf); oc_conv_byte_array_to_hex_string(packet->etag, packet->etag_len, buf, &buf_size); COAP_DBG(" ETag %u [0x%s]", packet->etag_len, buf); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ return COAP_NO_ERROR; } diff --git a/messaging/coap/observe.c b/messaging/coap/observe.c index 3f8b65b7bf..85b99f4283 100644 --- a/messaging/coap/observe.c +++ b/messaging/coap/observe.c @@ -1082,6 +1082,7 @@ coap_iterate_observers(oc_resource_t *resource, oc_response_t *response, } if (prepare_response) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START oc_string64_t ep_str; const char *ep_cstr = ""; if (oc_endpoint_to_string64(&obs->endpoint, &ep_str)) { @@ -1089,6 +1090,7 @@ coap_iterate_observers(oc_resource_t *resource, oc_response_t *response, } COAP_DBG("prepare GET request to resource(%s) for endpoint %s", oc_string(resource->uri), ep_cstr); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ if (!coap_fill_response(response, resource, &obs->endpoint, iface_mask, true)) { diff --git a/messaging/coap/transactions.c b/messaging/coap/transactions.c index e577c4e921..d09b4288ef 100644 --- a/messaging/coap/transactions.c +++ b/messaging/coap/transactions.c @@ -276,9 +276,11 @@ coap_free_transactions_by_endpoint(const oc_endpoint_t *endpoint, oc_status_t code) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START oc_string64_t ep_str; oc_endpoint_to_string64(endpoint, &ep_str); COAP_DBG("free transactions for endpoint(%s)", oc_string(ep_str)); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ #ifndef OC_CLIENT (void)code; diff --git a/port/linux/ipadapter.c b/port/linux/ipadapter.c index 7486deb6ab..7a15a0e6b9 100644 --- a/port/linux/ipadapter.c +++ b/port/linux/ipadapter.c @@ -878,6 +878,7 @@ process_event(ip_context_t *dev, fd_set *rdfds, fd_set *wfds) } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (rdfds != NULL) { for (int i = 0; i < FD_SETSIZE; ++i) { if (FD_ISSET(i, rdfds)) { @@ -892,6 +893,7 @@ process_event(ip_context_t *dev, fd_set *rdfds, fd_set *wfds) } } } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ return 0; diff --git a/port/windows/network_addresses.c b/port/windows/network_addresses.c index db8b9ad140..2fe7b53625 100644 --- a/port/windows/network_addresses.c +++ b/port/windows/network_addresses.c @@ -144,12 +144,14 @@ get_network_addresses(void) if (!IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) && !(address->Flags & IP_ADAPTER_ADDRESS_DNS_ELIGIBLE)) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char dotname[NI_MAXHOST] = { 0 }; getnameinfo((const SOCKADDR *)addr, sizeof(struct sockaddr_in6), dotname, sizeof(dotname), NULL, 0, NI_NUMERICHOST); OC_DBG("%s is not IN6_IS_ADDR_LINKLOCAL and not " "IP_ADAPTER_ADDRESS_DNS_ELIGIBLE, skipped.", dotname); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ continue; } diff --git a/security/oc_acl.c b/security/oc_acl.c index f5694e635d..2109a2625a 100644 --- a/security/oc_acl.c +++ b/security/oc_acl.c @@ -780,6 +780,7 @@ oc_sec_add_new_ace(oc_ace_subject_type_t type, const oc_ace_subject_t *subject, } else { memcpy(&ace->subject, subject, sizeof(oc_ace_subject_t)); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (type == OC_SUBJECT_UUID) { char c[OC_UUID_LEN]; oc_uuid_to_str(&ace->subject.uuid, c, OC_UUID_LEN); @@ -791,6 +792,7 @@ oc_sec_add_new_ace(oc_ace_subject_type_t type, const oc_ace_subject_t *subject, OC_DBG("Adding ACE for auth-crypt connection"); } } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ } @@ -825,6 +827,7 @@ oc_sec_add_new_ace_res(const char *href, oc_ace_wildcard_t wildcard, res->wildcard = wildcard; } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START switch (res->wildcard) { case OC_ACE_WC_ALL_SECURED: OC_DBG("Adding wildcard resource + with permission %d", permission); @@ -838,6 +841,7 @@ oc_sec_add_new_ace_res(const char *href, oc_ace_wildcard_t wildcard, default: break; } + // GCOVR_EXCL_STOP #else /* !OC_DBG_IS_ENABLED */ (void)permission; #endif /* OC_DBG_IS_ENABLED */ diff --git a/security/oc_certs.c b/security/oc_certs.c index bfaaeea2c2..6b4a0b1bb2 100644 --- a/security/oc_certs.c +++ b/security/oc_certs.c @@ -339,12 +339,14 @@ oc_certs_parse_CN_buffer_for_UUID(mbedtls_asn1_buf val, char *buffer, val.len - uuid_prefix_len < OC_UUID_LEN - 1) { // -1 because val is not nul-terminated #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START oc_string_t cn; oc_new_string(&cn, uuid_CN, val.len); OC_DBG("Common Name field (tag:%d val:%s) is not in format " UUID_PREFIX ":", val.tag, oc_string(cn)); oc_free_string(&cn); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ return false; } diff --git a/security/oc_pstat.c b/security/oc_pstat.c index fc9e6673a1..82f93b2a2d 100644 --- a/security/oc_pstat.c +++ b/security/oc_pstat.c @@ -71,7 +71,7 @@ oc_sec_pstat_free(void) free(g_pstat); g_pstat = NULL; } -#else +#else /* !OC_DYNAMIC_ALLOCATION */ memset(g_pstat, 0, sizeof(g_pstat)); #endif /* OC_DYNAMIC_ALLOCATION */ } @@ -84,7 +84,7 @@ oc_sec_pstat_init_for_devices(size_t num_device) if (!g_pstat) { oc_abort("Insufficient memory"); } -#else +#else /* !OC_DYNAMIC_ALLOCATION */ (void)num_device; #endif /* OC_DYNAMIC_ALLOCATION */ } @@ -341,10 +341,12 @@ oc_pstat_handle_state(oc_sec_pstat_t *ps, size_t device, bool from_storage, if (doxm->owned || !nil_uuid(&doxm->devowneruuid) || !pstat_check_ps_state(ps)) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (!nil_uuid(&doxm->devowneruuid)) { OC_DBG("non-Nil doxm:devowneruuid in RFOTM"); } OC_DBG("ERROR in RFOTM\n"); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ g_pstat[device].reset_in_progress = false; goto pstat_state_error; @@ -566,9 +568,11 @@ oc_sec_decode_pstat(const oc_rep_t *rep, bool from_storage, size_t device) oc_sec_pstat_t ps; oc_sec_pstat_copy(&ps, &g_pstat[device]); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (!from_storage) { print_pstat_dos(&ps); } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ bool transition_state = false; diff --git a/security/oc_tls.c b/security/oc_tls.c index 4a7b3c613a..3d6592f295 100644 --- a/security/oc_tls.c +++ b/security/oc_tls.c @@ -465,11 +465,13 @@ static void oc_tls_free_peer(oc_tls_peer_t *peer, bool inactivity_cb, bool from_reset) { #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START oc_string64_t endpoint_str; oc_endpoint_to_string64(&peer->endpoint, &endpoint_str); OC_DBG("oc_tls: freeing peer(%p): endpoint(%s), role(%s)", (void *)peer, oc_string(endpoint_str), peer->role == MBEDTLS_SSL_IS_SERVER ? "server" : "client"); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ #ifdef OC_PKI if (peer->user_data.free != NULL) { @@ -990,6 +992,7 @@ is_known_identity_cert(const oc_sec_cred_t *cred) return true; } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START mbedtls_x509_crt *c = &certs->cert; int chain_length = 0; while (c) { @@ -997,6 +1000,7 @@ is_known_identity_cert(const oc_sec_cred_t *cred) c = c->next; } OC_DBG("identity cert chain is now of size %d", chain_length); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ if (cert->next) { @@ -1065,6 +1069,7 @@ add_new_identity_cert(oc_sec_cred_t *cred, size_t device) } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START mbedtls_x509_crt *c = &cert->cert; int chain_length = 0; while (c) { @@ -1072,6 +1077,7 @@ add_new_identity_cert(oc_sec_cred_t *cred, size_t device) c = c->next; } OC_DBG("adding new identity cert chain of size %d", chain_length); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ oc_list_add(g_identity_certs, cert); @@ -1174,11 +1180,13 @@ oc_tls_reload_trust_anchors(void) } cert->cert = c; #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char buf[256]; if (mbedtls_x509_serial_gets(buf, OC_ARRAY_SIZE(buf) - 1, &c->serial) > 0) { OC_DBG("trust anchor(serial: %s) added to chain", buf); } OC_DBG("trust anchor chain is now of size %d", chain_length); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ cert = cert->next; @@ -1519,11 +1527,13 @@ add_new_trust_anchor(oc_sec_cred_t *cred, size_t device) } cert->cert = c; #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char buf[256]; if (mbedtls_x509_serial_gets(buf, OC_ARRAY_SIZE(buf) - 1, &c->serial) > 0) { OC_DBG("trust anchor(serial: %s) added to chain", buf); } OC_DBG("trust anchor chain is now of size %d", chain_length); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ oc_list_add(g_ca_certs, cert); @@ -1844,6 +1854,7 @@ verify_manufacturer_or_identity_certificate(oc_tls_peer_t *peer, OC_DBG("found matching trustca; check if trustca's cred entry has a " "UUID matching with the peer's UUID, or *"); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START if (ca_cert->cred->subjectuuid.id[0] != '*') { char ca_uuid[OC_UUID_LEN] = { 0 }; oc_uuid_to_str(&ca_cert->cred->subjectuuid, ca_uuid, @@ -1852,6 +1863,7 @@ verify_manufacturer_or_identity_certificate(oc_tls_peer_t *peer, } else { OC_DBG("trustca cred UUID is the wildcard *"); } + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ if (memcmp(ca_cert->cred->subjectuuid.id, peer->uuid.id, OC_ARRAY_SIZE(peer->uuid.id)) != 0) { @@ -2154,11 +2166,13 @@ oc_tls_add_new_peer(oc_tls_new_peer_params_t params) oc_list_add(g_tls_peers, peer); #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START oc_string64_t endpoint_str; oc_endpoint_to_string64(&peer->endpoint, &endpoint_str); OC_DBG("oc_tls: new peer(%p) added: endpoint(%s), role(%s)", (void *)peer, oc_string(endpoint_str), peer->role == MBEDTLS_SSL_IS_SERVER ? "server" : "client"); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ return peer; @@ -3010,10 +3024,12 @@ oc_tls_recv_message(oc_message_t *message) return; } #if OC_DBG_IS_ENABLED + // GCOVR_EXCL_START char u[OC_UUID_LEN]; oc_uuid_to_str(&peer->uuid, u, OC_UUID_LEN); OC_DBG("oc_tls: Received message from device(uuid=%s): length=%zu, peer=%p", u, message->length, (void *)peer); + // GCOVR_EXCL_STOP #endif /* OC_DBG_IS_ENABLED */ // Set the interface index from the incoming message if the network interface diff --git a/swig/swig_interfaces/oc_cloud.i b/swig/swig_interfaces/oc_cloud.i index a7fc6a322c..30241111f5 100644 --- a/swig/swig_interfaces/oc_cloud.i +++ b/swig/swig_interfaces/oc_cloud.i @@ -712,6 +712,11 @@ void jni_cloud_context_clear(oc_cloud_context_t *ctx, bool dump_async) %ignore cloud_context_has_permanent_access_token; %ignore cloud_context_clear_access_token; %ignore cloud_context_has_refresh_token; + +%ignore oc_cloud_registration_context_t; +%ignore oc_cloud_registration_context_init; +%ignore oc_cloud_registration_context_deinit; +%ignore oc_cloud_context_t::registration_ctx; %include "api/cloud/oc_cloud_context_internal.h" %ignore oc_cloud_add_server_address; diff --git a/swig/swig_interfaces/oc_endpoint_address.i b/swig/swig_interfaces/oc_endpoint_address.i index 1d3398e13d..19dbb97883 100644 --- a/swig/swig_interfaces/oc_endpoint_address.i +++ b/swig/swig_interfaces/oc_endpoint_address.i @@ -32,6 +32,7 @@ %ignore oc_endpoint_address_metadata_t; %ignore oc_endpoint_address_metadata_view_t; %ignore oc_endpoint_address_view_t; +%ignore oc_endpoint_address_view; %ignore oc_endpoint_address_make_view_with_uuid; %ignore oc_endpoint_address_make_view_with_name; @@ -87,6 +88,11 @@ /*******************Begin oc_endpoint_address_internal.h****************************/ +%ignore oc_endpoint_address_metadata_type_t; +%ignore oc_endpoint_address_encode; +%ignore on_selected_endpoint_address_change_fn_t; +%ignore oc_endpoint_addresses_on_selected_change_t; + %ignore oc_endpoint_addresses_t; %ignore oc_endpoint_addresses_init; %ignore oc_endpoint_addresses_deinit; @@ -104,11 +110,13 @@ %ignore oc_endpoint_addresses_select_by_uri; %ignore oc_endpoint_addresses_select_next; %ignore oc_endpoint_addresses_is_selected; +%ignore oc_endpoint_addresses_selected; %ignore oc_endpoint_addresses_selected_uri; %ignore oc_endpoint_addresses_selected_uuid; %ignore oc_endpoint_addresses_selected_name; %ignore oc_endpoint_addresses_encode; - +%ignore oc_endpoint_addresses_set_on_selected_change; +%ignore oc_endpoint_addresses_get_on_selected_change; %include "util/oc_endpoint_address_internal.h" diff --git a/swig/swig_interfaces/oc_rep.i b/swig/swig_interfaces/oc_rep.i index 4ec2829618..e7df398d32 100644 --- a/swig/swig_interfaces/oc_rep.i +++ b/swig/swig_interfaces/oc_rep.i @@ -1739,6 +1739,8 @@ oc_rep_t * jni_rep_get_object_array(oc_rep_t* rep, const char *key) { } %} +%ignore oc_rep_get_by_type_and_key; +// TODO: implement // Expose oc_array_t this will be exposed as a class that has no usage without helper functions %rename(OCArray) oc_mmem; diff --git a/util/oc_endpoint_address.c b/util/oc_endpoint_address.c index 7185b01ae8..3f70490a5c 100644 --- a/util/oc_endpoint_address.c +++ b/util/oc_endpoint_address.c @@ -79,7 +79,8 @@ oc_endpoint_address_uri(const oc_endpoint_address_t *ea) } static void -endpoint_address_free_metadata_id(oc_endpoint_address_metadata_t *metadata) +OC_NONNULL() + endpoint_address_free_metadata_id(oc_endpoint_address_metadata_t *metadata) { if (metadata->id_type == OC_ENDPOINT_ADDRESS_METADATA_TYPE_NAME) { oc_free_string(&metadata->id.name); @@ -131,22 +132,24 @@ endpoint_address_is_valid(oc_string_view_t uri) return uri.length > 0 && uri.length < OC_ENDPOINT_MAX_ENDPOINT_URI_LENGTH; } -static void -endpoint_address_select(oc_endpoint_addresses_t *eas, - const oc_endpoint_address_t *selected) +static bool OC_NONNULL(1) + endpoint_address_select(oc_endpoint_addresses_t *eas, + const oc_endpoint_address_t *selected) { if (eas->selected == selected) { - return; + return false; } eas->selected = selected; - if (eas->on_selected_change != NULL) { - eas->on_selected_change(eas->on_selected_change_data); + if (eas->on_selected_change.cb != NULL) { + eas->on_selected_change.cb(eas->on_selected_change.cb_data); } + return true; } static bool -endpoint_address_set_metadata(oc_endpoint_address_t *ea, - oc_endpoint_address_metadata_view_t metadata) +OC_NONNULL() + endpoint_address_set_metadata(oc_endpoint_address_t *ea, + oc_endpoint_address_metadata_view_t metadata) { ea->metadata.id_type = metadata.id_type; if (metadata.id_type == OC_ENDPOINT_ADDRESS_METADATA_TYPE_UUID) { @@ -162,9 +165,9 @@ endpoint_address_set_metadata(oc_endpoint_address_t *ea, } static oc_endpoint_address_t * -endpoint_address_allocate_and_add(oc_endpoint_addresses_t *eas, - oc_string_view_t uri, - oc_endpoint_address_metadata_view_t metadata) +OC_NONNULL() endpoint_address_allocate_and_add( + oc_endpoint_addresses_t *eas, oc_string_view_t uri, + oc_endpoint_address_metadata_view_t metadata) { if (!endpoint_address_is_valid(uri)) { return NULL; @@ -209,8 +212,8 @@ oc_endpoint_addresses_init( // set callback before calling allocate and add, because it may trigger the // callback - eas->on_selected_change = on_selected_change; - eas->on_selected_change_data = on_selected_change_data; + eas->on_selected_change.cb = on_selected_change; + eas->on_selected_change.cb_data = on_selected_change_data; if (default_ea.uri.length > 0) { const oc_endpoint_address_t *ea = endpoint_address_allocate_and_add( @@ -224,7 +227,8 @@ oc_endpoint_addresses_init( } static void -endpoint_address_free(oc_endpoint_addresses_t *eas, oc_endpoint_address_t *ea) +OC_NONNULL() + endpoint_address_free(oc_endpoint_addresses_t *eas, oc_endpoint_address_t *ea) { oc_free_string(&ea->uri); endpoint_address_free_metadata_id(&ea->metadata); @@ -250,8 +254,24 @@ oc_endpoint_addresses_reinit(oc_endpoint_addresses_t *eas, oc_endpoint_address_view_t default_ea) { oc_endpoint_addresses_deinit(eas); - return oc_endpoint_addresses_init(eas, eas->pool, eas->on_selected_change, - eas->on_selected_change_data, default_ea); + return oc_endpoint_addresses_init(eas, eas->pool, eas->on_selected_change.cb, + eas->on_selected_change.cb_data, + default_ea); +} + +void +oc_endpoint_addresses_set_on_selected_change( + oc_endpoint_addresses_t *eas, on_selected_endpoint_address_change_fn_t cb, + void *data) +{ + eas->on_selected_change.cb = cb; + eas->on_selected_change.cb_data = data; +} + +oc_endpoint_addresses_on_selected_change_t +oc_endpoint_addresses_get_on_selected_change(const oc_endpoint_addresses_t *eas) +{ + return eas->on_selected_change; } size_t @@ -289,7 +309,7 @@ oc_endpoint_addresses_add(oc_endpoint_addresses_t *eas, oc_endpoint_address_view_t ea) { if (!endpoint_address_is_valid(ea.uri)) { - OC_DBG("oc_endpoint_addresses_add: invalid uri(%s)", + OC_ERR("cannot add endpoint address: invalid uri(%s)", ea.uri.data != NULL ? ea.uri.data : "null"); return NULL; } @@ -301,9 +321,9 @@ oc_endpoint_addresses_add(oc_endpoint_addresses_t *eas, return endpoint_address_allocate_and_add(eas, ea.uri, ea.metadata); } -static const oc_endpoint_address_t * -endpoint_address_next(const oc_endpoint_addresses_t *eas, - const oc_endpoint_address_t *item_next) +static const oc_endpoint_address_t *OC_NONNULL(1) + endpoint_address_next(const oc_endpoint_addresses_t *eas, + const oc_endpoint_address_t *item_next) { if (item_next != NULL) { return item_next; @@ -375,7 +395,7 @@ typedef struct } endpoint_address_match_t; static bool -endpoint_address_match(oc_endpoint_address_t *ea, void *data) +OC_NONNULL() endpoint_address_match(oc_endpoint_address_t *ea, void *data) { endpoint_address_match_t *match = (endpoint_address_match_t *)data; if (oc_string_view_is_equal(oc_string_view2(&ea->uri), match->uri)) { @@ -420,13 +440,14 @@ oc_endpoint_addresses_select_by_uri(oc_endpoint_addresses_t *eas, return true; } -void +bool oc_endpoint_addresses_select_next(oc_endpoint_addresses_t *eas) { if (eas->selected == NULL) { - return; + return false; } - endpoint_address_select(eas, endpoint_address_next(eas, eas->selected->next)); + return endpoint_address_select( + eas, endpoint_address_next(eas, eas->selected->next)); } bool @@ -509,7 +530,7 @@ oc_endpoint_address_encode(CborEncoder *encoder, oc_string_view_t uri_key, } static bool -endpoint_addresses_encode(oc_endpoint_address_t *ea, void *data) +OC_NONNULL() endpoint_addresses_encode(oc_endpoint_address_t *ea, void *data) { endpoint_address_encoder_t *encoder = (endpoint_address_encoder_t *)data; @@ -518,8 +539,9 @@ endpoint_addresses_encode(oc_endpoint_address_t *ea, void *data) encoder->error |= oc_rep_encoder_create_map( encoder->ci_servers, &endpoint_map, CborIndefiniteLength); encoder->error |= oc_endpoint_address_encode( - &endpoint_map, OC_STRING_VIEW("uri"), OC_STRING_VIEW("id"), - OC_STRING_VIEW("name"), oc_endpoint_address_view(ea)); + &endpoint_map, OC_STRING_VIEW(OC_ENDPOINT_ADDRESS_URI), + OC_STRING_VIEW(OC_ENDPOINT_ADDRESS_ID), + OC_STRING_VIEW(OC_ENDPOINT_ADDRESS_NAME), oc_endpoint_address_view(ea)); encoder->error |= oc_rep_encoder_close_container(encoder->ci_servers, &endpoint_map); return true; diff --git a/util/oc_endpoint_address_internal.h b/util/oc_endpoint_address_internal.h index daf3357d2f..7f2734874e 100644 --- a/util/oc_endpoint_address_internal.h +++ b/util/oc_endpoint_address_internal.h @@ -99,16 +99,19 @@ CborError oc_endpoint_address_encode( typedef void (*on_selected_endpoint_address_change_fn_t)(void *data); +typedef struct +{ + on_selected_endpoint_address_change_fn_t + cb; ///< callback invoked when the selected endpoint address changes + void *cb_data; ///< data passed to the callback +} oc_endpoint_addresses_on_selected_change_t; + typedef struct { oc_memb_t *pool; ///< memory pool for endpoint addresses const oc_endpoint_address_t *selected; ///< currently selected endpoint address - on_selected_endpoint_address_change_fn_t - on_selected_change; ///< callback invoked when the selected endpoint address - ///< changes - void * - on_selected_change_data; ///< data passed to the on_selected_change callback + oc_endpoint_addresses_on_selected_change_t on_selected_change; OC_LIST_STRUCT(addresses); ///< list of endpoint addresses } oc_endpoint_addresses_t; @@ -139,6 +142,16 @@ bool oc_endpoint_addresses_reinit(oc_endpoint_addresses_t *eas, oc_endpoint_address_view_t default_ea) OC_NONNULL(1); +/** Set the callback invoked when the selected endpoint address changes */ +void oc_endpoint_addresses_set_on_selected_change( + oc_endpoint_addresses_t *eas, on_selected_endpoint_address_change_fn_t cb, + void *data) OC_NONNULL(1, 2); + +/** Get the callback invoked when the selected endpoint address changes */ +oc_endpoint_addresses_on_selected_change_t +oc_endpoint_addresses_get_on_selected_change(const oc_endpoint_addresses_t *eas) + OC_NONNULL(); + /** Get the number of endpoint adresses in the list */ size_t oc_endpoint_addresses_size(const oc_endpoint_addresses_t *eas) OC_NONNULL(); @@ -221,7 +234,7 @@ bool oc_endpoint_addresses_select_by_uri(oc_endpoint_addresses_t *eas, oc_string_view_t uri) OC_NONNULL(); /** Select the next endpoint address from the list of endpoint addresses */ -void oc_endpoint_addresses_select_next(oc_endpoint_addresses_t *eas) +bool oc_endpoint_addresses_select_next(oc_endpoint_addresses_t *eas) OC_NONNULL(); /** Check if an endpoint address matching the given URI is selected */ @@ -244,6 +257,10 @@ const oc_uuid_t *oc_endpoint_addresses_selected_uuid( const oc_string_t *oc_endpoint_addresses_selected_name( const oc_endpoint_addresses_t *eas) OC_NONNULL(); +#define OC_ENDPOINT_ADDRESS_URI ("uri") +#define OC_ENDPOINT_ADDRESS_ID ("id") +#define OC_ENDPOINT_ADDRESS_NAME ("name") + /** * Encode cloud server endpoints array to an encoder * diff --git a/util/unittest/endpoint_address_test.cpp b/util/unittest/endpoint_address_test.cpp index 5b6394edc5..a17c2fc9da 100644 --- a/util/unittest/endpoint_address_test.cpp +++ b/util/unittest/endpoint_address_test.cpp @@ -378,7 +378,17 @@ TEST_F(TestEndpointAddresses, Select) { oc_endpoint_addresses_t &ea{ getAddresses() }; - oc_endpoint_addresses_select_next(&ea); + auto on_selected_change = oc_endpoint_addresses_get_on_selected_change(&ea); + ASSERT_EQ(nullptr, on_selected_change.cb); + ASSERT_EQ(nullptr, on_selected_change.cb_data); + bool on_change_invoked = false; + oc_endpoint_addresses_set_on_selected_change( + &ea, [](void *data) { *static_cast(data) = true; }, + &on_change_invoked); + + bool selected_changed = oc_endpoint_addresses_select_next(&ea); + EXPECT_FALSE(selected_changed); + EXPECT_FALSE(on_change_invoked); auto *selected_addr = oc_endpoint_addresses_selected_uri(&ea); EXPECT_EQ(nullptr, selected_addr); auto *selected_uuid = oc_endpoint_addresses_selected_uuid(&ea); @@ -392,9 +402,9 @@ TEST_F(TestEndpointAddresses, Select) ASSERT_NE(nullptr, oc_endpoint_addresses_add( &ea, oc_endpoint_address_make_view_with_uuid(uri1, id1))); - // when adding to an empty list, the first added item is automatically // selected + EXPECT_TRUE(on_change_invoked); EXPECT_TRUE(oc_endpoint_addresses_is_selected(&ea, uri1)); selected_addr = oc_endpoint_addresses_selected_uri(&ea); ASSERT_NE(nullptr, selected_addr); @@ -407,8 +417,10 @@ TEST_F(TestEndpointAddresses, Select) ASSERT_EQ(nullptr, selected_name); // non-existing URI shouldn't change the selection + on_change_invoked = false; EXPECT_FALSE( oc_endpoint_addresses_select_by_uri(&ea, OC_STRING_VIEW("/fail"))); + EXPECT_FALSE(on_change_invoked); EXPECT_FALSE(oc_endpoint_addresses_is_selected(&ea, OC_STRING_VIEW("/fail"))); EXPECT_TRUE(oc_endpoint_addresses_is_selected(&ea, uri1)); selected_addr = oc_endpoint_addresses_selected_uri(&ea); @@ -453,10 +465,16 @@ TEST_F(TestEndpointAddresses, Select) EXPECT_STREQ(name3.data, oc_string(*selected_name)); // rotate back to uri1 - oc_endpoint_addresses_select_next(&ea); + on_change_invoked = false; + selected_changed = oc_endpoint_addresses_select_next(&ea); + EXPECT_TRUE(selected_changed); + EXPECT_TRUE(on_change_invoked); #else /* !OC_DYNAMIC_ALLOCATION */ // the list has a single element, so the selection should stay at uri1 - oc_endpoint_addresses_select_next(&ea); + on_change_invoked = false; + selected_changed = oc_endpoint_addresses_select_next(&ea); + EXPECT_FALSE(selected_changed); + EXPECT_FALSE(on_change_invoked); #endif /* OC_DYNAMIC_ALLOCATION */ EXPECT_TRUE(oc_endpoint_addresses_is_selected(&ea, uri1)); From 47728221aa4ab7c31a48ed708e24d571771983c9 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Wed, 27 Mar 2024 18:17:40 +0100 Subject: [PATCH 2/2] cloud: set/get function for timeouts of the default retry action --- api/cloud/oc_cloud_context.c | 9 - api/cloud/oc_cloud_manager.c | 110 +---------- api/cloud/oc_cloud_manager_internal.h | 3 - api/cloud/oc_cloud_schedule.c | 183 ++++++++++++++++++ api/cloud/oc_cloud_schedule_internal.h | 58 ++++++ api/cloud/unittest/cloud_manager_test.cpp | 7 +- port/esp32/main/CMakeLists.txt | 1 + port/windows/vs2015/IoTivity-lite.vcxproj | 11 +- .../vs2015/IoTivity-lite.vcxproj.filters | 38 +++- 9 files changed, 288 insertions(+), 132 deletions(-) create mode 100644 api/cloud/oc_cloud_schedule.c create mode 100644 api/cloud/oc_cloud_schedule_internal.h diff --git a/api/cloud/oc_cloud_context.c b/api/cloud/oc_cloud_context.c index ba5101ca04..5557c076db 100644 --- a/api/cloud/oc_cloud_context.c +++ b/api/cloud/oc_cloud_context.c @@ -334,15 +334,6 @@ oc_cloud_set_keepalive( ctx->keepalive.user_data = user_data; } -void -oc_cloud_set_schedule_action(oc_cloud_context_t *ctx, - oc_cloud_schedule_action_cb_t on_schedule_action, - void *user_data) -{ - ctx->schedule_action.on_schedule_action = on_schedule_action; - ctx->schedule_action.user_data = user_data; -} - oc_endpoint_address_t * oc_cloud_add_server_address(oc_cloud_context_t *ctx, const char *uri, size_t uri_len, oc_uuid_t sid) diff --git a/api/cloud/oc_cloud_manager.c b/api/cloud/oc_cloud_manager.c index 0d77873ac5..4acf17c2b2 100644 --- a/api/cloud/oc_cloud_manager.c +++ b/api/cloud/oc_cloud_manager.c @@ -29,6 +29,7 @@ #include "api/cloud/oc_cloud_log_internal.h" #include "api/cloud/oc_cloud_manager_internal.h" #include "api/cloud/oc_cloud_resource_internal.h" +#include "api/cloud/oc_cloud_schedule_internal.h" #include "api/cloud/oc_cloud_store_internal.h" #include "api/cloud/rd_client_internal.h" #include "api/oc_rep_internal.h" @@ -36,7 +37,6 @@ #include "oc_api.h" #include "oc_cloud_access.h" #include "oc_endpoint.h" -#include "port/oc_random.h" #include "util/oc_endpoint_address_internal.h" #include "util/oc_list.h" #include "util/oc_memb.h" @@ -49,10 +49,6 @@ #include #include -#define MILLISECONDS_PER_SECOND (1000) -#define MILLISECONDS_PER_MINUTE (60 * MILLISECONDS_PER_SECOND) -#define MILLISECONDS_PER_HOUR (60 * MILLISECONDS_PER_MINUTE) - static void cloud_start_process(oc_cloud_context_t *ctx); static oc_event_callback_retval_t cloud_manager_reconnect_async(void *data); static oc_event_callback_retval_t cloud_manager_register_async(void *data); @@ -60,30 +56,6 @@ static oc_event_callback_retval_t cloud_manager_login_async(void *data); static oc_event_callback_retval_t cloud_manager_refresh_token_async(void *data); static oc_event_callback_retval_t cloud_manager_send_ping_async(void *data); -#define OC_CLOUD_DEFAULT_RETRY_TIMEOUTS \ - { \ - 2 * MILLISECONDS_PER_SECOND, 4 * MILLISECONDS_PER_SECOND, \ - 8 * MILLISECONDS_PER_SECOND, 16 * MILLISECONDS_PER_SECOND, \ - 32 * MILLISECONDS_PER_SECOND, 64 * MILLISECONDS_PER_SECOND \ - } - -static uint16_t g_retry_timeout_ms[] = OC_CLOUD_DEFAULT_RETRY_TIMEOUTS; - -void -oc_cloud_manager_set_retry_timeouts(const uint16_t *timeouts, size_t size) -{ - if (timeouts == NULL) { - uint16_t def[] = OC_CLOUD_DEFAULT_RETRY_TIMEOUTS; - memcpy(g_retry_timeout_ms, def, sizeof(g_retry_timeout_ms)); - return; - } - - assert(size > 0); - assert(size <= OC_ARRAY_SIZE(g_retry_timeout_ms)); - memset(g_retry_timeout_ms, 0, sizeof(g_retry_timeout_ms)); - memcpy(g_retry_timeout_ms, timeouts, size * sizeof(uint16_t)); -} - static oc_event_callback_retval_t cloud_manager_callback_handler_async(void *data) { @@ -123,86 +95,6 @@ cloud_manager_reconnect_async(void *data) return OC_EVENT_DONE; } -static bool -cloud_retry_is_over(uint8_t retry_count) -{ - return retry_count >= OC_ARRAY_SIZE(g_retry_timeout_ms) || - g_retry_timeout_ms[retry_count] == 0; -} - -static bool -OC_NONNULL() - default_schedule_action(oc_cloud_context_t *ctx, uint8_t retry_count, - uint64_t *delay, uint16_t *timeout) -{ - if (cloud_retry_is_over(retry_count)) { - // we have made all attempts, try to select next server - OC_CLOUD_DBG("retry loop over, selecting next server"); - oc_endpoint_addresses_select_next(&ctx->store.ci_servers); - return false; - } - *timeout = (g_retry_timeout_ms[retry_count] / MILLISECONDS_PER_SECOND); - // for delay use timeout/2 value + random [0, timeout/2] - *delay = (uint64_t)(g_retry_timeout_ms[retry_count]) / 2; - // Include a random delay to prevent multiple devices from attempting to - // connect or make requests simultaneously. - *delay += oc_random_value() % *delay; - return true; -} - -static bool -on_action_response_set_retry(oc_cloud_context_t *ctx, oc_cloud_action_t action, - uint8_t retry_count, uint64_t *delay) -{ - bool ok = false; - if (ctx->schedule_action.on_schedule_action != NULL) { - ok = ctx->schedule_action.on_schedule_action( - action, retry_count, delay, &ctx->schedule_action.timeout, - ctx->schedule_action.user_data); - } else { - ok = default_schedule_action(ctx, retry_count, delay, - &ctx->schedule_action.timeout); - } - if (!ok) { - OC_CLOUD_DBG("for retry(%d), action(%s) is stopped", retry_count, - oc_cloud_action_to_str(action)); - return false; - } - OC_CLOUD_DBG( - "for retry(%d), action(%s) is delayed for %llu milliseconds with " - "and set with %u seconds timeout", - retry_count, oc_cloud_action_to_str(action), (long long unsigned)*delay, - ctx->schedule_action.timeout); - return true; -} - -static bool -cloud_schedule_action(oc_cloud_context_t *ctx, oc_cloud_action_t action, - oc_trigger_t callback, bool is_retry) -{ - uint64_t interval = 0; - uint8_t count = 0; - - if (action == OC_CLOUD_ACTION_REFRESH_TOKEN) { - if (is_retry) { - count = ++ctx->retry.refresh_token_count; - } else { - ctx->retry.refresh_token_count = 0; - } - } else { - if (is_retry) { - count = ++ctx->retry.count; - } else { - ctx->retry.count = 0; - } - } - if (!on_action_response_set_retry(ctx, action, count, &interval)) { - return false; - } - oc_reset_delayed_callback_ms(ctx, callback, interval); - return true; -} - static bool cloud_schedule_retry(oc_cloud_context_t *ctx, oc_cloud_action_t action, oc_trigger_t callback) diff --git a/api/cloud/oc_cloud_manager_internal.h b/api/cloud/oc_cloud_manager_internal.h index 50e31dee07..fd7dbf54fb 100644 --- a/api/cloud/oc_cloud_manager_internal.h +++ b/api/cloud/oc_cloud_manager_internal.h @@ -36,9 +36,6 @@ extern "C" { #define USER_ID_KEY "uid" #define EXPIRESIN_KEY "expiresin" -/** Set timeout intervals for the default retry action */ -void oc_cloud_manager_set_retry_timeouts(const uint16_t *timeouts, size_t size); - /** * @brief Parse sign-up response retrieved from the server and store the data to * cloud context. diff --git a/api/cloud/oc_cloud_schedule.c b/api/cloud/oc_cloud_schedule.c new file mode 100644 index 0000000000..909033f20f --- /dev/null +++ b/api/cloud/oc_cloud_schedule.c @@ -0,0 +1,183 @@ +/**************************************************************************** + * + * Copyright (c) 2019 Intel Corporation + * Copyright (c) 2024 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 "oc_config.h" + +#ifdef OC_CLOUD + +#include "api/cloud/oc_cloud_context_internal.h" +#include "api/cloud/oc_cloud_log_internal.h" +#include "api/cloud/oc_cloud_schedule_internal.h" +#include "api/oc_server_api_internal.h" +#include "oc_cloud.h" +#include "util/oc_endpoint_address_internal.h" +#include "util/oc_macros_internal.h" +#include "port/oc_random.h" + +#include + +#define OC_CLOUD_DEFAULT_RETRY_TIMEOUTS \ + { \ + 2 * MILLISECONDS_PER_SECOND, 4 * MILLISECONDS_PER_SECOND, \ + 8 * MILLISECONDS_PER_SECOND, 16 * MILLISECONDS_PER_SECOND, \ + 32 * MILLISECONDS_PER_SECOND, 64 * MILLISECONDS_PER_SECOND \ + } + +static uint16_t g_retry_timeout_ms[OC_CLOUD_RETRY_TIMEOUTS_SIZE] = + OC_CLOUD_DEFAULT_RETRY_TIMEOUTS; + +bool +oc_cloud_set_retry_timeouts(const uint16_t *timeouts, uint8_t size) +{ + if (timeouts == NULL) { + OC_CLOUD_DBG("retry timeouts reset to defaults"); + uint16_t def[] = OC_CLOUD_DEFAULT_RETRY_TIMEOUTS; + memcpy(g_retry_timeout_ms, def, sizeof(g_retry_timeout_ms)); + return true; + } + + if (size > OC_ARRAY_SIZE(g_retry_timeout_ms)) { + OC_ERR("invalid retry timeouts array"); + return false; + } + for (int i = 0; i < size; ++i) { + if (timeouts[i] == 0) { + OC_ERR("invalid retry timeout value"); + return false; + } + } + + memset(g_retry_timeout_ms, 0, sizeof(g_retry_timeout_ms)); + memcpy(g_retry_timeout_ms, timeouts, size * sizeof(uint16_t)); +#if OC_DBG_IS_ENABLED + OC_CLOUD_DBG("retry timeouts set to:"); + for (int i = 0; i < size; ++i) { + OC_CLOUD_DBG("\t%ums", (unsigned)timeouts[i]); + } +#endif /* OC_DBG_IS_ENABLED */ + return true; +} + +int +oc_cloud_get_retry_timeouts(uint16_t *timeouts, uint8_t size) +{ + uint8_t count = 0; + for (; count < (uint8_t)OC_ARRAY_SIZE(g_retry_timeout_ms); ++count) { + if (g_retry_timeout_ms[count] == 0) { + break; + } + } + if (size < count) { + return -1; + } + memcpy(timeouts, g_retry_timeout_ms, count * sizeof(uint16_t)); + return count; +} + +bool +cloud_retry_is_over(uint8_t retry_count) +{ + return retry_count >= OC_ARRAY_SIZE(g_retry_timeout_ms) || + g_retry_timeout_ms[retry_count] == 0; +} + +static bool +OC_NONNULL() + default_schedule_action(oc_cloud_context_t *ctx, uint8_t retry_count, + uint64_t *delay, uint16_t *timeout) +{ + if (cloud_retry_is_over(retry_count)) { + // we have made all attempts, try to select next server + OC_CLOUD_DBG("retry loop over, selecting next server"); + oc_endpoint_addresses_select_next(&ctx->store.ci_servers); + return false; + } + assert(g_retry_timeout_ms[retry_count] >= MILLISECONDS_PER_SECOND); + *timeout = (g_retry_timeout_ms[retry_count] / MILLISECONDS_PER_SECOND); + // for delay use timeout/2 value + random [0, timeout/2] + *delay = (uint64_t)(g_retry_timeout_ms[retry_count]) / 2; + // Include a random delay to prevent multiple devices from attempting to + // connect or make requests simultaneously. + *delay += oc_random_value() % *delay; + return true; +} + +static bool +on_action_response_set_retry(oc_cloud_context_t *ctx, oc_cloud_action_t action, + uint8_t retry_count, uint64_t *delay) +{ + bool ok = false; + if (ctx->schedule_action.on_schedule_action != NULL) { + ok = ctx->schedule_action.on_schedule_action( + action, retry_count, delay, &ctx->schedule_action.timeout, + ctx->schedule_action.user_data); + } else { + ok = default_schedule_action(ctx, retry_count, delay, + &ctx->schedule_action.timeout); + } + if (!ok) { + OC_CLOUD_DBG("for retry(%d), action(%s) is stopped", retry_count, + oc_cloud_action_to_str(action)); + return false; + } + OC_CLOUD_DBG( + "for retry(%d), action(%s) is delayed for %llu milliseconds with " + "and set with %u seconds timeout", + retry_count, oc_cloud_action_to_str(action), (long long unsigned)*delay, + ctx->schedule_action.timeout); + return true; +} + +bool +cloud_schedule_action(oc_cloud_context_t *ctx, oc_cloud_action_t action, + oc_trigger_t callback, bool is_retry) +{ + uint64_t interval = 0; + uint8_t count = 0; + + if (action == OC_CLOUD_ACTION_REFRESH_TOKEN) { + if (is_retry) { + count = ++ctx->retry.refresh_token_count; + } else { + ctx->retry.refresh_token_count = 0; + } + } else { + if (is_retry) { + count = ++ctx->retry.count; + } else { + ctx->retry.count = 0; + } + } + if (!on_action_response_set_retry(ctx, action, count, &interval)) { + return false; + } + oc_reset_delayed_callback_ms(ctx, callback, interval); + return true; +} + +void +oc_cloud_set_schedule_action(oc_cloud_context_t *ctx, + oc_cloud_schedule_action_cb_t on_schedule_action, + void *user_data) +{ + ctx->schedule_action.on_schedule_action = on_schedule_action; + ctx->schedule_action.user_data = user_data; +} + +#endif /* OC_CLOUD */ diff --git a/api/cloud/oc_cloud_schedule_internal.h b/api/cloud/oc_cloud_schedule_internal.h new file mode 100644 index 0000000000..2c06057f4c --- /dev/null +++ b/api/cloud/oc_cloud_schedule_internal.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * + * Copyright (c) 2024 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_CLOUD_SCHEDULE_INTERNAL_H +#define OC_CLOUD_SCHEDULE_INTERNAL_H + +#include "oc_cloud.h" +#include "oc_ri.h" +#include "util/oc_compiler.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MILLISECONDS_PER_SECOND (1000) +#define MILLISECONDS_PER_MINUTE (60 * MILLISECONDS_PER_SECOND) +#define MILLISECONDS_PER_HOUR (60 * MILLISECONDS_PER_MINUTE) + +/** Check if retrying is over (checks whether the timeout value indexed by \p + * retry_count is zero) */ +bool cloud_retry_is_over(uint8_t retry_count); + +/** Set timeout intervals for the default retry action */ +#define OC_CLOUD_RETRY_TIMEOUTS_SIZE (6) + +/** Set timeout intervals for the default retry action */ +bool oc_cloud_set_retry_timeouts(const uint16_t *timeouts, uint8_t size); + +/** Get timeout intervals for the default retry action */ +int oc_cloud_get_retry_timeouts(uint16_t *timeouts, uint8_t size) OC_NONNULL(); + +/** Schedule a cloud action */ +bool cloud_schedule_action(oc_cloud_context_t *ctx, oc_cloud_action_t action, + oc_trigger_t callback, bool is_retry) OC_NONNULL(); + +#ifdef __cplusplus +} +#endif + +#endif /* OC_CLOUD_SCHEDULE_INTERNAL_H */ diff --git a/api/cloud/unittest/cloud_manager_test.cpp b/api/cloud/unittest/cloud_manager_test.cpp index 2924f0386e..f2c3f763b1 100644 --- a/api/cloud/unittest/cloud_manager_test.cpp +++ b/api/cloud/unittest/cloud_manager_test.cpp @@ -21,6 +21,7 @@ #include "api/cloud/oc_cloud_internal.h" #include "api/cloud/oc_cloud_manager_internal.h" #include "api/cloud/oc_cloud_resource_internal.h" +#include "api/cloud/oc_cloud_schedule_internal.h" #include "api/cloud/oc_cloud_store_internal.h" #include "api/oc_rep_internal.h" #include "api/oc_runtime_internal.h" @@ -62,7 +63,7 @@ class TestCloudManager : public testing::Test { // make the second timeout longer, so we can interrupt the retry std::chrono::duration_cast(5s).count(), }; - oc_cloud_manager_set_retry_timeouts(timeouts.data(), timeouts.size()); + ASSERT_TRUE(oc_cloud_set_retry_timeouts(timeouts.data(), timeouts.size())); memset(&m_context, 0, sizeof(m_context)); m_context.cloud_ep = oc_new_endpoint(); @@ -89,7 +90,7 @@ class TestCloudManager : public testing::Test { void TearDown() override { oc_cloud_set_schedule_action(&m_context, nullptr, nullptr); - oc_cloud_manager_set_retry_timeouts(nullptr, 0); + ASSERT_TRUE(oc_cloud_set_retry_timeouts(nullptr, 0)); oc_free_endpoint(m_context.cloud_ep); oc_cloud_store_deinitialize(&m_context.store); @@ -341,7 +342,7 @@ TEST_F(TestCloudManager, cloud_manager_select_next_server_on_retry) // single try -> the cloud server endpoint should be changed after each try uint16_t timeout = std::chrono::duration_cast(kTimeout).count(); - oc_cloud_manager_set_retry_timeouts(&timeout, 1); + ASSERT_TRUE(oc_cloud_set_retry_timeouts(&timeout, 1)); oc_string_view_t uri = OC_STRING_VIEW("coap://13.3.7.187:5683"); oc_uuid_t sid; diff --git a/port/esp32/main/CMakeLists.txt b/port/esp32/main/CMakeLists.txt index 8a6a397427..69adee8db1 100644 --- a/port/esp32/main/CMakeLists.txt +++ b/port/esp32/main/CMakeLists.txt @@ -136,6 +136,7 @@ if (CONFIG_CLOUD) ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud_manager.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud_rd.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud_resource.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud_schedule.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud_store.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/oc_cloud.c ${CMAKE_CURRENT_SOURCE_DIR}/../../../api/cloud/rd_client.c diff --git a/port/windows/vs2015/IoTivity-lite.vcxproj b/port/windows/vs2015/IoTivity-lite.vcxproj index 1443878b0d..679ee6a71a 100644 --- a/port/windows/vs2015/IoTivity-lite.vcxproj +++ b/port/windows/vs2015/IoTivity-lite.vcxproj @@ -213,8 +213,16 @@ + + + - + + + + + + @@ -339,6 +347,7 @@ + diff --git a/port/windows/vs2015/IoTivity-lite.vcxproj.filters b/port/windows/vs2015/IoTivity-lite.vcxproj.filters index c66217321c..e1498a0007 100644 --- a/port/windows/vs2015/IoTivity-lite.vcxproj.filters +++ b/port/windows/vs2015/IoTivity-lite.vcxproj.filters @@ -440,6 +440,9 @@ Core\cloud + + Core\cloud + Core\cloud @@ -479,6 +482,9 @@ Core + + Core + Core @@ -766,10 +772,34 @@ Headers + + Core\cloud + + + Core\cloud + + + Core\cloud + Core\cloud - + + Core\cloud + + + Core\cloud + + + Core\cloud + + + Core\cloud + + + Core\cloud + + Core\cloud @@ -814,12 +844,6 @@ Port - - Core - - - Core - Port