diff --git a/api/oc_etag.c b/api/oc_etag.c index 603a12bf9..2c8150db3 100644 --- a/api/oc_etag.c +++ b/api/oc_etag.c @@ -56,18 +56,29 @@ oc_etag_global(void) return g_etag; } +uint64_t +oc_etag_set_global(uint64_t etag) +{ + if (etag == OC_ETAG_UNINITALIZED) { + etag = 1; + } + if (etag < g_etag) { + // TODO: handle wrap around = all resource etags must be reinitialized + OC_DBG("etag wrap around detected: %" PRIu64 " -> %" PRIu64, g_etag, etag); + } + return (g_etag = etag); +} + uint64_t oc_etag_get(void) { uint64_t now = oc_clock_time(); - if (now > g_etag) { - g_etag = now; - } - g_etag += etag_random(); - if (g_etag == OC_ETAG_UNINITALIZED) { - g_etag = 1; + uint64_t etag = g_etag; + if (now > etag) { + etag = now; } - return g_etag; + etag += etag_random(); + return oc_etag_set_global(etag); } #ifdef OC_STORAGE @@ -106,7 +117,10 @@ etag_iterate_resources(size_t device, etag_process_resource_fn_t process_fn, // core resources for (int type = OCF_CON; type <= OCF_D; ++type) { oc_resource_t *core_res = oc_core_get_resource_by_index(type, device); - process_fn(core_res, data); + if (process_fn(core_res, data) != 0) { + OC_ERR("failed to process core resource(%s)", oc_string(core_res->uri)); + return false; + } } // app resources @@ -232,7 +246,7 @@ static int etag_iterate_clear_etag(oc_resource_t *resource, void *data) { (void)data; - oc_resource_set_etag(resource, 0); + oc_resource_set_etag(resource, OC_ETAG_UNINITALIZED); return 0; } @@ -259,7 +273,7 @@ etag_iterate_update_empty_etag(oc_resource_t *resource, void *data) } bool -oc_etag_load_from_storage(void) +oc_etag_load_from_storage(bool set_empty_etags) { bool success = true; // load g_etag and resource etags from storage @@ -272,19 +286,19 @@ oc_etag_load_from_storage(void) long ret = oc_storage_data_load(OC_ETAG_STORE_NAME, i, etag_store_decode_etags, &etag); if (ret <= 0) { - OC_ERR("failed to load etag for device %zu", i); + OC_ERR("failed to load etags for device %zu", i); success = false; } } - g_etag = etag + etag_random(); - if (g_etag == OC_ETAG_UNINITALIZED) { - g_etag = 1; - } - OC_DBG("g_tag: %" PRIu64, g_etag); - - // update empty etags - for (size_t i = 0; i < oc_core_get_num_devices(); ++i) { - etag_iterate_resources(i, etag_iterate_update_empty_etag, NULL); + etag += etag_random(); + oc_etag_set_global(etag); + OC_DBG("g_tag: %" PRIu64, oc_etag_global()); + + if (set_empty_etags) { + // update empty etags + for (size_t i = 0; i < oc_core_get_num_devices(); ++i) { + etag_iterate_resources(i, etag_iterate_update_empty_etag, NULL); + } } return success; } @@ -292,7 +306,7 @@ oc_etag_load_from_storage(void) bool oc_etag_load_and_clear(void) { - bool success = oc_etag_load_from_storage(); + bool success = oc_etag_load_from_storage(true); success = oc_etag_clear_storage() && success; return success; } diff --git a/api/oc_etag_internal.h b/api/oc_etag_internal.h index 1a976b7c3..b9cd75f5e 100644 --- a/api/oc_etag_internal.h +++ b/api/oc_etag_internal.h @@ -37,6 +37,9 @@ extern "C" { /** Get the global ETag value without modifying it. */ uint64_t oc_etag_global(void); +/** Set global ETag value */ +uint64_t oc_etag_set_global(uint64_t etag); + /** Get the next global ETag value. */ uint64_t oc_etag_get(void); @@ -71,10 +74,13 @@ bool oc_etag_clear_storage(void); * load other stores and the resources of device 0 will be updated by calling * oc_etag_get(). The function will return false in this case. * + * @param set_empty_etags if true, the ETag values of resources thay aren't + * present in the persistent storage are updated by calling oc_etag_get() + * * @return true everything was succesfully loaded from persistent storage * @return false otherwise */ -bool oc_etag_load_from_storage(void); +bool oc_etag_load_from_storage(bool set_empty_etags); #endif /* OC_STORAGE */ diff --git a/api/oc_storage.c b/api/oc_storage.c index b4abbe1da..1b4feb669 100644 --- a/api/oc_storage.c +++ b/api/oc_storage.c @@ -129,7 +129,7 @@ oc_storage_data_load(const char *name, size_t device, OC_ERR("cannot load from %s from store: cannot parse representation", name); goto error; } - if (decode(rep, device, decode_data) != 0) { + if (rep != NULL && decode(rep, device, decode_data) != 0) { OC_ERR("cannot load from %s from store: cannot decode data", name); oc_free_rep(rep); goto error; diff --git a/api/unittest/etagtest.cpp b/api/unittest/etagtest.cpp index 8fb4ccb36..bf1a861d2 100644 --- a/api/unittest/etagtest.cpp +++ b/api/unittest/etagtest.cpp @@ -189,6 +189,13 @@ TEST_F(TestETagWithServer, ETagsInitialized) }); } +TEST_F(TestETagWithServer, ETagWrapAround) +{ + oc_etag_set_global(0); + // TODO: check that all resources have reinitialized etags with the wrapped + // value +} + #ifdef OC_DYNAMIC_ALLOCATION // check that newly created resources have etags @@ -221,6 +228,21 @@ setAllETags(uint64_t etag) [etag](oc_resource_t *resource) { oc_resource_set_etag(resource, etag); }); } +static bool +isETagStorageEmpty() +{ + for (size_t i = 0; i < oc_core_get_num_devices(); ++i) { + long ret = oc_storage_data_load( + OC_ETAG_STORE_NAME, i, [](const oc_rep_t *, size_t, void *) { return 0; }, + nullptr); + if (ret > 0) { + OC_ERR("storage for device %zu is not empty", i); + return false; + } + } + return true; +} + TEST_F(TestETagWithServer, DumpAndLoad) { #ifdef OC_COLLECTIONS @@ -265,19 +287,7 @@ TEST_F(TestETagWithServer, DumpAndLoad) }); // storage should be empty - auto is_empty_storage = []() { - for (size_t i = 0; i < oc_core_get_num_devices(); ++i) { - long ret = oc_storage_data_load( - OC_ETAG_STORE_NAME, i, - [](const oc_rep_t *, size_t, void *) { return 0; }, nullptr); - if (ret > 0) { - OC_ERR("storage for device %zu is not empty", i); - return false; - } - } - return true; - }; - EXPECT_TRUE(is_empty_storage()); + EXPECT_TRUE(isETagStorageEmpty()); // clean-up #ifdef OC_DYNAMIC_ALLOCATION @@ -287,6 +297,93 @@ TEST_F(TestETagWithServer, DumpAndLoad) #endif // OC_DYNAMIC_ALLOCATION } +TEST_F(TestETagWithServer, SkipDumpOfEmptyETags) +{ + // set all etags to 0 + setAllETags(OC_ETAG_UNINITALIZED); + // no etags should be stored + ASSERT_TRUE(oc_etag_dump()); + + // all etags should be reinitialized by oc_etag_load_from_storage + uint64_t max_etag = oc_etag_global(); + EXPECT_TRUE(oc_etag_load_from_storage(true)); + iterateAllResources([&max_etag](const oc_resource_t *resource) { + EXPECT_LT(max_etag, oc_resource_get_etag(resource)); + }); +} + +TEST_F(TestETagWithServer, IgnoreInvalidStorageData) +{ + // expected storage data: + // { + // "": { + // "etag": , + // }, + // ... + // } + + auto store_encode_single_string = [](size_t, void *) { + oc_rep_start_root_object(); + oc_rep_set_text_string(root, uri, "/oic/d"); + oc_rep_end_root_object(); + return 0; + }; + ASSERT_LT(0, oc_storage_data_save(OC_ETAG_STORE_NAME, kDeviceID1, + store_encode_single_string, nullptr)); + EXPECT_FALSE(oc_etag_load_from_storage(false)); + + auto store_encode_invalid_type = [](size_t, void *) { + oc_rep_start_root_object(); + std::string uri = "/oic/d"; + int err = + oc_rep_encode_text_string(oc_rep_object(root), uri.c_str(), uri.length()); + CborEncoder etag_map; + memset(&etag_map, 0, sizeof(etag_map)); + err |= oc_rep_encoder_create_map(oc_rep_object(root), &etag_map, + CborIndefiniteLength); + std::string key = "etag"; + err |= oc_rep_encode_text_string(&etag_map, key.c_str(), key.length()); + std::string value = "invalid"; + err |= oc_rep_encode_text_string(&etag_map, value.c_str(), value.length()); + err |= oc_rep_encoder_close_container(oc_rep_object(root), &etag_map); + oc_rep_end_root_object(); + return err; + }; + ASSERT_LT(0, oc_storage_data_save(OC_ETAG_STORE_NAME, kDeviceID1, + store_encode_invalid_type, nullptr)); + EXPECT_FALSE(oc_etag_load_from_storage(false)); + + auto store_encode_invalid_value = [](size_t, void *) { + oc_rep_start_root_object(); + std::string uri = "/oic/p"; + int err = + oc_rep_encode_text_string(oc_rep_object(root), uri.c_str(), uri.length()); + CborEncoder etag_map; + memset(&etag_map, 0, sizeof(etag_map)); + err |= oc_rep_encoder_create_map(oc_rep_object(root), &etag_map, + CborIndefiniteLength); + std::string key = "etag"; + err |= oc_rep_encode_text_string(&etag_map, key.c_str(), key.length()); + err |= oc_rep_encode_uint(&etag_map, 0); + err |= oc_rep_encoder_close_container(oc_rep_object(root), &etag_map); + + uri = "/oic/d"; + err |= + oc_rep_encode_text_string(oc_rep_object(root), uri.c_str(), uri.length()); + memset(&etag_map, 0, sizeof(etag_map)); + err |= oc_rep_encoder_create_map(oc_rep_object(root), &etag_map, + CborIndefiniteLength); + err |= oc_rep_encode_text_string(&etag_map, key.c_str(), key.length()); + err |= oc_rep_encode_int(&etag_map, -1); + err |= oc_rep_encoder_close_container(oc_rep_object(root), &etag_map); + oc_rep_end_root_object(); + return err; + }; + ASSERT_LT(0, oc_storage_data_save(OC_ETAG_STORE_NAME, kDeviceID1, + store_encode_invalid_value, nullptr)); + EXPECT_FALSE(oc_etag_load_from_storage(false)); +} + TEST_F(TestETagWithServer, LoadGlobalETagFromStorage) { uint64_t max_etag = oc_etag_global(); @@ -301,7 +398,7 @@ TEST_F(TestETagWithServer, LoadGlobalETagFromStorage) oc_resource_set_etag(platform, max_etag); ASSERT_TRUE(oc_etag_dump()); - EXPECT_TRUE(oc_etag_load_from_storage()); + EXPECT_TRUE(oc_etag_load_from_storage(false)); // the global etag should be > than the maximal etag of all resources EXPECT_GT(oc_etag_global(), max_etag); @@ -326,7 +423,7 @@ TEST_F(TestETagWithServer, ClearStorage) // clear the storage ASSERT_TRUE(oc_etag_clear_storage()); - EXPECT_FALSE(oc_etag_load_from_storage()); + EXPECT_FALSE(oc_etag_load_from_storage(true)); iterateAllResources([](const oc_resource_t *resource) { // nor 0 nor 1337