From d4555c1878ebf4d99e3a553ebae148cc216c8dac Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Tue, 20 Feb 2024 15:58:14 +0100 Subject: [PATCH] cloud: dump storage on server endpoint selection change --- api/cloud/oc_cloud.c | 11 ++- api/cloud/oc_cloud_apis.c | 8 +- api/cloud/oc_cloud_context.c | 1 - api/cloud/oc_cloud_endpoint.c | 42 +++++++--- api/cloud/oc_cloud_endpoint_internal.h | 16 +++- api/cloud/oc_cloud_resource.c | 79 ++++++++--------- api/cloud/oc_cloud_store.c | 47 +++++++---- api/cloud/unittest/cloud_endpoint_test.cpp | 12 +-- api/cloud/unittest/cloud_manager_test.cpp | 4 +- api/cloud/unittest/cloud_resource_test.cpp | 98 ++++++++++++++++++++-- api/cloud/unittest/cloud_store_test.cpp | 65 ++++++++++++-- api/cloud/unittest/cloud_test.cpp | 36 +++++++- api/oc_uuid.c | 8 +- api/unittest/uuidtest.cpp | 32 +++++++ include/oc_cloud.h | 2 +- 15 files changed, 354 insertions(+), 107 deletions(-) diff --git a/api/cloud/oc_cloud.c b/api/cloud/oc_cloud.c index 48b148006..297770903 100644 --- a/api/cloud/oc_cloud.c +++ b/api/cloud/oc_cloud.c @@ -187,7 +187,9 @@ cloud_set_cloudconf(oc_cloud_context_t *ctx, const oc_cloud_conf_update_t *data) oc_cloud_endpoints_deinit(&ctx->store.ci_servers); // oc_cloud_endpoints_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_endpoints_init(&ctx->store.ci_servers, cis, data->sid)) { + if (!oc_cloud_endpoints_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"); } } @@ -218,9 +220,10 @@ oc_cloud_provision_conf_resource(oc_cloud_context_t *ctx, const char *server, return -1; } size_t server_id_len = oc_strnlen(server_id, OC_UUID_LEN); - oc_uuid_t sid = { 0 }; - if (server_id_len != (OC_UUID_LEN - 1) || - oc_str_to_uuid_v1(server_id, server_id_len, &sid) < 0) { + 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); return -1; } size_t auth_provider_len = oc_strnlen_s(auth_provider, OC_MAX_STRING_LENGTH); diff --git a/api/cloud/oc_cloud_apis.c b/api/cloud/oc_cloud_apis.c index 4eda4a17d..2bb147414 100644 --- a/api/cloud/oc_cloud_apis.c +++ b/api/cloud/oc_cloud_apis.c @@ -333,18 +333,12 @@ int oc_cloud_discover_resources(const oc_cloud_context_t *ctx, oc_discovery_all_handler_t handler, void *user_data) { - if (!ctx) { + if (ctx == NULL || (ctx->store.status & OC_CLOUD_LOGGED_IN) == 0) { return -1; } - - if (!(ctx->store.status & OC_CLOUD_LOGGED_IN)) { - return -1; - } - if (oc_do_ip_discovery_all_at_endpoint(handler, ctx->cloud_ep, user_data)) { return 0; } - return -1; } diff --git a/api/cloud/oc_cloud_context.c b/api/cloud/oc_cloud_context.c index ea44192b5..3ec89d680 100644 --- a/api/cloud/oc_cloud_context.c +++ b/api/cloud/oc_cloud_context.c @@ -287,7 +287,6 @@ oc_cloud_select_server(oc_cloud_context_t *ctx, return false; } ctx->store.ci_servers.selected = server; - // TODO: dump to storage on selection change return true; } diff --git a/api/cloud/oc_cloud_endpoint.c b/api/cloud/oc_cloud_endpoint.c index 91432c041..eb7b2f8d1 100644 --- a/api/cloud/oc_cloud_endpoint.c +++ b/api/cloud/oc_cloud_endpoint.c @@ -33,6 +33,19 @@ cloud_endpoint_uri_is_valid(oc_string_view_t uri) return uri.length > 0 && uri.length < OC_ENDPOINT_MAX_ENDPOINT_URI_LENGTH; } +static void +cloud_endpoints_set_selected(oc_cloud_endpoints_t *ce, + const oc_cloud_endpoint_t *selected) +{ + if (ce->selected == selected) { + return; + } + ce->selected = selected; + if (ce->on_selected_change != NULL) { + ce->on_selected_change(ce->on_selected_change_data); + } +} + 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) @@ -55,7 +68,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); - ce->selected = cei; + cloud_endpoints_set_selected(ce, cei); } oc_list_add(ce->endpoints, cei); @@ -88,8 +101,10 @@ oc_cloud_endpoint_id(const oc_cloud_endpoint_t *ce) } bool -oc_cloud_endpoints_init(oc_cloud_endpoints_t *ce, oc_string_view_t default_uri, - oc_uuid_t default_id) +oc_cloud_endpoints_init(oc_cloud_endpoints_t *ce, + on_selected_change_fn_t on_selected_change, + void *on_selected_change_data, + oc_string_view_t default_uri, oc_uuid_t default_id) { memset(ce, 0, sizeof(oc_cloud_endpoints_t)); OC_LIST_STRUCT_INIT(ce, endpoints); @@ -101,6 +116,9 @@ oc_cloud_endpoints_init(oc_cloud_endpoints_t *ce, oc_string_view_t default_uri, return false; } } + + ce->on_selected_change = on_selected_change; + ce->on_selected_change_data = on_selected_change_data; return true; } @@ -122,7 +140,9 @@ oc_cloud_endpoints_reinit(oc_cloud_endpoints_t *ce, oc_string_view_t default_uri, oc_uuid_t default_id) { oc_cloud_endpoints_deinit(ce); - return oc_cloud_endpoints_init(ce, default_uri, default_id); + return oc_cloud_endpoints_init(ce, ce->on_selected_change, + ce->on_selected_change_data, default_uri, + default_id); } size_t @@ -162,7 +182,7 @@ oc_cloud_endpoints_clear(oc_cloud_endpoints_t *ce) cloud_endpoint_item_free(cei); cei = oc_list_pop(ce->endpoints); } - ce->selected = NULL; + cloud_endpoints_set_selected(ce, NULL); } typedef struct @@ -250,7 +270,8 @@ oc_cloud_endpoint_remove(oc_cloud_endpoints_t *ce, return false; } if (ce->selected == ep) { - ce->selected = cloud_endpoint_item_next(ce, ce->selected, ep_next); + cloud_endpoints_set_selected( + ce, cloud_endpoint_item_next(ce, ce->selected, ep_next)); } cloud_endpoint_item_free(found); return true; @@ -273,7 +294,7 @@ oc_cloud_endpoint_select_by_uri(oc_cloud_endpoints_t *ce, oc_string_view_t uri) if (found == NULL) { return false; } - ce->selected = found; + cloud_endpoints_set_selected(ce, found); return true; } @@ -321,10 +342,11 @@ cloud_encode_server(oc_cloud_endpoint_t *endpoint, void *data) encoder->ci_servers, &endpoint_map, CborIndefiniteLength); oc_rep_object_set_text_string(&endpoint_map, "uri", OC_CHAR_ARRAY_LEN("uri"), uri.data, uri.length); - char sid_str[OC_UUID_LEN]; - oc_uuid_to_str(&endpoint->id, sid_str, OC_UUID_LEN); + char sid_str[OC_UUID_LEN] = { 0 }; + int sid_str_len = oc_uuid_to_str_v1(&endpoint->id, sid_str, OC_UUID_LEN); + assert(sid_str_len > 0); oc_rep_object_set_text_string(&endpoint_map, "id", OC_CHAR_ARRAY_LEN("id"), - sid_str, strlen(sid_str)); + sid_str, (size_t)sid_str_len); encoder->error |= oc_rep_encoder_close_container(encoder->ci_servers, &endpoint_map); diff --git a/api/cloud/oc_cloud_endpoint_internal.h b/api/cloud/oc_cloud_endpoint_internal.h index ab1d1f574..2119a3e84 100644 --- a/api/cloud/oc_cloud_endpoint_internal.h +++ b/api/cloud/oc_cloud_endpoint_internal.h @@ -45,15 +45,23 @@ struct oc_cloud_endpoint_t oc_uuid_t id; ///< identity of the cloud server }; +typedef void (*on_selected_change_fn_t)(void *data); + typedef struct { const oc_cloud_endpoint_t *selected; ///< currently selected server endpoint - OC_LIST_STRUCT(endpoints); ///< list of server endpoints + on_selected_change_fn_t + on_selected_change; ///< callback invoked when the selected endpoint changes + void * + on_selected_change_data; ///< data passed to the on_selected_change callback + OC_LIST_STRUCT(endpoints); ///< list of server endpoints } oc_cloud_endpoints_t; /** Initialize cloud server endpoints * * @param ce cloud endpoints (cannot be NULL) + * @param on_selected_change callback invoked when the selected endpoint changes + * @param on_selected_change_data data passed to the on_selected_change callback * @param default_uri URI of the default endpoint to add (if the URI is empty * the list will remain empty) * @param default_id identity of the default endpoint to add @@ -61,8 +69,10 @@ typedef struct * @return false on failure */ bool oc_cloud_endpoints_init(oc_cloud_endpoints_t *ce, + on_selected_change_fn_t on_selected_change, + void *on_selected_change_data, oc_string_view_t default_uri, oc_uuid_t default_id) - OC_NONNULL(); + OC_NONNULL(1); /** Deinitialize cloud server endpoints */ void oc_cloud_endpoints_deinit(oc_cloud_endpoints_t *ce) OC_NONNULL(); @@ -70,7 +80,7 @@ void oc_cloud_endpoints_deinit(oc_cloud_endpoints_t *ce) OC_NONNULL(); /** Deinitialize and reinitialize cloud server endpoints */ bool oc_cloud_endpoints_reinit(oc_cloud_endpoints_t *ce, oc_string_view_t default_uri, - oc_uuid_t default_id) OC_NONNULL(); + oc_uuid_t default_id) OC_NONNULL(1); /** Get the number of cloud server endpoints */ size_t oc_cloud_endpoints_size(const oc_cloud_endpoints_t *ce) OC_NONNULL(); diff --git a/api/cloud/oc_cloud_resource.c b/api/cloud/oc_cloud_resource.c index 2d928320f..108ede4ba 100644 --- a/api/cloud/oc_cloud_resource.c +++ b/api/cloud/oc_cloud_resource.c @@ -69,9 +69,6 @@ oc_cloud_encode(const oc_cloud_context_t *ctx) oc_string_view_t cis = oc_string_view2(oc_cloud_endpoint_selected_address(&ctx->store.ci_servers)); oc_rep_set_text_string_v1(root, cis, cis.data, cis.length); - g_err |= oc_cloud_endpoints_encode( - oc_rep_object(root), &ctx->store.ci_servers, - OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_CISERVERS), true); const oc_uuid_t *sid = oc_cloud_endpoint_selected_id(&ctx->store.ci_servers); char sid_str[OC_UUID_LEN] = { 0 }; @@ -82,6 +79,10 @@ oc_cloud_encode(const oc_cloud_context_t *ctx) } oc_rep_set_text_string_v1(root, sid, sid_str, sid_str_len); + g_err |= oc_cloud_endpoints_encode( + oc_rep_object(root), &ctx->store.ci_servers, + OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_CISERVERS), true); + oc_rep_set_int(root, clec, (int)ctx->last_error); OC_CLOUD_DBG( @@ -118,49 +119,49 @@ cloud_resource_get(oc_request_t *request, oc_interface_mask_t interface, oc_send_response_with_callback(request, OC_STATUS_OK, true); } +static const oc_string_t * +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); + if (rep == NULL || + (cannotBeEmpty && oc_string_is_empty(&rep->value.string))) { + return NULL; + } + return &rep->value.string; +} + static bool cloud_update_from_request(oc_cloud_context_t *ctx, const oc_request_t *request) { oc_cloud_conf_update_t data; memset(&data, 0, sizeof(data)); - const oc_rep_t *rep = oc_rep_get( - request->request_payload, OC_REP_STRING, OCF_COAPCLOUDCONF_PROP_ACCESSTOKEN, - OC_CHAR_ARRAY_LEN(OCF_COAPCLOUDCONF_PROP_ACCESSTOKEN)); - if (rep != NULL && !oc_string_is_empty(&rep->value.string)) { - data.access_token = &rep->value.string; - } - - rep = oc_rep_get(request->request_payload, OC_REP_STRING, - OCF_COAPCLOUDCONF_PROP_AUTHPROVIDER, - OC_CHAR_ARRAY_LEN(OCF_COAPCLOUDCONF_PROP_AUTHPROVIDER)); - if (rep != NULL && !oc_string_is_empty(&rep->value.string)) { - data.auth_provider = &rep->value.string; - } - - rep = oc_rep_get(request->request_payload, OC_REP_STRING, - OCF_COAPCLOUDCONF_PROP_CISERVER, - OC_CHAR_ARRAY_LEN(OCF_COAPCLOUDCONF_PROP_CISERVER)); - if (rep != NULL) { - data.ci_server = &rep->value.string; - } + data.access_token = resource_payload_get_string_property( + request->request_payload, + OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_ACCESSTOKEN), true); + data.auth_provider = resource_payload_get_string_property( + request->request_payload, + OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_AUTHPROVIDER), true); + data.ci_server = resource_payload_get_string_property( + request->request_payload, OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_CISERVER), + false); // OCF 2.0 spec version added sid property. - bool has_sid = false; - rep = oc_rep_get(request->request_payload, OC_REP_STRING, - OCF_COAPCLOUDCONF_PROP_SERVERID, - OC_CHAR_ARRAY_LEN(OCF_COAPCLOUDCONF_PROP_SERVERID)); - if (rep != NULL && !oc_string_is_empty(&rep->value.string)) { - has_sid = true; - if (oc_str_to_uuid_v1(oc_string(rep->value.string), - oc_string_len(rep->value.string), &data.sid) < 0) { - OC_ERR("Error parsing sid(%s)", oc_string(rep->value.string)); + const oc_string_t *sid = resource_payload_get_string_property( + request->request_payload, OC_STRING_VIEW(OCF_COAPCLOUDCONF_PROP_SERVERID), + true); + 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); return false; } } if (data.ci_server != NULL && (oc_string_is_empty(data.ci_server) || - (data.access_token != NULL && has_sid))) { + (data.access_token != NULL && sid != NULL))) { oc_cloud_update_by_resource(ctx, &data); return true; } @@ -217,13 +218,13 @@ cloud_resource_post(oc_request_t *request, oc_interface_mask_t interface, return; } - bool changed = cloud_update_from_request(ctx, request); - oc_cloud_encode(ctx); - oc_send_response_with_callback( - request, changed ? OC_STATUS_CHANGED : OC_STATUS_BAD_REQUEST, true); - if (changed) { - oc_cloud_store_dump_async(&ctx->store); + if (!cloud_update_from_request(ctx, request) || !oc_cloud_encode(ctx)) { + oc_send_response_with_callback(request, OC_STATUS_BAD_REQUEST, true); + return; } + + oc_send_response_with_callback(request, OC_STATUS_CHANGED, true); + oc_cloud_store_dump_async(&ctx->store); } void diff --git a/api/cloud/oc_cloud_store.c b/api/cloud/oc_cloud_store.c index 09151c237..7c80ea711 100644 --- a/api/cloud/oc_cloud_store.c +++ b/api/cloud/oc_cloud_store.c @@ -52,6 +52,8 @@ check oc_config.h and make sure OC_STORAGE is defined if OC_CLOUD is defined. #define CLOUD_EXPIRES_IN "expires_in" #define CLOUD_STATUS "status" #define CLOUD_CPS "cps" +#define CLOUD_ENDPOINT_URI "uri" +#define CLOUD_ENDPOINT_ID "id" static void rep_set_text_string(CborEncoder *object_map, oc_string_view_t key, @@ -129,7 +131,7 @@ oc_cloud_store_dump(const oc_cloud_store_t *store) static oc_event_callback_retval_t cloud_store_dump_handler(void *data) { - const oc_cloud_store_t *store = (oc_cloud_store_t *)data; + const oc_cloud_store_t *store = (const oc_cloud_store_t *)data; if (oc_cloud_store_dump(store) < 0) { OC_CLOUD_ERR("failed to dump store"); } @@ -239,6 +241,13 @@ 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, @@ -246,13 +255,15 @@ cloud_store_set_servers(oc_cloud_store_t *store, { oc_cloud_endpoints_deinit(&store->ci_servers); oc_uuid_t uuid = OCF_COAPCLOUDCONF_DEFAULT_SID; - if (selected_id != NULL && - oc_str_to_uuid_v1(oc_string(*selected_id), oc_string_len(*selected_id), - &uuid) < 0) { - return false; + 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_cloud_endpoints_init(&store->ci_servers, - oc_string_view2(selected_uri), uuid)) { + if (!oc_cloud_endpoints_init(&store->ci_servers, cloud_store_on_server_change, + store, oc_string_view2(selected_uri), uuid)) { return false; } @@ -262,24 +273,26 @@ cloud_store_set_servers(oc_cloud_store_t *store, 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, "uri", - OC_CHAR_ARRAY_LEN("uri")); + 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("cannot parse cloud server uri"); + 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, "id", - OC_CHAR_ARRAY_LEN("id")); + 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("cannot parse cloud server id"); + OC_ERR("cloud server id missing"); continue; } - if (oc_str_to_uuid_v1(oc_string(rep->value.string), - oc_string_len(rep->value.string), &uuid) < 0) { + 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_cloud_endpoint_contains(&store->ci_servers, uri)) { continue; } @@ -382,8 +395,8 @@ void oc_cloud_store_initialize(oc_cloud_store_t *store) { oc_cloud_store_deinitialize(store); - oc_cloud_endpoints_init(&store->ci_servers, - OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), + oc_cloud_endpoints_init(&store->ci_servers, cloud_store_on_server_change, + store, OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), OCF_COAPCLOUDCONF_DEFAULT_SID); } diff --git a/api/cloud/unittest/cloud_endpoint_test.cpp b/api/cloud/unittest/cloud_endpoint_test.cpp index 95eacbc46..430599dda 100644 --- a/api/cloud/unittest/cloud_endpoint_test.cpp +++ b/api/cloud/unittest/cloud_endpoint_test.cpp @@ -36,7 +36,8 @@ class TestCloudEndpoint : public testing::Test { void SetUp() override { - ASSERT_TRUE(oc_cloud_endpoints_init(&ce, OC_STRING_VIEW_NULL, {})); + ASSERT_TRUE( + oc_cloud_endpoints_init(&ce, nullptr, nullptr, OC_STRING_VIEW_NULL, {})); } void TearDown() override { oc_cloud_endpoints_deinit(&ce); } @@ -59,9 +60,9 @@ encodeCloudEndpointItem(const std::string &uri, oc_uuid_t id) TEST_F(TestCloudEndpoint, InitWithDefault) { oc_cloud_endpoints_t ce{}; - ASSERT_TRUE( - oc_cloud_endpoints_init(&ce, OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), - OCF_COAPCLOUDCONF_DEFAULT_SID)); + ASSERT_TRUE(oc_cloud_endpoints_init( + &ce, nullptr, nullptr, OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS), + OCF_COAPCLOUDCONF_DEFAULT_SID)); EXPECT_TRUE(oc_cloud_endpoint_contains( &ce, OC_STRING_VIEW(OCF_COAPCLOUDCONF_DEFAULT_CIS))); auto *selected = ce.selected; @@ -76,7 +77,8 @@ TEST_F(TestCloudEndpoint, Init_FailInvalidDefault) oc_cloud_endpoints_t ce{}; std::string tooLong(OC_ENDPOINT_MAX_ENDPOINT_URI_LENGTH + 1, 'a'); EXPECT_FALSE(oc_cloud_endpoints_init( - &ce, oc_string_view(tooLong.c_str(), tooLong.length()), {})); + &ce, nullptr, nullptr, oc_string_view(tooLong.c_str(), tooLong.length()), + {})); oc_cloud_endpoints_deinit(&ce); } diff --git a/api/cloud/unittest/cloud_manager_test.cpp b/api/cloud/unittest/cloud_manager_test.cpp index 76ba53814..251229e25 100644 --- a/api/cloud/unittest/cloud_manager_test.cpp +++ b/api/cloud/unittest/cloud_manager_test.cpp @@ -320,8 +320,8 @@ class TestCloudManagerData : public testing::Test { void SetUp() override { memset(&m_context, 0, sizeof(m_context)); - oc_cloud_endpoints_init(&m_context.store.ci_servers, OC_STRING_VIEW_NULL, - {}); + oc_cloud_endpoints_init(&m_context.store.ci_servers, nullptr, nullptr, + OC_STRING_VIEW_NULL, {}); } void TearDown() override diff --git a/api/cloud/unittest/cloud_resource_test.cpp b/api/cloud/unittest/cloud_resource_test.cpp index b0c605c2e..7fe5b184c 100644 --- a/api/cloud/unittest/cloud_resource_test.cpp +++ b/api/cloud/unittest/cloud_resource_test.cpp @@ -141,6 +141,11 @@ class TestCloudResourceWithServer : public testing::Test { { oc::TestDevice::StopServer(); } + + void TearDown() override + { + oc::TestDevice::Reset(); + } }; TEST_F(TestCloudResourceWithServer, GetResourceByIndex_F) @@ -174,26 +179,45 @@ TEST_F(TestCloudResourceWithServer, GetResourceByURI) #if !defined(OC_SECURITY) || defined(OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM) -TEST_F(TestCloudResourceWithServer, GetRequest) +template +static void +getRequest(CloudResourceData *crd = nullptr) { auto epOpt = oc::TestDevice::GetEndpoint(kDeviceID); ASSERT_TRUE(epOpt.has_value()); auto ep = std::move(*epOpt); + struct get_handler_data + { + bool invoked; + CloudResourceData *crd; + }; auto get_handler = [](oc_client_response_t *data) { EXPECT_EQ(OC_STATUS_OK, data->code); oc::TestDevice::Terminate(); OC_DBG("GET payload: %s", oc::RepPool::GetJson(data->payload, true).data()); - *static_cast(data->user_data) = - CloudResourceData::decode(data->payload); + auto ghd = static_cast(data->user_data); + ghd->invoked = true; + ASSERT_EQ(CODE, data->code); + if (data->code == OC_STATUS_OK && ghd->crd != nullptr) { + *ghd->crd = CloudResourceData::decode(data->payload); + } }; auto timeout = 1s; - CloudResourceData crd{}; + get_handler_data ghd{ false, crd }; ASSERT_TRUE(oc_do_get_with_timeout(OCF_COAPCLOUDCONF_URI, &ep, nullptr, timeout.count(), get_handler, LOW_QOS, - &crd)); + &ghd)); + oc::TestDevice::PoolEventsMsV1(timeout, true); + EXPECT_TRUE(ghd.invoked); +} + +TEST_F(TestCloudResourceWithServer, GetRequest) +{ + CloudResourceData crd{}; + getRequest(&crd); EXPECT_TRUE(crd.apn.empty()); EXPECT_STREQ(OCF_COAPCLOUDCONF_DEFAULT_CIS, crd.cis.c_str()); @@ -202,6 +226,26 @@ TEST_F(TestCloudResourceWithServer, GetRequest) EXPECT_EQ(0, crd.clec); } +TEST_F(TestCloudResourceWithServer, GetRequest_NoCloudServers) +{ + // remove default + auto *ctx = oc_cloud_get_context(kDeviceID); + ASSERT_NE(nullptr, ctx); + oc_cloud_endpoints_clear(&ctx->store.ci_servers); + + CloudResourceData crd{}; + getRequest(&crd); + + EXPECT_TRUE(crd.apn.empty()); + EXPECT_TRUE(crd.cis.empty()); + EXPECT_TRUE(oc_uuid_is_equal(OCF_COAPCLOUDCONF_DEFAULT_SID, crd.sid)); + EXPECT_EQ(OC_CPS_UNINITIALIZED_STR, crd.cps); + EXPECT_EQ(0, crd.clec); + + // restore default store + oc_cloud_store_initialize(&ctx->store); +} + template static void postRequest(const Fn &encodeFn, CloudResourceData *crd = nullptr) @@ -229,22 +273,60 @@ postRequest(const Fn &encodeFn, CloudResourceData *crd = nullptr) post_handler_data phd{ false, crd }; ASSERT_TRUE(oc_init_post(OCF_COAPCLOUDCONF_URI, &ep, nullptr, post_handler, - HIGH_QOS, &phd)); + LOW_QOS, &phd)); if (encodeFn != nullptr) { encodeFn(); } - auto timeout = 2s; + auto timeout = 1s; ASSERT_TRUE(oc_do_post_with_timeout(timeout.count())); oc::TestDevice::PoolEventsMsV1(timeout, true); EXPECT_TRUE(phd.invoked); } -TEST_F(TestCloudResourceWithServer, PostRequest_FailNoChange) +TEST_F(TestCloudResourceWithServer, PostRequest_FailMissingCis) { auto encode = []() { oc_rep_begin_root_object(); + oc_rep_set_text_string(root, at, "access_token"); + oc_rep_set_text_string(root, sid, "00000000-0000-0000-0000-000000000000"); + oc_rep_end_root_object(); + }; + postRequest(encode); +} + +TEST_F(TestCloudResourceWithServer, PostRequest_FailMissingAccessToken) +{ + auto encode = []() { + oc_rep_begin_root_object(); + oc_rep_set_text_string(root, cis, "coap://mock.plgd.dev"); + oc_rep_set_text_string(root, sid, "00000000-0000-0000-0000-000000000000"); + oc_rep_end_root_object(); + }; + postRequest(encode); +} + +/// for sid and at properties, missing and empty values are equivalent +TEST_F(TestCloudResourceWithServer, PostRequest_FailMissingSid) +{ + 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, ""); + oc_rep_end_root_object(); + }; + postRequest(encode); +} + +TEST_F(TestCloudResourceWithServer, PostRequest_InvalidSid) +{ + 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, "invalid"); // not a valid UUID oc_rep_end_root_object(); }; postRequest(encode); diff --git a/api/cloud/unittest/cloud_store_test.cpp b/api/cloud/unittest/cloud_store_test.cpp index 9bada69bc..632e43f69 100644 --- a/api/cloud/unittest/cloud_store_test.cpp +++ b/api/cloud/unittest/cloud_store_test.cpp @@ -132,8 +132,8 @@ class TestCloudStore : public testing::Test { oc_cloud_store_t store; memset(&store, 0, sizeof(store)); oc_cloud_endpoints_init( - &store.ci_servers, oc_string_view(ci_server.data(), ci_server.length()), - sid); + &store.ci_servers, nullptr, nullptr, + oc_string_view(ci_server.data(), ci_server.length()), sid); oc_new_string(&store.auth_provider, auth_provider.data(), auth_provider.length()); oc_new_string(&store.uid, uid.data(), uid.length()); @@ -181,6 +181,45 @@ class TestCloudStore : public testing::Test { } }; +TEST_F(TestCloudStore, Decode_ServersArray) +{ + 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()); + + oc_cloud_store_t store{}; + EXPECT_TRUE(oc_cloud_store_decode(pool.ParsePayload().get(), &store)); + freeStore(&store); +} + TEST_F(TestCloudStore, Decode_FailUnknownProperty) { oc::RepPool pool{}; @@ -220,15 +259,16 @@ TEST_F(TestCloudStore, Decode_FailUnknownStringProperty) freeStore(&store); } -TEST_F(TestCloudStore, Decode_FailUnknownStringArrayProperty) +TEST_F(TestCloudStore, Decode_FailUnknownObjectArrayProperty) { oc::RepPool pool{}; oc_rep_start_root_object(); std::string key{ "key" }; oc_rep_encode_text_string(oc_rep_object(root), key.c_str(), key.length()); oc_rep_begin_array(oc_rep_object(root), plgd); - std::string value{ "dev" }; - oc_rep_encode_text_string(oc_rep_array(plgd), value.c_str(), value.length()); + oc_rep_object_array_begin_item(plgd); + oc_rep_set_text_string(plgd, plgd, "dev"); + oc_rep_object_array_end_item(plgd); oc_rep_end_array(oc_rep_object(root), plgd); oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); @@ -238,6 +278,21 @@ TEST_F(TestCloudStore, Decode_FailUnknownStringArrayProperty) freeStore(&store); } +TEST_F(TestCloudStore, Decode_FailInvalidSid) +{ + oc::RepPool pool{}; + oc_rep_start_root_object(); + oc_rep_set_text_string(root, ci_server, "coaps://plgd.dev"); + oc_rep_set_text_string(root, sid, "non-UUID"); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + oc_cloud_store_t store{}; + // invalid sid resuls in a warning, not an error + EXPECT_TRUE(oc_cloud_store_decode(pool.ParsePayload().get(), &store)); + freeStore(&store); +} + TEST_F(TestCloudStore, LoadDefaults) { oc_cloud_store_t store{}; diff --git a/api/cloud/unittest/cloud_test.cpp b/api/cloud/unittest/cloud_test.cpp index e8253ec4a..fe563dd6a 100644 --- a/api/cloud/unittest/cloud_test.cpp +++ b/api/cloud/unittest/cloud_test.cpp @@ -62,6 +62,18 @@ TEST_F(TestCloud, oc_cloud_get_context) EXPECT_EQ(nullptr, oc_cloud_get_context(42)); } +TEST_F(TestCloud, set_published_resources_ttl) +{ + oc_cloud_context_t *ctx = oc_cloud_get_context(kDeviceID); + ASSERT_NE(nullptr, ctx); + + uint32_t default_ttl = ctx->time_to_live; + oc_cloud_set_published_resources_ttl(ctx, 42); + EXPECT_EQ(42, ctx->time_to_live); + + oc_cloud_set_published_resources_ttl(ctx, default_ttl); +} + TEST_F(TestCloud, cloud_status) { oc_cloud_status_t status; @@ -130,9 +142,6 @@ TEST_F(TestCloud, oc_cloud_provision_conf_resource) sid, nullptr)); const char *auth_provider = "auth_provider"; - ASSERT_EQ(0, oc_cloud_provision_conf_resource(ctx, ci_server, access_token, - sid, auth_provider)); - EXPECT_EQ(-1, oc_cloud_provision_conf_resource( ctx, invalid.c_str(), access_token, sid, auth_provider)); EXPECT_EQ(-1, oc_cloud_provision_conf_resource( @@ -143,11 +152,21 @@ TEST_F(TestCloud, oc_cloud_provision_conf_resource) EXPECT_EQ(-1, oc_cloud_provision_conf_resource(ctx, ci_server, access_token, sid, invalid.c_str())); + ASSERT_EQ(0, oc_cloud_provision_conf_resource(ctx, ci_server, access_token, + sid, auth_provider)); EXPECT_STREQ(access_token, oc_cloud_get_at(ctx)); EXPECT_STREQ(auth_provider, oc_cloud_get_apn(ctx)); EXPECT_STREQ(ci_server, oc_cloud_get_cis(ctx)); EXPECT_TRUE(oc_uuid_is_equal(sid_uuid, *oc_cloud_get_sid(ctx))); EXPECT_EQ(OC_CLOUD_INITIALIZED, ctx->store.status); + + ASSERT_EQ(0, oc_cloud_provision_conf_resource(ctx, "", "", "", "")); + EXPECT_EQ(nullptr, oc_cloud_get_at(ctx)); + EXPECT_EQ(nullptr, oc_cloud_get_apn(ctx)); + EXPECT_STREQ(OCF_COAPCLOUDCONF_DEFAULT_CIS, oc_cloud_get_cis(ctx)); + EXPECT_TRUE( + oc_uuid_is_equal(OCF_COAPCLOUDCONF_DEFAULT_SID, *oc_cloud_get_sid(ctx))); + EXPECT_EQ(OC_CLOUD_INITIALIZED, ctx->store.status); } TEST_F(TestCloud, oc_cloud_action_to_str) @@ -720,8 +739,13 @@ TEST_F(TestCloud, EndpointAPI) ASSERT_NE(nullptr, ctx); // after initialization, the default endpoint should be selected EXPECT_STREQ(OCF_COAPCLOUDCONF_DEFAULT_CIS, oc_cloud_get_cis(ctx)); + EXPECT_TRUE( + oc_uuid_is_equal(OCF_COAPCLOUDCONF_DEFAULT_SID, *oc_cloud_get_sid(ctx))); // 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_get_cis(ctx)); + EXPECT_EQ(nullptr, oc_cloud_get_sid(ctx)); // add std::string uri1 = "/uri/1"; @@ -800,6 +824,12 @@ TEST_F(TestCloud, EndpointAPI) EXPECT_TRUE(oc_uuid_is_equal(uid2, *oc_cloud_get_sid(ctx))); #endif /* OC_DYNAMIC_ALLOCATION */ + oc_uuid_t uid; + oc_gen_uuid(&uid); + oc_cloud_endpoint_set_id(toSelect, uid); + EXPECT_TRUE(oc_uuid_is_equal(uid, oc_cloud_endpoint_id(toSelect))); + EXPECT_TRUE(oc_uuid_is_equal(uid, *oc_cloud_get_sid(ctx))); + EXPECT_TRUE(oc_cloud_remove_server(ctx, toSelect)); cloud_context_deinit(ctx); diff --git a/api/oc_uuid.c b/api/oc_uuid.c index a01b68349..c7050ed5d 100644 --- a/api/oc_uuid.c +++ b/api/oc_uuid.c @@ -21,6 +21,8 @@ #include "port/oc_random.h" #include "util/oc_macros_internal.h" #include "util/oc_secure_string_internal.h" + +#include #include #include #include @@ -82,8 +84,9 @@ str_to_uuid(const char *str, size_t str_len, oc_uuid_t *uuid) } if ((j + 1) * 2 == k) { if (uuid != NULL) { - uuid->id[j++] = c; + uuid->id[j] = c; } + j++; c = 0; } else { c = (uint8_t)(c << 4); @@ -97,7 +100,8 @@ int oc_str_to_uuid_v1(const char *str, size_t str_len, oc_uuid_t *uuid) { int ret = str_to_uuid(str, str_len, uuid); - if (ret < 0 || (str_len == 1 && str[0] == '*')) { + if (str_len == 1 && str[0] == '*') { + assert(ret == 1); return ret; } if (ret != OC_UUID_ID_SIZE) { diff --git a/api/unittest/uuidtest.cpp b/api/unittest/uuidtest.cpp index d9aade5cc..4ed7177d3 100644 --- a/api/unittest/uuidtest.cpp +++ b/api/unittest/uuidtest.cpp @@ -40,6 +40,38 @@ TEST(UUID, UUIDIsNill) EXPECT_FALSE(oc_uuid_is_empty(uuid2)); } +TEST(UUID, StrToUUIDTestV1_P) +{ + oc_uuid_t uuid{}; + oc_uuid_t uuidTemp = uuid; + ASSERT_EQ(OC_UUID_ID_SIZE, oc_str_to_uuid_v1(UUID, sizeof(UUID), &uuid)); + EXPECT_NE(0, memcmp(uuid.id, uuidTemp.id, OC_UUID_ID_SIZE)); + + oc_uuid_t uuid2{}; + oc_uuid_t uuid2Temp = uuid2; + ASSERT_EQ(OC_UUID_ID_SIZE, oc_str_to_uuid_v1(UUID2, sizeof(UUID2), &uuid2)); + EXPECT_NE(0, memcmp(uuid2.id, uuid2Temp.id, OC_UUID_ID_SIZE)); + + // no output buffer + EXPECT_EQ(OC_UUID_ID_SIZE, oc_str_to_uuid_v1(UUID, sizeof(UUID), nullptr)); + EXPECT_EQ(OC_UUID_ID_SIZE, oc_str_to_uuid_v1(UUID2, sizeof(UUID2), nullptr)); + + // wildcard string ("*") + ASSERT_EQ(1, oc_str_to_uuid_v1("*", 1, &uuid)); + EXPECT_EQ('*', uuid.id[0]); + EXPECT_EQ(1, oc_str_to_uuid_v1("*", 1, nullptr)); +} + +TEST(UUID, StrToUUIDTestV1_F) +{ + /* only wildcard string "*" should work as a single char string */ + EXPECT_EQ(-1, oc_str_to_uuid_v1("a", 1, nullptr)); + + std::string truncated_uuid = std::string(UUID).substr(1); + EXPECT_EQ(-1, oc_str_to_uuid_v1(truncated_uuid.c_str(), truncated_uuid.size(), + nullptr)); +} + TEST(UUID, StrToUUIDTest_P) { oc_uuid_t uuid{}; diff --git a/include/oc_cloud.h b/include/oc_cloud.h index ea630043c..9039e9c88 100644 --- a/include/oc_cloud.h +++ b/include/oc_cloud.h @@ -366,7 +366,7 @@ int oc_cloud_publish_resources(size_t device); OC_API int oc_cloud_discover_resources(const oc_cloud_context_t *ctx, oc_discovery_all_handler_t handler, - void *user_data); + void *user_data) OC_NONNULL(2); /** * @brief Configure cloud properties.