/* * Copyright (c) 2018 ARM Limited. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * 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 "TDBStore.h" #ifdef MBED_CONF_RTOS_PRESENT #include "Thread.h" #endif #include "mbed_error.h" #include "Timer.h" #include "HeapBlockDevice.h" #include "FlashSimBlockDevice.h" #include "SlicingBlockDevice.h" #include "greentea-client/test_env.h" #include "unity/unity.h" #include "utest/utest.h" #include <string.h> #include <stdlib.h> #include <stdio.h> #include <algorithm> #if !KVSTORE_ENABLED #error [NOT_SUPPORTED] KVStore needs to be enabled for this test #endif using namespace mbed; using namespace utest::v1; #undef TEST_SPIF #undef TEST_SD #undef TEST_FLASHIAP #ifdef TEST_SPIF #include "SPIFBlockDevice.h" SPIFBlockDevice bd(MBED_CONF_SPIF_DRIVER_SPI_MOSI, MBED_CONF_SPIF_DRIVER_SPI_MISO, MBED_CONF_SPIF_DRIVER_SPI_CLK, MBED_CONF_SPIF_DRIVER_SPI_CS); SlicingBlockDevice flash_bd(&bd, 0, 16 * 4096); #elif defined(TEST_SD) #include "SDBlockDevice.h" SDBlockDevice bd(MBED_CONF_SD_SPI_MOSI, MBED_CONF_SD_SPI_MISO, MBED_CONF_SD_SPI_CLK, MBED_CONF_SD_SPI_CS); SlicingBlockDevice slice_bd(&bd, 0, 512 * 512); FlashSimBlockDevice flash_bd(&slice_bd); #elif defined(TEST_FLASHIAP) #include "FlashIAPBlockDevice.h" FlashIAPBlockDevice flash_bd(0xF0000, 0x10000); #else #define USE_HEAP_BD 1 HeapBlockDevice bd(8 * 4096, 1, 1, 4096); FlashSimBlockDevice flash_bd(&bd); #endif static const int heap_alloc_threshold_size = 4096; typedef struct { size_t size; size_t read_size; size_t prog_size; size_t erase_size; } bd_params_t; static const char *const key1 = "key1"; static const char *const key1_val1 = "val1"; static const char *const key2 = "name_of_key2"; static const char *const key2_val1 = "val3"; static const char *const key2_val2 = "val2 of key 2"; static const char *const key2_val3 = "Val1 value of key 2 "; static const char *const key3 = "This_is_the_name_of_key3"; static const char *const key3_val1 = "Data value of key 3 is the following"; static const char *const key4 = "This_is_the_name_of_key4"; static const char *const key4_val1 = "Is this the value of key 4?"; static const char *const key4_val2 = "What the hell is the value of key 4, god damn it!"; static const char *const key5 = "This_is_the_real_name_of_Key5"; static const char *const key5_val1 = "Key 5 value that should definitely be written"; static const char *const key5_val2 = "Key 5 value that should definitely not be written"; static const char *const res_val1 = "This should be saved as the reserved data"; static const char *const res_val2 = "This should surely not be saved as the reserved data"; static void white_box_test() { bd_params_t bd_params[] = { {8192, 1, 16, 4096}, // Standard {4096 * 4, 1, 1, 4096}, // K82F like {8192, 64, 128, 2048}, // Awkward read and program sizes, both larger than header size {2048, 1, 4, 128}, // Small sector and total sizes {0, 0, 0, 0}, // Place holder for real non volatile device (if defined) }; int num_bds = sizeof(bd_params) / sizeof(bd_params_t); uint8_t get_buf[128]; size_t actual_data_size; int result; mbed::Timer timer; int elapsed; KVStore::info_t info; #ifndef USE_HEAP_BD flash_bd.init(); bd_params[num_bds - 1].size = flash_bd.size(); bd_params[num_bds - 1].read_size = flash_bd.get_read_size(); bd_params[num_bds - 1].prog_size = flash_bd.get_program_size(); bd_params[num_bds - 1].erase_size = flash_bd.get_erase_size(); flash_bd.deinit(); #endif timer.start(); for (int bd_num = 0; bd_num < num_bds; bd_num++) { uint8_t *dummy = new (std::nothrow) uint8_t[heap_alloc_threshold_size]; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough heap to run test"); bd_params_t *bdp = &bd_params[bd_num]; if (!bdp->size) { break; } printf("\n\nBD #%d: size %d, read %d, prog %d, erase %d\n", bd_num, bdp->size, bdp->read_size, bdp->prog_size, bdp->erase_size); HeapBlockDevice heap_bd(bdp->size, bdp->read_size, bdp->prog_size, bdp->erase_size); FlashSimBlockDevice flash_sim_bd(&heap_bd); BlockDevice *test_bd; if (bd_num == num_bds - 1) { test_bd = &flash_bd; } else { test_bd = &flash_sim_bd; // We need to skip the test if we don't have enough memory for the heap block device. // However, this device allocates the erase units on the fly, so "erase" it via the flash // simulator. A failure here means we haven't got enough memory. flash_sim_bd.init(); result = flash_sim_bd.erase(0, flash_sim_bd.size()); TEST_SKIP_UNLESS_MESSAGE(!result, "Not enough heap to run test"); flash_sim_bd.deinit(); } delete[] dummy; TDBStore *tdbs = new TDBStore(test_bd); timer.reset(); result = tdbs->init(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); elapsed = timer.read_ms(); printf("Elapsed time for init %d ms\n", elapsed); timer.reset(); result = tdbs->reset(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); elapsed = timer.read_ms(); printf("Elapsed time for reset is %d ms\n", elapsed); result = tdbs->reserved_data_get(get_buf, strlen(res_val1)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); result = tdbs->reserved_data_set(res_val1, strlen(res_val1)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->reserved_data_set(res_val2, strlen(res_val2)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_WRITE_FAILED, result); result = tdbs->set(key1, key1_val1, strlen(key1_val1), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set(key2, key2_val1, strlen(key2_val1), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set(key2, key2_val2, strlen(key2_val2), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); timer.reset(); result = tdbs->set(key2, key2_val3, strlen(key2_val3), 0); elapsed = timer.read_ms(); printf("Elapsed time for set is %d ms\n", elapsed); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set(key3, key3_val1, strlen(key3_val1), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->get(key3, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key3_val1), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key3_val1, get_buf, strlen(key3_val1)); KVStore::set_handle_t handle; result = tdbs->set_start(&handle, key4, 15, 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set_add_data(handle, key4_val2, 10); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set_add_data(handle, key4_val2 + 10, 5); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set_finalize(handle); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->get(key4, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(15, actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key4_val2, get_buf, actual_data_size); result = tdbs->get(key4, get_buf, 7, &actual_data_size, 4); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(7, actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key4_val2 + 4, get_buf, actual_data_size); for (int j = 0; j < 2; j++) { result = tdbs->set(key4, key4_val1, strlen(key4_val1), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set(key4, key4_val2, strlen(key4_val2), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); } result = tdbs->remove(key3); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->remove(key3); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); result = tdbs->get_info(key5, &info); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); result = tdbs->set(key5, key5_val1, strlen(key5_val1), KVStore::WRITE_ONCE_FLAG); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->set(key5, key5_val2, strlen(key5_val2), 0); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_WRITE_PROTECTED, result); result = tdbs->remove(key5); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_WRITE_PROTECTED, result); result = tdbs->get_info(key5, &info); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key5_val1), info.size); TEST_ASSERT_EQUAL(KVStore::WRITE_ONCE_FLAG, info.flags); result = tdbs->get(key5, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key5_val1), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key5_val1, get_buf, strlen(key5_val1)); for (int i = 0; i < 2; i++) { printf("%s deinit/init\n", i ? "After" : "Before"); result = tdbs->get(key1, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key1_val1), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key1_val1, get_buf, strlen(key1_val1)); timer.reset(); result = tdbs->get(key2, get_buf, sizeof(get_buf), &actual_data_size); elapsed = timer.read_ms(); printf("Elapsed time for get is %d ms\n", elapsed); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key2_val3), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key2_val3, get_buf, strlen(key2_val3)); result = tdbs->get(key3, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); result = tdbs->get(key4, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key4_val2), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key4_val2, get_buf, strlen(key4_val2)); result = tdbs->get(key5, get_buf, sizeof(get_buf), &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(strlen(key5_val1), actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(key5_val1, get_buf, strlen(key5_val1)); KVStore::iterator_t it; char *char_get_buf = reinterpret_cast <char *>(get_buf); result = tdbs->iterator_open(&it, "This"); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->iterator_next(it, char_get_buf, sizeof(get_buf)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); bool got_key4 = !strcmp(key4, char_get_buf); bool got_key5 = !strcmp(key5, char_get_buf); TEST_ASSERT_EQUAL(true, got_key4 || got_key5); result = tdbs->iterator_next(it, char_get_buf, sizeof(get_buf)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); if (got_key4) { TEST_ASSERT_EQUAL_STRING(key5, char_get_buf); } else { TEST_ASSERT_EQUAL_STRING(key4, char_get_buf); } result = tdbs->iterator_next(it, (char *)get_buf, sizeof(get_buf)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); result = tdbs->iterator_close(it); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->reserved_data_get(get_buf, strlen(res_val1)); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL_STRING_LEN(res_val1, get_buf, strlen(res_val1)); result = tdbs->deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); timer.reset(); result = tdbs->init(); elapsed = timer.read_ms(); printf("Elapsed time for init is %d ms\n", elapsed); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); } result = tdbs->deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); delete tdbs; } } static void multi_set_test() { char *key; uint8_t *get_buf, *set_buf; size_t key_size = 32; size_t data_size = 512; size_t num_keys = 16; size_t set_iters = 3; size_t actual_data_size; int result; mbed::Timer timer; int elapsed; size_t i; uint8_t key_ind; timer.start(); uint8_t *dummy = new (std::nothrow) uint8_t[heap_alloc_threshold_size]; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough heap to run test"); #ifdef USE_HEAP_BD // We need to skip the test if we don't have enough memory for the heap block device. // However, this device allocates the erase units on the fly, so "erase" it via the flash // simulator. A failure here means we haven't got enough memory. flash_bd.init(); result = flash_bd.erase(0, flash_bd.size()); TEST_SKIP_UNLESS_MESSAGE(!result, "Not enough heap to run test"); flash_bd.deinit(); #endif delete[] dummy; TDBStore *tdbs = new TDBStore(&flash_bd); timer.reset(); result = tdbs->init(); elapsed = timer.read_ms(); printf("Elapsed time for initial init is %d ms\n", elapsed); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); key = new char[key_size + 1]; get_buf = new uint8_t[data_size]; set_buf = new uint8_t[data_size]; srand(1); for (i = 0; i < key_size; i++) { // Alphabet characters only key[i] = 'a' + rand() % ('z' - 'a' + 1); } key[key_size] = '\0'; for (i = 0; i < data_size; i++) { set_buf[i] = rand() % 256; } int max_set_time = 0, total_set_time = 0; int max_get_time = 0, total_get_time = 0; timer.reset(); result = tdbs->reset(); elapsed = timer.read_ms(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); for (i = 0; i < set_iters; i++) { for (key_ind = 0; key_ind < num_keys; key_ind++) { key[0] = 'A' + key_ind; set_buf[0] = key_ind * (i + 1); timer.reset(); result = tdbs->set(key, set_buf, data_size, 0); elapsed = timer.read_ms(); TEST_ASSERT_EQUAL_ERROR_CODE(0, result); if (elapsed > max_set_time) { max_set_time = elapsed; } total_set_time += elapsed; } } for (key_ind = 0; key_ind < num_keys; key_ind++) { key[0] = 'A' + key_ind; set_buf[0] = key_ind * set_iters; timer.reset(); result = tdbs->get(key, get_buf, data_size, &actual_data_size); elapsed = timer.read_ms(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(data_size, actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(set_buf, get_buf, data_size); if (elapsed > max_get_time) { max_get_time = elapsed; } total_get_time += elapsed; } printf("set time: Total (%d * %d times) - %d ms, Average - %d ms, Max - %d ms\n", set_iters, num_keys, total_set_time, total_set_time / (set_iters * num_keys), max_set_time); printf("get time: Total (%d times) - %d ms, Average - %d ms, Max - %d ms\n", num_keys, total_get_time, total_get_time / num_keys, max_get_time); result = tdbs->deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); timer.reset(); result = tdbs->init(); elapsed = timer.read_ms(); printf("Elapsed time for init is %d ms\n", elapsed); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); delete[] key; delete[] get_buf; delete[] set_buf; delete tdbs; } static void error_inject_test() { char *key; uint8_t *get_buf, *set_buf, *exists; size_t key_size = 8; size_t data_size = 16; size_t num_keys = 'Z' - 'A' + 1; size_t num_blocks = 4; size_t block_size = 1024; size_t actual_data_size; int result; uint8_t set_iters = 20; uint8_t i, key_ind; key = new char[key_size + 1]; get_buf = new uint8_t[data_size]; set_buf = new uint8_t[data_size]; exists = new uint8_t[num_keys]; uint8_t *dummy = new (std::nothrow) uint8_t[heap_alloc_threshold_size]; TEST_SKIP_UNLESS_MESSAGE(dummy, "Not enough heap to run test"); // Don't use a non volatile BD here (won't work in this test) HeapBlockDevice bd(num_blocks * block_size, 1, 1, block_size); FlashSimBlockDevice flash_bd(&bd); // Erase flash first, as we search the erase value later result = flash_bd.init(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); // We need to skip the test if we don't have enough memory for the heap block device. // However, this device allocates the erase units on the fly, so "erase" it via the flash // simulator. A failure here means we haven't got enough memory. result = flash_bd.erase(0, flash_bd.size()); TEST_SKIP_UNLESS_MESSAGE(!result, "Not enough heap to run test"); result = flash_bd.deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); delete[] dummy; TDBStore *tdbs = new TDBStore(&flash_bd); result = tdbs->init(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); memset(exists, 0, num_keys); for (i = 0; i < set_iters; i++) { for (key_ind = 0; key_ind < num_keys; key_ind++) { memset(key, 'A' + key_ind, key_size); key[key_size] = '\0'; memset(set_buf, key_ind * i, data_size); if ((key_ind != (num_keys - 1)) && exists[key_ind] && !(rand() % 3)) { result = tdbs->remove(key); exists[key_ind] = 0; } else { result = tdbs->set(key, set_buf, data_size, 0); exists[key_ind] = 1; } TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); } } for (uint8_t get_iter = 0; get_iter < 2; get_iter++) { for (key_ind = 0; key_ind < num_keys; key_ind++) { memset(key, 'A' + key_ind, key_size); key[key_size] = '\0'; if (key_ind == (num_keys - 1)) { // last key will hold the previous version at the second iteration (after being crippled) memset(set_buf, key_ind * (set_iters - get_iter - 1), data_size); } else { memset(set_buf, key_ind * (set_iters - 1), data_size); } result = tdbs->get(key, get_buf, data_size, &actual_data_size); if (exists[key_ind]) { TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); TEST_ASSERT_EQUAL(data_size, actual_data_size); TEST_ASSERT_EQUAL_STRING_LEN(set_buf, get_buf, data_size); } else { TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_ITEM_NOT_FOUND, result); } } if (get_iter) { break; } // Cripple the last key in both areas uint8_t erase_val = (uint8_t) flash_bd.get_erase_value(); uint8_t x; bd_addr_t addr = bd.size(); for (int area = 1; area >= 0; area--) { int j; bool found = false; // look for last non blank while (addr && !found) { addr -= data_size; bd.read(get_buf, addr, data_size); for (j = data_size - 1; j >= 0; j--) { if (get_buf[j] != erase_val) { addr += j; x = get_buf[j] + 1; found = true; break; } } } if (!found) { break; } // Cripple last non blank in area bd.program(&x, addr, 1); addr -= j; if ((area == 0) || !addr) { break; } // Skip at least one blank erase unit uint32_t num_blanks = 0; memset(set_buf, erase_val, data_size); while (addr && (num_blanks < block_size)) { bd.read(get_buf, addr, data_size); if (memcmp(get_buf, set_buf, data_size)) { num_blanks = 0; } else { num_blanks += data_size; } addr -= data_size; } } result = tdbs->get(key, get_buf, data_size, &actual_data_size); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_ERROR_INVALID_DATA_DETECTED, result); result = tdbs->deinit(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); result = tdbs->init(); TEST_ASSERT_EQUAL_ERROR_CODE(MBED_SUCCESS, result); } delete[] key; delete[] get_buf; delete[] set_buf; delete[] exists; delete tdbs; } utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) { greentea_case_failure_abort_handler(source, reason); return STATUS_CONTINUE; } Case cases[] = { Case("TDBStore: White box test", white_box_test, greentea_failure_handler), Case("TDBStore: Multiple set test", multi_set_test, greentea_failure_handler), Case("TDBStore: Error inject test", error_inject_test, greentea_failure_handler), }; utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { GREENTEA_SETUP(120, "default_auto"); return greentea_test_setup_handler(number_of_cases); } Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); int main() { return !Harness::run(specification); }