diff --git a/api/cloud/oc_cloud.c b/api/cloud/oc_cloud.c index 297770903..e5d0af58d 100644 --- a/api/cloud/oc_cloud.c +++ b/api/cloud/oc_cloud.c @@ -234,7 +234,7 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, 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_store_initialize(&ctx->store); + oc_cloud_store_reinitialize(&ctx->store); cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); @@ -286,7 +286,7 @@ oc_cloud_update_by_resource(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; - oc_cloud_store_initialize(&ctx->store); + oc_cloud_store_reinitialize(&ctx->store); cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); @@ -460,7 +460,7 @@ oc_cloud_manager_stop(oc_cloud_context_t *ctx) oc_remove_delayed_callback(ctx, start_manager); cloud_rd_reset_context(ctx); cloud_manager_stop(ctx); - oc_cloud_store_initialize(&ctx->store); + 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; diff --git a/api/cloud/oc_cloud_context.c b/api/cloud/oc_cloud_context.c index 3ec89d680..70acc3235 100644 --- a/api/cloud/oc_cloud_context.c +++ b/api/cloud/oc_cloud_context.c @@ -40,6 +40,13 @@ OC_LIST(g_cloud_context_list); OC_MEMB(g_cloud_context_pool, oc_cloud_context_t, OC_MAX_NUM_DEVICES); +void +cloud_retry_reset(oc_cloud_retry_t *retry) +{ + retry->count = 0; + retry->refresh_token_count = 0; +} + static bool need_to_reinitialize_cloud_storage(const oc_cloud_context_t *ctx) { @@ -52,6 +59,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) +{ + const oc_cloud_context_t *ctx = (oc_cloud_context_t *)data; + oc_cloud_store_dump_async(&ctx->store); +} + static void reinitialize_cloud_storage(oc_cloud_context_t *ctx) { @@ -59,7 +73,7 @@ reinitialize_cloud_storage(oc_cloud_context_t *ctx) return; } OC_CLOUD_DBG("reinitializing cloud context in storage"); - oc_cloud_store_initialize(&ctx->store); + oc_cloud_store_initialize(&ctx->store, cloud_on_server_change, ctx); if (oc_cloud_store_dump(&ctx->store) < 0) { OC_CLOUD_ERR("failed to dump cloud store"); } @@ -185,7 +199,7 @@ oc_cloud_context_clear(oc_cloud_context_t *ctx, bool dump_async) ctx->cloud_ep_state = OC_SESSION_DISCONNECTED; cloud_manager_stop(ctx); oc_cloud_deregister_stop(ctx); - oc_cloud_store_initialize(&ctx->store); + oc_cloud_store_reinitialize(&ctx->store); ctx->last_error = 0; ctx->store.cps = 0; ctx->selected_identity_cred_id = -1; @@ -283,11 +297,13 @@ bool oc_cloud_select_server(oc_cloud_context_t *ctx, const oc_cloud_endpoint_t *server) { - if (!oc_list_has_item(ctx->store.ci_servers.endpoints, server)) { - return false; - } - ctx->store.ci_servers.selected = server; - return true; + return oc_cloud_endpoint_select(&ctx->store.ci_servers, server); +} + +const oc_cloud_endpoint_t * +oc_cloud_selected_server(const oc_cloud_context_t *ctx) +{ + return ctx->store.ci_servers.selected; } #endif /* OC_CLOUD */ diff --git a/api/cloud/oc_cloud_context_internal.h b/api/cloud/oc_cloud_context_internal.h index 7a1729a95..f34d667d6 100644 --- a/api/cloud/oc_cloud_context_internal.h +++ b/api/cloud/oc_cloud_context_internal.h @@ -19,6 +19,7 @@ #ifndef OC_CLOUD_CONTEXT_INTERNAL_H #define OC_CLOUD_CONTEXT_INTERNAL_H +#include "api/cloud/oc_cloud_endpoint_internal.h" #include "api/cloud/oc_cloud_store_internal.h" #include "oc_cloud.h" #include "util/oc_compiler.h" @@ -59,6 +60,15 @@ typedef struct oc_cloud_schedule_action_t uint16_t timeout; /**< Timeout for the action in seconds. */ } oc_cloud_schedule_action_t; +typedef struct +{ + uint8_t count; + uint8_t refresh_token_count; +} oc_cloud_retry_t; + +/** Reset the retry counters */ +void cloud_retry_reset(oc_cloud_retry_t *retry) OC_NONNULL(1); + struct oc_cloud_context_t { struct oc_cloud_context_t *next; @@ -67,6 +77,8 @@ struct oc_cloud_context_t oc_cloud_on_status_change_cb_t on_status_change; oc_cloud_store_t store; + oc_cloud_retry_t retry; /**< Retry configuration */ + oc_cloud_keepalive_t keepalive; /**< Keepalive configuration */ oc_cloud_schedule_action_t schedule_action; /**< Schedule action configuration */ @@ -85,9 +97,6 @@ struct oc_cloud_context_t uint32_t time_to_live; /**< Time to live of published resources in seconds */ bool cloud_manager; /**< cloud manager has been started */ - - uint8_t retry_count; - uint8_t retry_refresh_token_count; }; /** diff --git a/api/cloud/oc_cloud_endpoint.c b/api/cloud/oc_cloud_endpoint.c index eb7b2f8d1..826df213b 100644 --- a/api/cloud/oc_cloud_endpoint.c +++ b/api/cloud/oc_cloud_endpoint.c @@ -34,8 +34,8 @@ cloud_endpoint_uri_is_valid(oc_string_view_t uri) } static void -cloud_endpoints_set_selected(oc_cloud_endpoints_t *ce, - const oc_cloud_endpoint_t *selected) +cloud_endpoint_select(oc_cloud_endpoints_t *ce, + const oc_cloud_endpoint_t *selected) { if (ce->selected == selected) { return; @@ -46,6 +46,17 @@ cloud_endpoints_set_selected(oc_cloud_endpoints_t *ce, } } +bool +oc_cloud_endpoint_select(oc_cloud_endpoints_t *ce, + const oc_cloud_endpoint_t *selected) +{ + if (!oc_list_has_item(ce->endpoints, selected)) { + return false; + } + cloud_endpoint_select(ce, selected); + return true; +} + static oc_cloud_endpoint_t * cloud_endpoint_item_allocate_and_add(oc_cloud_endpoints_t *ce, oc_string_view_t uri, oc_uuid_t id) @@ -68,7 +79,7 @@ cloud_endpoint_item_allocate_and_add(oc_cloud_endpoints_t *ce, // automatically select the first endpoint added if (ce->selected == NULL) { assert(oc_list_length(ce->endpoints) == 0); - cloud_endpoints_set_selected(ce, cei); + cloud_endpoint_select(ce, cei); } oc_list_add(ce->endpoints, cei); @@ -182,7 +193,7 @@ oc_cloud_endpoints_clear(oc_cloud_endpoints_t *ce) cloud_endpoint_item_free(cei); cei = oc_list_pop(ce->endpoints); } - cloud_endpoints_set_selected(ce, NULL); + cloud_endpoint_select(ce, NULL); } typedef struct @@ -270,8 +281,8 @@ oc_cloud_endpoint_remove(oc_cloud_endpoints_t *ce, return false; } if (ce->selected == ep) { - cloud_endpoints_set_selected( - ce, cloud_endpoint_item_next(ce, ce->selected, ep_next)); + cloud_endpoint_select(ce, + cloud_endpoint_item_next(ce, ce->selected, ep_next)); } cloud_endpoint_item_free(found); return true; @@ -294,10 +305,20 @@ oc_cloud_endpoint_select_by_uri(oc_cloud_endpoints_t *ce, oc_string_view_t uri) if (found == NULL) { return false; } - cloud_endpoints_set_selected(ce, found); + cloud_endpoint_select(ce, found); return true; } +void +oc_cloud_endpoint_select_next(oc_cloud_endpoints_t *ce) +{ + if (ce->selected == NULL) { + return; + } + cloud_endpoint_select( + ce, cloud_endpoint_item_next(ce, ce->selected, ce->selected->next)); +} + bool oc_cloud_endpoint_is_selected(const oc_cloud_endpoints_t *ce, oc_string_view_t uri) diff --git a/api/cloud/oc_cloud_endpoint_internal.h b/api/cloud/oc_cloud_endpoint_internal.h index 2119a3e84..687247a61 100644 --- a/api/cloud/oc_cloud_endpoint_internal.h +++ b/api/cloud/oc_cloud_endpoint_internal.h @@ -131,14 +131,23 @@ bool oc_cloud_endpoint_remove_by_uri(oc_cloud_endpoints_t *ce, * @brief Select a cloud server endpoint from the list of endpoints * * @param ce cloud endpoints (cannot be NULL) - * @param uri cloud endpoint URI to select + * @param selected cloud endpoint to select (cannot be NULL, must be in the list + * of endpoints) * * @return true if the endpoint was selected - * @return false if the endpoint was not found (the previous selection remains) + * @return false if the endpoint was not found in the list of endpoints (the + * previous selection remains) */ +bool oc_cloud_endpoint_select(oc_cloud_endpoints_t *ce, + const oc_cloud_endpoint_t *selected) OC_NONNULL(); + +/** Select a cloud server endpoint by URI from the list of endpoints */ bool oc_cloud_endpoint_select_by_uri(oc_cloud_endpoints_t *ce, oc_string_view_t uri) OC_NONNULL(); +/** Select the next cloud server endpoint from the list of endpoints */ +void oc_cloud_endpoint_select_next(oc_cloud_endpoints_t *ce) OC_NONNULL(); + /** Check if a cloud server endpoint matching the given URI is selected */ bool oc_cloud_endpoint_is_selected(const oc_cloud_endpoints_t *ce, oc_string_view_t uri) OC_NONNULL(); diff --git a/api/cloud/oc_cloud_manager.c b/api/cloud/oc_cloud_manager.c index 1a52e4ce6..c75094e69 100644 --- a/api/cloud/oc_cloud_manager.c +++ b/api/cloud/oc_cloud_manager.c @@ -59,7 +59,29 @@ 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); -static uint8_t g_retry_timeout[] = { 2, 4, 8, 16, 32, 64 }; +#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) @@ -101,16 +123,26 @@ cloud_manager_reconnect_async(void *data) } static bool -OC_NONNULL() default_schedule_action(uint8_t retry_count, uint64_t *delay, - uint16_t *timeout) +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 (retry_count >= OC_ARRAY_SIZE(g_retry_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_cloud_endpoint_select_next(&ctx->store.ci_servers); return false; } - *timeout = g_retry_timeout[retry_count]; + *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[retry_count]) * MILLISECONDS_PER_SECOND / 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; @@ -127,7 +159,7 @@ on_action_response_set_retry(oc_cloud_context_t *ctx, oc_cloud_action_t action, action, retry_count, delay, &ctx->schedule_action.timeout, ctx->schedule_action.user_data); } else { - ok = default_schedule_action(retry_count, delay, + ok = default_schedule_action(ctx, retry_count, delay, &ctx->schedule_action.timeout); } if (!ok) { @@ -152,15 +184,15 @@ cloud_schedule_action(oc_cloud_context_t *ctx, oc_cloud_action_t action, if (action == OC_CLOUD_ACTION_REFRESH_TOKEN) { if (is_retry) { - count = ++ctx->retry_refresh_token_count; + count = ++ctx->retry.refresh_token_count; } else { - ctx->retry_refresh_token_count = 0; + ctx->retry.refresh_token_count = 0; } } else { if (is_retry) { - count = ++ctx->retry_count; + count = ++ctx->retry.count; } else { - ctx->retry_count = 0; + ctx->retry.count = 0; } } if (!on_action_response_set_retry(ctx, action, count, &interval)) { @@ -187,8 +219,7 @@ cloud_schedule_first_attempt(oc_cloud_context_t *ctx, oc_cloud_action_t action, static void cloud_start_process(oc_cloud_context_t *ctx) { - ctx->retry_count = 0; - ctx->retry_refresh_token_count = 0; + cloud_retry_reset(&ctx->retry); #ifdef OC_SECURITY const oc_sec_pstat_t *pstat = oc_sec_get_pstat(ctx->device); if (pstat->s != OC_DOS_RFNOP && pstat->s != OC_DOS_RFPRO) { @@ -392,7 +423,7 @@ _register_handler(oc_cloud_context_t *ctx, const oc_client_response_t *data, } oc_cloud_store_dump_async(&ctx->store); - ctx->retry_count = 0; + cloud_retry_reset(&ctx->retry); ctx->store.status |= OC_CLOUD_REGISTERED; OC_CLOUD_INFO("Registration succeeded"); *cps = OC_CPS_REGISTERED; @@ -467,7 +498,7 @@ cloud_manager_register_async(void *data) return OC_EVENT_DONE; } - OC_CLOUD_DBG("try register(%d)", ctx->retry_count); + OC_CLOUD_DBG("try register(%d)", ctx->retry.count); oc_cloud_access_conf_t conf; if (!oc_cloud_set_access_conf(ctx, cloud_manager_register_handler, ctx, ctx->schedule_action.timeout, &conf)) { @@ -545,7 +576,7 @@ _login_handler(oc_cloud_context_t *ctx, const oc_client_response_t *data, } oc_cloud_store_dump_async(&ctx->store); - ctx->retry_count = 0; + cloud_retry_reset(&ctx->retry); ctx->store.status |= OC_CLOUD_LOGGED_IN; OC_CLOUD_INFO("Login succeeded"); *cps = cps_ok; @@ -586,7 +617,7 @@ on_keepalive_response_default(oc_cloud_context_t *ctx, bool response_received, { if (response_received) { *next_ping = 20UL * MILLISECONDS_PER_SECOND; - ctx->retry_count = 0; + ctx->retry.count = 0; } else { *next_ping = 4UL * MILLISECONDS_PER_SECOND; uint64_t keepalive_ping_timeout_ms = @@ -595,9 +626,9 @@ on_keepalive_response_default(oc_cloud_context_t *ctx, bool response_received, if (keepalive_ping_timeout_ms >= (*next_ping + MILLISECONDS_PER_SECOND)) { *next_ping = (keepalive_ping_timeout_ms - *next_ping); } - ++ctx->retry_count; + ++ctx->retry.count; } - return ctx->retry_count < OC_ARRAY_SIZE(g_retry_timeout); + return !cloud_retry_is_over(ctx->retry.count); } static bool @@ -693,7 +724,7 @@ cloud_manager_login_async(void *data) return OC_EVENT_DONE; } - OC_CLOUD_DBG("try login(%d)", ctx->retry_count); + OC_CLOUD_DBG("try login(%d)", ctx->retry.count); oc_cloud_access_conf_t conf; if (!oc_cloud_set_access_conf(ctx, cloud_manager_login_handler, ctx, ctx->schedule_action.timeout, &conf)) { @@ -805,8 +836,7 @@ _refresh_token_handler(oc_cloud_context_t *ctx, oc_cloud_store_dump_async(&ctx->store); - ctx->retry_count = 0; - ctx->retry_refresh_token_count = 0; + cloud_retry_reset(&ctx->retry); ctx->store.status |= OC_CLOUD_REFRESHED_TOKEN; OC_CLOUD_INFO("Refreshing of access token succeeded"); @@ -918,7 +948,7 @@ cloud_manager_refresh_token_async(void *data) } oc_remove_delayed_callback(ctx, cloud_manager_send_ping_async); - OC_CLOUD_DBG("try refresh token(%d)", ctx->retry_refresh_token_count); + OC_CLOUD_DBG("try refresh token(%d)", ctx->retry.refresh_token_count); oc_cloud_access_conf_t conf; if (!oc_cloud_set_access_conf(ctx, cloud_manager_refresh_token_handler, ctx, ctx->schedule_action.timeout, &conf)) { diff --git a/api/cloud/oc_cloud_manager_internal.h b/api/cloud/oc_cloud_manager_internal.h index 06b0088b5..3067080b2 100644 --- a/api/cloud/oc_cloud_manager_internal.h +++ b/api/cloud/oc_cloud_manager_internal.h @@ -36,6 +36,9 @@ 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_store.c b/api/cloud/oc_cloud_store.c index 7c80ea711..1f75f17d5 100644 --- a/api/cloud/oc_cloud_store.c +++ b/api/cloud/oc_cloud_store.c @@ -241,13 +241,6 @@ cloud_store_parse_object_array_property(const oc_rep_t *rep, return false; } -static void -cloud_store_on_server_change(void *data) -{ - const oc_cloud_store_t *store = (const oc_cloud_store_t *)data; - oc_cloud_store_dump_async(store); -} - static bool cloud_store_set_servers(oc_cloud_store_t *store, const oc_string_t *selected_uri, @@ -262,8 +255,8 @@ cloud_store_set_servers(oc_cloud_store_t *store, return false; } } - if (!oc_cloud_endpoints_init(&store->ci_servers, cloud_store_on_server_change, - store, oc_string_view2(selected_uri), uuid)) { + if (!oc_cloud_endpoints_reinit(&store->ci_servers, + oc_string_view2(selected_uri), uuid)) { return false; } @@ -384,7 +377,7 @@ 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_store_initialize(store); + oc_cloud_store_reinitialize(store); return false; } OC_DBG("cloud loaded from storage"); @@ -392,17 +385,28 @@ oc_cloud_store_load(oc_cloud_store_t *store) } void -oc_cloud_store_initialize(oc_cloud_store_t *store) +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); +} + +void +oc_cloud_store_initialize(oc_cloud_store_t *store, + on_selected_change_fn_t on_cloud_server_change, + void *on_cloud_server_change_data) { oc_cloud_store_deinitialize(store); - oc_cloud_endpoints_init(&store->ci_servers, cloud_store_on_server_change, - store, OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), + oc_cloud_endpoints_init(&store->ci_servers, on_cloud_server_change, + on_cloud_server_change_data, + OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), OCF_COAPCLOUDCONF_DEFAULT_SID); } void oc_cloud_store_deinitialize(oc_cloud_store_t *store) { + oc_remove_delayed_callback(store, cloud_store_dump_handler); oc_cloud_endpoints_deinit(&store->ci_servers); oc_set_string(&store->auth_provider, NULL, 0); oc_set_string(&store->uid, NULL, 0); diff --git a/api/cloud/oc_cloud_store_internal.h b/api/cloud/oc_cloud_store_internal.h index bab05f7ab..f052a3434 100644 --- a/api/cloud/oc_cloud_store_internal.h +++ b/api/cloud/oc_cloud_store_internal.h @@ -54,7 +54,12 @@ typedef struct oc_cloud_store_t } oc_cloud_store_t; /** @brief Set store data to default values */ -void oc_cloud_store_initialize(oc_cloud_store_t *store) OC_NONNULL(); +void oc_cloud_store_initialize(oc_cloud_store_t *store, + on_selected_change_fn_t on_cloud_server_change, + void *on_cloud_server_change_dat) OC_NONNULL(1); + +/** @brief Reinitialize store data */ +void oc_cloud_store_reinitialize(oc_cloud_store_t *store) OC_NONNULL(); /** @brief Deallocate allocated data */ void oc_cloud_store_deinitialize(oc_cloud_store_t *store) OC_NONNULL(); diff --git a/api/cloud/unittest/cloud_context_test.cpp b/api/cloud/unittest/cloud_context_test.cpp index fb47683e0..4e30ba966 100644 --- a/api/cloud/unittest/cloud_context_test.cpp +++ b/api/cloud/unittest/cloud_context_test.cpp @@ -48,7 +48,7 @@ TEST_F(TestCloudContext, GetCisAndSid) { oc_cloud_context_t ctx{}; - oc_cloud_store_initialize(&ctx.store); + oc_cloud_store_initialize(&ctx.store, nullptr, nullptr); auto cis = OC_STRING_VIEW("cis"); oc_uuid_t sid; oc_gen_uuid(&sid); diff --git a/api/cloud/unittest/cloud_manager_test.cpp b/api/cloud/unittest/cloud_manager_test.cpp index 251229e25..5e348398d 100644 --- a/api/cloud/unittest/cloud_manager_test.cpp +++ b/api/cloud/unittest/cloud_manager_test.cpp @@ -31,6 +31,7 @@ #include "tests/gtest/Device.h" #include "tests/gtest/RepPool.h" +#include #include #include #include @@ -40,6 +41,8 @@ using namespace std::chrono_literals; #ifndef OC_SECURITY +// cannot use lower value than 1s because oc_cloud_schedule_action_t::timeout is +// in seconds static constexpr auto kTimeout = 1s; class TestCloudManager : public testing::Test { @@ -52,14 +55,22 @@ class TestCloudManager : public testing::Test { void SetUp() override { + std::array timeouts = { + std::chrono::duration_cast(kTimeout).count(), + // 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()); + 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); + oc_cloud_store_initialize(&m_context.store, nullptr, nullptr); oc_string_view_t ep = OC_STRING_VIEW("coap://224.0.1.187:5683"); oc_uuid_t sid; oc_gen_uuid(&sid); - ASSERT_TRUE(oc_cloud_endpoint_add(&m_context.store.ci_servers, ep, sid)); + ASSERT_NE(nullptr, + oc_cloud_endpoint_add(&m_context.store.ci_servers, ep, sid)); ASSERT_TRUE( oc_cloud_endpoint_select_by_uri(&m_context.store.ci_servers, ep)); std::string uid = "501"; @@ -73,6 +84,8 @@ class TestCloudManager : public testing::Test { void TearDown() override { + oc_cloud_manager_set_retry_timeouts(nullptr, 0); + oc_free_endpoint(m_context.cloud_ep); oc_cloud_store_deinitialize(&m_context.store); oc::TestDevice::Reset(); @@ -108,8 +121,8 @@ TEST_F(TestCloudManager, cloud_manager_start_initialized_schedule_turnoff) cloud_manager_stop(&m_context); // Then - EXPECT_EQ(0, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_EQ(0, m_context.retry.count); + 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); @@ -145,8 +158,8 @@ TEST_F(TestCloudManager, cloud_manager_stop(&m_context); // Then - EXPECT_EQ(1, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_EQ(1, m_context.retry.count); + 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); @@ -179,8 +192,8 @@ TEST_F(TestCloudManager, cloud_manager_start_initialized_without_retry_f) cloud_manager_stop(&m_context); // Then - EXPECT_LT(0, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_LT(0, m_context.retry.count); + 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); @@ -193,15 +206,15 @@ TEST_F(TestCloudManager, cloud_manager_start_initialized_f) m_context.store.status = OC_CLOUD_INITIALIZED; m_context.store.cps = OC_CPS_READYTOREGISTER; cloud_manager_start(&m_context); - // by default: first retry should happen after 2 seconds + jitter + // by default: first retry should happen timeout + jitter // ([timeout/2..timeout]) (see default_schedule_action) - oc::TestDevice::PoolEventsMsV1(/*timeout*/ 2s + /*max possible jitter*/ 2s, - true); + oc::TestDevice::PoolEventsMsV1( + /*timeout*/ kTimeout + /*max possible jitter*/ kTimeout, true); cloud_manager_stop(&m_context); // Then - EXPECT_LT(0, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_LT(0, m_context.retry.count); + EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_OK, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED, m_context.store.status); } @@ -232,8 +245,8 @@ TEST_F(TestCloudManager, cloud_manager_start_registered_without_retry_and_uid_f) cloud_manager_stop(&m_context); // Then - EXPECT_LT(0, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_LT(0, m_context.retry.count); + EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_ERROR_CONNECT, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED | OC_CLOUD_FAILURE, m_context.store.status); @@ -247,15 +260,15 @@ TEST_F(TestCloudManager, cloud_manager_start_registered_f) m_context.store.status = OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED; m_context.store.expires_in = -1; cloud_manager_start(&m_context); - // by default: first retry should happen after 2 seconds + jitter + // by default: first retry should happen timeout + jitter // ([timeout/2..timeout]) (see default_schedule_action) - oc::TestDevice::PoolEventsMsV1(/*timeout*/ 2s + /*max possible jitter*/ 2s, - true); + oc::TestDevice::PoolEventsMsV1( + /*timeout*/ kTimeout + /*max possible jitter*/ kTimeout, true); cloud_manager_stop(&m_context); // Then - EXPECT_LT(0, m_context.retry_count); - EXPECT_EQ(0, m_context.retry_refresh_token_count); + EXPECT_LT(0, m_context.retry.count); + EXPECT_EQ(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_OK, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED, m_context.store.status); } @@ -286,8 +299,8 @@ TEST_F(TestCloudManager, cloud_manager_stop(&m_context); // Then - EXPECT_EQ(0, m_context.retry_count); - EXPECT_LT(0, m_context.retry_refresh_token_count); + EXPECT_EQ(0, m_context.retry.count); + EXPECT_LT(0, m_context.retry.refresh_token_count); 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); @@ -300,19 +313,47 @@ TEST_F(TestCloudManager, cloud_manager_start_with_refresh_token_f) // When m_context.store.status = OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED; cloud_manager_start(&m_context); - // by default: first retry should happen after 2 seconds + jitter + // by default: first retry should happen timeout + jitter // ([timeout/2..timeout]) (see default_schedule_action) - oc::TestDevice::PoolEventsMsV1(/*timeout*/ 2s + /*max possible jitter*/ 2s, - true); + oc::TestDevice::PoolEventsMsV1( + /*timeout*/ kTimeout + /*max possible jitter*/ kTimeout, true); cloud_manager_stop(&m_context); // Then - EXPECT_EQ(0, m_context.retry_count); - EXPECT_LT(0, m_context.retry_refresh_token_count); + EXPECT_EQ(0, m_context.retry.count); + EXPECT_LT(0, m_context.retry.refresh_token_count); EXPECT_EQ(CLOUD_OK, m_context.last_error); EXPECT_EQ(OC_CLOUD_INITIALIZED | OC_CLOUD_REGISTERED, m_context.store.status); } +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); + + 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_cloud_endpoint_add(&m_context.store.ci_servers, uri, sid); + ASSERT_NE(nullptr, ep); + ASSERT_FALSE(oc_cloud_endpoint_is_selected(&m_context.store.ci_servers, uri)); + + // When + m_context.store.status = OC_CLOUD_INITIALIZED; + m_context.store.cps = OC_CPS_READYTOREGISTER; + cloud_manager_start(&m_context); + // 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); + + // Then + EXPECT_TRUE(oc_cloud_endpoint_is_selected(&m_context.store.ci_servers, uri)); +} + #endif /* !OC_SECURITY */ class TestCloudManagerData : public testing::Test { diff --git a/api/cloud/unittest/cloud_resource_test.cpp b/api/cloud/unittest/cloud_resource_test.cpp index cfb2d1826..45f9aebb4 100644 --- a/api/cloud/unittest/cloud_resource_test.cpp +++ b/api/cloud/unittest/cloud_resource_test.cpp @@ -55,7 +55,7 @@ struct CloudResourceData static std::vector decodeArray(const oc_rep_t *servers) { std::vector result{}; - for (const oc_rep_t *server = servers; server != NULL; + 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")); @@ -264,7 +264,7 @@ TEST_F(TestCloudResourceWithServer, GetRequest_NoCloudServers) EXPECT_EQ(0, crd.clec); // restore default store - oc_cloud_store_initialize(&ctx->store); + oc_cloud_store_initialize(&ctx->store, nullptr, nullptr); } template diff --git a/api/cloud/unittest/cloud_store_test.cpp b/api/cloud/unittest/cloud_store_test.cpp index 9269f4f29..3842e3083 100644 --- a/api/cloud/unittest/cloud_store_test.cpp +++ b/api/cloud/unittest/cloud_store_test.cpp @@ -123,7 +123,7 @@ class TestCloudStore : public testing::Test { static void validateDefaults(const oc_cloud_store_t *store) { oc_cloud_store_t def{}; - oc_cloud_store_initialize(&def); + oc_cloud_store_initialize(&def, nullptr, nullptr); compareStores(&def, store); freeStore(&def); } diff --git a/api/cloud/unittest/cloud_test.cpp b/api/cloud/unittest/cloud_test.cpp index fe563dd6a..6907866a4 100644 --- a/api/cloud/unittest/cloud_test.cpp +++ b/api/cloud/unittest/cloud_test.cpp @@ -744,6 +744,7 @@ TEST_F(TestCloud, EndpointAPI) // remove default oc_cloud_endpoints_clear(&ctx->store.ci_servers); // no enpoint selected -> both cis and sid should be nullptr + EXPECT_EQ(nullptr, oc_cloud_selected_server(ctx)); EXPECT_EQ(nullptr, oc_cloud_get_cis(ctx)); EXPECT_EQ(nullptr, oc_cloud_get_sid(ctx)); @@ -770,6 +771,7 @@ TEST_F(TestCloud, EndpointAPI) #endif /* OC_DYNAMIC_ALLOCATION */ // first item added to empty list should be selected + EXPECT_EQ(ep1, oc_cloud_selected_server(ctx)); EXPECT_STREQ(uri1.c_str(), oc_cloud_get_cis(ctx)); EXPECT_TRUE(oc_uuid_is_equal(uid1, *oc_cloud_get_sid(ctx))); @@ -777,6 +779,7 @@ TEST_F(TestCloud, EndpointAPI) ASSERT_TRUE(oc_cloud_remove_server(ctx, ep1)); // next endpoint should be selected + EXPECT_EQ(ep2, oc_cloud_selected_server(ctx)); EXPECT_STREQ(uri2.c_str(), oc_cloud_get_cis(ctx)); EXPECT_TRUE(oc_uuid_is_equal(uid2, *oc_cloud_get_sid(ctx))); @@ -817,9 +820,11 @@ TEST_F(TestCloud, EndpointAPI) ASSERT_NE(nullptr, toSelect); EXPECT_TRUE(oc_cloud_select_server(ctx, toSelect)); #ifdef OC_DYNAMIC_ALLOCATION + EXPECT_EQ(ep3, oc_cloud_selected_server(ctx)); EXPECT_STREQ(uri3.c_str(), oc_cloud_get_cis(ctx)); EXPECT_TRUE(oc_uuid_is_equal(uid3, *oc_cloud_get_sid(ctx))); #else /* !OC_DYNAMIC_ALLOCATION */ + EXPECT_EQ(ep2, oc_cloud_selected_server(ctx)); EXPECT_STREQ(uri2.c_str(), oc_cloud_get_cis(ctx)); EXPECT_TRUE(oc_uuid_is_equal(uid2, *oc_cloud_get_sid(ctx))); #endif /* OC_DYNAMIC_ALLOCATION */ diff --git a/include/oc_cloud.h b/include/oc_cloud.h index 9039e9c88..eec292a98 100644 --- a/include/oc_cloud.h +++ b/include/oc_cloud.h @@ -495,14 +495,20 @@ oc_cloud_endpoint_t *oc_cloud_add_server(oc_cloud_context_t *ctx, * @param ctx cloud context (cannot be NULL) * @param ce cloud endpoint to remove * + * @return true if the endpoint address was removed from the list of cloud + * servers + * @return false on failure + * * @note The servers are stored in a list. If the selected server is removed, * then next server in the list will be selected. If the selected server is the * last item in the list, then the first server in the list will be selected (if * it exists). * - * @return true if the endpoint address was removed from the list of cloud - * servers - * @return false on failure + * @note The server is cached by the cloud, so if you remove the selected server + * during cloud provisioning then it might be necessary to restart the cloud + * manager for the change to take effect. + * + * @see oc_cloud_manager_restart */ OC_API bool oc_cloud_remove_server(oc_cloud_context_t *ctx, @@ -563,14 +569,30 @@ void oc_cloud_iterate_servers(const oc_cloud_context_t *ctx, * @note The address of the selected server will be returned as the cis value * and the identity of the selected server will be returned as the sid value. * + * @note The server is cached by the cloud, so if you the selected server during + * cloud provisioning then it might be necessary to restart the cloud manager + * for the change to take effect. + * * @see oc_cloud_remove_server * @see oc_cloud_get_cis * @see oc_cloud_get_sid + * @see oc_cloud_manager_restart */ OC_API bool oc_cloud_select_server(oc_cloud_context_t *ctx, const oc_cloud_endpoint_t *server) OC_NONNULL(); +/** + * @brief Get the selected cloud server. + * + * @param ctx cloud context (cannot be NULL) + * @return oc_cloud_endpoint_t* pointer to the selected cloud server + * @return NULL if no cloud server is selected + */ +OC_API +const oc_cloud_endpoint_t *oc_cloud_selected_server( + const oc_cloud_context_t *ctx) OC_NONNULL(); + /** @} */ // end of cloud_servers #ifdef __cplusplus