diff --git a/features/filesystem/bd/ExhaustibleBlockDevice.cpp b/features/filesystem/bd/ExhaustibleBlockDevice.cpp index cd949d66071..fa154020764 100644 --- a/features/filesystem/bd/ExhaustibleBlockDevice.cpp +++ b/features/filesystem/bd/ExhaustibleBlockDevice.cpp @@ -68,8 +68,7 @@ int ExhaustibleBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_ MBED_ASSERT(is_valid_program(addr, size)); if (_erase_array[addr / get_erase_size()] == 0) { - // TODO possibly something more destructive here - return 0; + return BD_ERROR_ERASE_UNIT_WORN_OUT; } return _bd->program(buffer, addr, size); @@ -79,17 +78,26 @@ int ExhaustibleBlockDevice::erase(bd_addr_t addr, bd_size_t size) { MBED_ASSERT(is_valid_erase(addr, size)); - // use an erase cycle - if (_erase_array[addr / get_erase_size()] > 0) { - _erase_array[addr / get_erase_size()] -= 1; - } + bd_size_t eu_size = get_erase_size(); + while (size) { + // use an erase cycle + if (_erase_array[addr / eu_size] > 0) { + _erase_array[addr / eu_size] -= 1; + } - if (_erase_array[addr / get_erase_size()] == 0) { - // TODO possibly something more destructive here - return 0; + if (_erase_array[addr / eu_size] == 0) { + return BD_ERROR_ERASE_UNIT_WORN_OUT; + } + + int err = _bd->erase(addr, eu_size); + if (err) { + return err; + } + addr += eu_size; + size -= eu_size; } - return _bd->erase(addr, size); + return 0; } bd_size_t ExhaustibleBlockDevice::get_read_size() const diff --git a/features/filesystem/bd/ExhaustibleBlockDevice.h b/features/filesystem/bd/ExhaustibleBlockDevice.h index e55f662fe94..a488e2a4019 100644 --- a/features/filesystem/bd/ExhaustibleBlockDevice.h +++ b/features/filesystem/bd/ExhaustibleBlockDevice.h @@ -24,6 +24,10 @@ #include "BlockDevice.h" +enum { + BD_ERROR_ERASE_UNIT_WORN_OUT = -3301, +}; + /** Heap backed block device which simulates failures * diff --git a/features/filesystem/storagelite/README.md b/features/filesystem/storagelite/README.md new file mode 100644 index 00000000000..dc2c546b7eb --- /dev/null +++ b/features/filesystem/storagelite/README.md @@ -0,0 +1,92 @@ +## StorageLite + +StorageLite is a storage solution, providing a key value store like API of set/get data or a reduced POSIX API of open-write-close and open-read-close. It provides fast write and read functions, power failure resilience, security and built-in backup and factory reset support. + +Uses: keeping credentials and cryptographic keys, application settings, application states and small application data to be sent to servers. + +Hardware: designed for external NOR flash, it can work also on internal flash and with additional block devices on SD cards. + +StorageLite is a lightweight file system storage module for files in external memory. Files are stored and accessed by file-name. StorageLite has the following attributes: + +- High performance through a small RAM table. +- Security: + - Integrity through file authentication. + - Confidentiality through file encryption. + - Rollback protection. +- Wear level: StorageLite stores files in a sequential order. This concept uses the entire external memory space. It is optimized for NOR flash that supports a limited number of erase per sector. +- Backup: built-in backup and factory reset support. +- Low RAM memory footprint: indexing each file uses 8 bytes of RAM. + +Please see the [StorageLite design document](./StorageLiteDesign.md) for a detailed design description of StorageLite. + +#### Flash memory + +StorageLite is optimized for NOR flash memory. It is based on dividing the allocated memory storage to areas: active and standby. Data is written to the active area until it becomes full. When the active area becomes full, an internal garbage collection mechanism moves only the updated relevant files to the standby area and switches between the active and standby areas. The new standby area is then erased. + +This concept is ideal for low wear leveling of the memory, but it comes with a price of using half of the external memory size. + +#### APIs + +You can either use StorageLite with set- and get-based APIs or it can be defined as StorageLiteFS and work with a limited POSIX open-write or read-close based API. (Only a **single** write or read operation is allowed.) + +##### StorageLite + +- `init`: binds StorageLite to a specific block device and constructs and initializes StorageLite module elements. +- `deinit`: deinitialize the StorageLite module. +- `set`: saves data into persistent storage by file name. +- `get`: retrieves file data from storage after authenticating it. +- `remove`: removes an existing file from storage. +- `get_file_size`: returns the file size after authenticating the data. +- `file_exist`: verifies that file exists and authenticates it. +- `get_file_flags`: returns files supported features (such as rollback protection, encryption and factory reset). +- `get_first/next`: provides the ability to iterate through existing files, starting from the first one. +- `factory_reset`: returns storage to contain only files that are backed up and returns those files to their backup version. + +##### StorageLiteFS + +- Mount or unmount: attaches or detaches a block device to or from the file system. +- Open or close: establishes the connection between a file and a file descriptor. +- Write: Writing data to the file associated with the opened file descriptor (replacing existing data - if exists). You can only call write once. +- Read: reads data from the file associated with the open file descriptor. Read can be called only once. +- Remove: deletes a file name from the file system. +- Reformat: clears a file system, resulting in an empty and mounted file system. + +#### Usage + +##### Using StorageLite + +First, define a block device, and define the maximum number of files you want StorageLite to support. Next, create an instance of StorageLite, and initialize it with the above block device and max files setup. Then, you can start saving new files and getting their data. + +``` c++ + #define MAX_NUM_OF_FILES 50 + SPIFBlockDevice spif(PTE2, PTE4, PTE1, PTE5); + StorageLite sl; + sl.init(&spif, MAX_NUM_OF_FILES); +``` + +##### Using StorageLiteFS + +First, define the block device you want StorageLiteFS to use, and create an instance of StorageLite. Next, create a StorageLiteFS instance, giving it the name under which it is cataloged in the filesystem tree, the StorageLite instance it uses and its feature flags. Finally, mount the selected block device onto the StorageLiteFS instance. Now you can start opening new files for write and read. + +``` c++ + SPIFBlockDevice spif(PTE2, PTE4, PTE1, PTE5); + StorageLite sl; + StorageLiteFS slfs("/sl", &sl, StorageLite::encrypt_flag); + slfs.mount(&spif); +``` + +#### Testing StorageLite + +Run any of the StorageLite tests with the `mbed` command: + +```mbed test -n features-storagelite-tests-storagelite-whitebox``` +```mbed test -n features-storagelite-tests-storagelite-fs_tests``` +```mbed test -n features-storagelite-tests-storagelite-general_tests``` + +### StorageLite API + +TODO: Link to StorageLite class reference once code merges. + +### SotrageLite example + +TODO: Link to transcluded example once it exists. \ No newline at end of file diff --git a/features/filesystem/storagelite/TESTS/storagelite/fs_tests/main.cpp b/features/filesystem/storagelite/TESTS/storagelite/fs_tests/main.cpp new file mode 100644 index 00000000000..65de289760a --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/fs_tests/main.cpp @@ -0,0 +1,670 @@ +/* Copyright (c) 2017 ARM Limited + * + * 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 "StorageLite.h" +#include "StorageLiteFS.h" +#include "FlashSimBlockDevice.h" +#include "SlicingBlockDevice.h" +#include "HeapBlockDevice.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest/utest.h" + +// Define this if you want to run the test locally (requires SPIF/SD block device) +#undef LOCAL_TEST + +#ifdef LOCAL_TEST +#undef TEST_SPIF +#ifdef TEST_SPIF +#include "SPIFBlockDevice.h" +#endif + +#undef TEST_SD +#ifdef TEST_SD +#include "SDBlockDevice.h" +#endif +#endif + +#if !STORAGELITE_ENABLED +#error [NOT_SUPPORTED] StorageLite needs to be enabled for this test +#endif + +// MBED_TEST_SIM_BLOCKDEVICE is used here only to filter out inappropriate boards (little RAM etc.) +#if !defined(MBED_TEST_SIM_BLOCKDEVICE) && !defined(LOCAL_TEST) +#error [NOT_SUPPORTED] StorageLite test not supported on this platform +#endif + +using namespace utest::v1; + +static const size_t buf_size = 10; +static const size_t test_buffer = 8192; +static const size_t test_files = 4; + +FILE *fd[test_files]; +uint8_t wbuffer[test_buffer]; +uint8_t rbuffer[test_buffer]; +uint8_t buffer[test_buffer]; +StorageLite stlite; + + +#ifdef TEST_SPIF +#ifdef TARGET_K82F +SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5); +#else +SPIFBlockDevice bd(D11, D12, D13, D8); +#endif +SlicingBlockDevice flash_bd(&bd, 0 * 4096, bd.size()); +#elif defined(TEST_SD) +HeapBlockDevice bd(512 * 512, 16, 16, 512); +BufferedBlockDevice buf_bd(&bd); +FlashSimBlockDevice flash_bd(&buf_bd); +#endif + +#if !defined(TEST_SPIF) && !defined(TEST_SD) +HeapBlockDevice bd(4096 * 4, 1, 1, 4096); +FlashSimBlockDevice flash_bd(&bd); +#endif + +/*----------------help functions------------------*/ + +static void init() +{ + int result = STORAGELITE_SUCCESS; + + result = stlite.init(&flash_bd); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); +} + +static void fs_init(StorageLiteFS *stlitefs) +{ + int result = STORAGELITE_SUCCESS; + + result = stlitefs->mount(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + + result = stlitefs->reformat(&flash_bd); + TEST_ASSERT_EQUAL(0, result); +} + +static void open_write_file(FILE *fd, size_t buf_size) +{ + int res = !((fd = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd); + TEST_ASSERT_EQUAL(0, res); +} + +/*----------------fopen()------------------*/ + +//fopen path without stfs prefix +static void StorageLiteFS_fopen_path_not_valid() +{ + int res = !((fd[0] = fopen("hello", "w")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen empty file name with r mode +static void StorageLiteFS_fopen_empty_path_r_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "", "r")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen empty file name with w mode +static void StorageLiteFS_fopen_empty_path_w_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); +} + +//fopen empty mode +static void StorageLiteFS_fopen_invalid_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "invalid_mode", "")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen with unsupported a mode +static void StorageLiteFS_fopen_unsupported_a_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "unsupported_mode", "a")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen with unsupported a+ mode +static void StorageLiteFS_fopen_unsupported_a_plus_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "unsupported_mode", "a+")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen with unsupported r+ mode +static void StorageLiteFS_fopen_unsupported_r_plus_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "unsupported_mode", "r+")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen with unsupported w+ mode +static void StorageLiteFS_fopen_unsupported_w_plus_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "unsupported_mode", "w+")) != NULL); + TEST_ASSERT_EQUAL(1, res); +} + +//fopen with unsupported rb mode +static void StorageLiteFS_fopen_supported_rb_plus_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "rb")) != NULL); + TEST_ASSERT_EQUAL(0, res); +} + +//fopen with unsupported wb mode +static void StorageLiteFS_fopen_supported_wb_plus_mode() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "unsupported_mode", "wb")) != NULL); + TEST_ASSERT_EQUAL(0, res); +} + +/*----------------fclose()------------------*/ + +//fclose valid flow +static void StorageLiteFS_fclose_valid_flow() +{ + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + + +//fwrite with size zero +static void StorageLiteFS_fwrite_size_zero() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, 0, buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fwrite with nmemb zero +static void StorageLiteFS_fwrite_nmemb_zero() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), 0, fd[0]); + TEST_ASSERT_EQUAL(0, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fwrite valid flow +static void StorageLiteFS_fwrite_valid_flow() +{ + char buffer[buf_size] = "good_day", rbuffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(rbuffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, read_sz); + TEST_ASSERT_EQUAL_STRING(buffer, rbuffer); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fwrite to fopen mode r +static void StorageLiteFS_fwrite_with_fopen_r_mode() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, write_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fwrite to closed file +static void StorageLiteFS_fwrite_closed_file() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, write_sz); +} + +//fwrite twice to the same file +static void StorageLiteFS_fwrite_twice_to_file() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(-1, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fwrite twice to the same file +static void StorageLiteFS_fwrite_twice_separated_with_fclose() +{ + char buffer[buf_size] = "good_day"; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +/*----------------fread()------------------*/ + +//fread with size zero +static void StorageLiteFS_fread_size_zero() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, 0, buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread with nmemb zero +static void StorageLiteFS_fread_nmemb_zero() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), 0, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread valid flow +static void StorageLiteFS_fread_valid_flow() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread after fwrite without fclose +static void StorageLiteFS_fread_fwrite_no_fclose() +{ + char buffer[buf_size] = "good_day", rbuffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int write_sz = fwrite(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, write_sz); + + res = fflush(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(rbuffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread to fopen mode w +static void StorageLiteFS_fread_with_fopen_w_mode() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "w")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread to closed file +static void StorageLiteFS_fread_closed_file() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); +} + +//fread twice from file +static void StorageLiteFS_fread_twice_from_file() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, read_sz); + + read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(0, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + +//fread twice from file +static void StorageLiteFS_fread_twice_separated_with_fclose() +{ + char buffer[buf_size] = {}; + + init(); + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + fs_init(&stlitefs); + + open_write_file(fd[0], buf_size); + + int res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + int read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); + + res = !((fd[0] = fopen("/stfs/" "hello", "r")) != NULL); + TEST_ASSERT_EQUAL(0, res); + + read_sz = fread(buffer, sizeof(char), buf_size, fd[0]); + TEST_ASSERT_EQUAL(buf_size, read_sz); + + res = fclose(fd[0]); + TEST_ASSERT_EQUAL(0, res); +} + + +/*----------------setup------------------*/ + +utest::v1::status_t setup_init(const Case *const source, const size_t index_of_case, uint16_t max_bd_files) +{ + return STATUS_CONTINUE; +} + +utest::v1::status_t tear_down_handler(const Case *const source, const size_t passed, const size_t failed, + const failure_t reason) +{ + return STATUS_CONTINUE; +} + +utest::v1::status_t failure_handler(const Case *const source, const failure_t reason) +{ + greentea_case_failure_abort_handler(source, reason); + return STATUS_CONTINUE; +} + +Case cases[] = { + Case("StorageLiteFS_fopen_path_not_valid", StorageLiteFS_fopen_path_not_valid), + Case("StorageLiteFS_fopen_empty_path_r_mode", StorageLiteFS_fopen_empty_path_r_mode), + Case("StorageLiteFS_fopen_empty_path_w_mode", StorageLiteFS_fopen_empty_path_w_mode), + Case("StorageLiteFS_fopen_invalid_mode", StorageLiteFS_fopen_invalid_mode), + Case("StorageLiteFS_fopen_unsupported_a_mode", StorageLiteFS_fopen_unsupported_a_mode), + Case("StorageLiteFS_fopen_unsupported_a_plus_mode", StorageLiteFS_fopen_unsupported_a_plus_mode), + Case("StorageLiteFS_fopen_unsupported_r_plus_mode", StorageLiteFS_fopen_unsupported_r_plus_mode), + Case("StorageLiteFS_fopen_unsupported_w_plus_mode", StorageLiteFS_fopen_unsupported_w_plus_mode), + Case("StorageLiteFS_fopen_supported_rb_plus_mode", StorageLiteFS_fopen_supported_rb_plus_mode), + Case("StorageLiteFS_fopen_supported_wb_plus_mode", StorageLiteFS_fopen_supported_wb_plus_mode), + + Case("StorageLiteFS_fclose_valid_flow", StorageLiteFS_fclose_valid_flow), + + Case("StorageLiteFS_fwrite_size_zero", StorageLiteFS_fwrite_size_zero), + Case("StorageLiteFS_fwrite_nmemb_zero", StorageLiteFS_fwrite_nmemb_zero), + Case("StorageLiteFS_fwrite_valid_flow", StorageLiteFS_fwrite_valid_flow), + Case("StorageLiteFS_fwrite_with_fopen_r_mode", StorageLiteFS_fwrite_with_fopen_r_mode), + Case("StorageLiteFS_fwrite_closed_file", StorageLiteFS_fwrite_closed_file), + Case("StorageLiteFS_fwrite_twice_to_file", StorageLiteFS_fwrite_twice_to_file), + Case("StorageLiteFS_fwrite_twice_separated_with_fclose", StorageLiteFS_fwrite_twice_separated_with_fclose), + + Case("StorageLiteFS_fread_size_zero", StorageLiteFS_fread_size_zero), + Case("StorageLiteFS_fread_nmemb_zero", StorageLiteFS_fread_nmemb_zero), + Case("StorageLiteFS_fread_valid_flow", StorageLiteFS_fread_valid_flow), + Case("StorageLiteFS_fread_fwrite_no_fclose", StorageLiteFS_fread_fwrite_no_fclose), + Case("StorageLiteFS_fread_with_fopen_w_mode", StorageLiteFS_fread_with_fopen_w_mode), + Case("StorageLiteFS_fread_closed_file", StorageLiteFS_fread_closed_file), + Case("StorageLiteFS_fread_twice_from_file", StorageLiteFS_fread_twice_from_file), + Case("StorageLiteFS_fread_twice_separated_with_fclose", StorageLiteFS_fread_twice_separated_with_fclose), +}; + +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); +} diff --git a/features/filesystem/storagelite/TESTS/storagelite/general_tests/main.cpp b/features/filesystem/storagelite/TESTS/storagelite/general_tests/main.cpp new file mode 100644 index 00000000000..f18edc0a7a4 --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/general_tests/main.cpp @@ -0,0 +1,1494 @@ +/* Copyright (c) 2017 ARM Limited + * + * 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 "StorageLite.h" +#include "HeapBlockDevice.h" +#include "FlashSimBlockDevice.h" +#include "SlicingBlockDevice.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest/utest.h" +#ifdef MBED_CONF_RTOS_PRESENT +#include "Thread.h" +#endif +#include "nvstore.h" +#include "sha256.h" + +// Define this if you want to run the test locally (requires SPIF/SD block device) +#undef LOCAL_TEST + +#if !STORAGELITE_ENABLED +#error [NOT_SUPPORTED] StorageLite needs to be enabled for this test +#endif + +// MBED_TEST_SIM_BLOCKDEVICE is used here only to filter out inappropriate boards (little RAM etc.) +#if !defined(MBED_TEST_SIM_BLOCKDEVICE) && !defined(LOCAL_TEST) +#error [NOT_SUPPORTED] StorageLite test not supported on this platform +#endif + +using namespace utest::v1; + +static const uint32_t data_buf_size = 10; +static const uint16_t default_name_size = 16; +static const uint8_t default_name = 1; +static const uint8_t non_exist_file_name = 2; +static const uint8_t fr_file_name = 3; +static const uint8_t empty_file_name = 4; + +static const uint16_t name_max_size = StorageLite::max_name_size; +static const uint32_t invalid_flags = 0xFFFF; + +static const size_t bd_size = 8192; +static const size_t bd_erase_size = 4096; +static const size_t bd_prog_size = 16; +static const size_t bd_read_size = 1; + +StorageLite *stlite = NULL; +HeapBlockDevice bd(bd_size, bd_read_size, bd_prog_size, bd_erase_size); +FlashSimBlockDevice flash_bd(&bd); + +static void terminated() +{ + int status = STORAGELITE_SUCCESS; + + stlite->reset(); + status = stlite->deinit(); + TEST_ASSERT_EQUAL_MESSAGE(STORAGELITE_SUCCESS, status, "StorageLite::deinit failed\n"); + + delete stlite; +} + +static void set_thread(uint8_t *file_name) +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->set(file_name, 1, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); +} + +/*------------------set()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_set_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(NULL, 0, data_buf, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_set_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(NULL, default_name_size, data_buf, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_set_name_len_zero_name_not_null() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, 0, data_buf, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_set_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, name_max_size + 1, data_buf, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : invalid flags +static void storagelite_set_invalid_flags() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, invalid_flags); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : buf_size is not 0 and buf is null +static void storagelite_set_buf_size_not_zero_buf_null() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->set(&default_name, default_name_size, NULL, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : buf_size is 0 and buf is not null +static void storagelite_set_buf_size_zero_buf_not_null() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//buf size is 0 and buf is null +static void storagelite_set_buf_size_zero_buf_null() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->set(&default_name, default_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//set two files with the same parameters +static void storagelite_set_two_files_same_params() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, 0); + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//multithreaded set, check with get all files were created succesfully +static void storagelite_set_multithreded() +{ + int status = STORAGELITE_SUCCESS; + Thread T1, T2, T3; + uint8_t i1 = 0; + uint8_t i2 = 1; + uint8_t i3 = 2; + + osStatus err = T1.start(callback(set_thread, &i1)); + if (err) { + TEST_FAIL_MESSAGE("creating thread failed!"); + } + + err = T2.start(callback(set_thread, &i2)); + if (err) { + TEST_FAIL_MESSAGE("creating thread failed!"); + } + + err = T3.start(callback(set_thread, &i3)); + if (err) { + TEST_FAIL_MESSAGE("creating thread failed!"); + } + + err = T1.join(); + if (err) { + TEST_FAIL_MESSAGE("joining thread failed!"); + } + err = T2.join(); + if (err) { + TEST_FAIL_MESSAGE("joining thread failed!"); + } + err = T3.join(); + if (err) { + TEST_FAIL_MESSAGE("joining thread failed!"); + } + + size_t actual_len_bytes = 0; + + for (uint8_t i = 0; i < 3; i++) + { + status = stlite->get(&i, 1, NULL, 0, actual_len_bytes); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + } + + terminated(); +} + +/*------------------get()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_get_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(NULL, 0, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_get_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(NULL, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_get_name_len_zero_name_not_null() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, 0, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_get_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, name_max_size + 1, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : buf_size is not 0 and buf is null +static void storagelite_get_buf_size_not_zero_buf_null() +{ + int status = STORAGELITE_SUCCESS; + size_t actual_len_bytes = 0; + status = stlite->get(&default_name, default_name_size, NULL, data_buf_size, actual_len_bytes); + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : buf_size is 0 and buf is not null +static void storagelite_get_not_empty_file_buf_size_zero_buf_null() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + status = stlite->get(&default_name, default_name_size, NULL, 0, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//bad params : buf_size is 0 and buf is null (when file req. is not empty) +static void storagelite_get_empty_file_buf_size_zero_buf_null() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->set(&empty_file_name, default_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&empty_file_name, default_name_size, NULL, 0, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//get file with valid params but with insufficient buf size +static void storagelite_get_buf_size_insufficient() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size / 2, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//get a non existing file +static void storagelite_get_non_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&non_exist_file_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get an existing file +static void storagelite_get_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(data_buf_size, actual_len_bytes); + terminated(); +} + +//get a removed file +static void storagelite_get_removed_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//flags: set RollBack - file retrieved ok +static void storagelite_get_rollback_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, StorageLite::rollback_protect_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//flags: set RollBack - corrupt file’s CMAC record on Nvstore +static void storagelite_get_corrupt_rollback_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, StorageLite::rollback_protect_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + NVStore& nvstore = NVStore::get_instance(); + status = nvstore.reset(); + TEST_ASSERT_EQUAL(NVSTORE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_RB_PROTECT_ERROR, status); + terminated(); +} + +//flags: set Encrypt - file retrieved ok +static void storagelite_get_encrypt_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, StorageLite::encrypt_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +/*------------------remove()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_remove_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(NULL, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_remove_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(NULL, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_remove_name_not_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_remove_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, name_max_size + 1); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//set a file and remove it +static void storagelite_remove_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//try to remove a non existing file +static void storagelite_remove_non_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&non_exist_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//try to remove a removed file +static void storagelite_remove_removed_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//set FR file, remove it and try to get it +static void storagelite_remove_fr_file_try_get() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&fr_file_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +/*------------------get_file_size()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_get_item_size_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(NULL, 0, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_get_item_size_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(NULL, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_get_item_size_name_not_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(&default_name, 0, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_get_item_size_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(&default_name, name_max_size + 1, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//get_item_size that doesn’t exist +static void storagelite_get_item_size_non_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(&non_exist_file_name, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get_item_size that exists +static void storagelite_get_item_size_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + size_t actual_data_size = 0; + status = stlite->get_file_size(&default_name, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(data_buf_size, actual_data_size); + terminated(); +} + +//get_item_size that was removed +static void storagelite_get_item_size_removed_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_data_size = 0; + status = stlite->get_file_size(&default_name, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get_item_size for empty file +static void storagelite_get_item_size_empty_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t empty_file_name[] = "empty_file"; + status = stlite->set(empty_file_name, default_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_data_size = data_buf_size; + status = stlite->get_file_size(empty_file_name, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(0, actual_data_size); + terminated(); +} + +//get_item_size for modified file +static void storagelite_get_item_size_modified_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size * 2] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size * 2, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_data_size = 0; + status = stlite->get_file_size(&default_name, default_name_size, actual_data_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(data_buf_size * 2, actual_data_size); + terminated(); +} + +//set FR file, remove it and try to get_item_size on it +static void storagelite_get_item_size_fr_file_try_get() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get_file_size(&fr_file_name, default_name_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +/*------------------file_exists()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_file_exists_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(NULL, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_file_exists_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(NULL, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_file_exists_name_not_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(&default_name, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_file_exists_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(&default_name, name_max_size + 1); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//search item that doesn’t exist +static void storagelite_file_exists_non_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(&non_exist_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//search item that exists +static void storagelite_file_exists_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->file_exists(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//search item that was removed +static void storagelite_file_exists_removed_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->file_exists(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//set FR file, remove it and try to item_exist it +static void storagelite_file_exists_fr_file_try_get() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->file_exists(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +/*------------------get_file_flags()------------------*/ + +//bad params : name is NULL and name_length is 0 +static void storagelite_get_file_flags_name_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(NULL, 0, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name is NULL and name_length is not 0 +static void storagelite_get_file_flags_name_null_name_len_not_zero() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(NULL, default_name_size, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is 0 and name blob is not NULL +static void storagelite_get_file_flags_name_not_null_name_len_zero() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(&default_name, 0, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//bad params : name_length is big not valid number +static void storagelite_get_file_flags_name_len_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(&default_name, name_max_size + 1, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//get_item_flags for a non existing file +static void storagelite_get_file_flags_non_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(&non_exist_file_name, default_name_size, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get_item_flags for a an existing file +static void storagelite_get_file_flags_existing_file() +{ + int status = STORAGELITE_SUCCESS; + + uint32_t flags = 0; + status = stlite->get_file_flags(&default_name, default_name_size, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//get_item_flags for a removed file +static void storagelite_get_file_flags_removed_file() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->remove(&default_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + uint32_t flags = 0; + status = stlite->get_file_flags(&default_name, default_name_size, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//set FR file, remove it and try to item_exist it +static void storagelite_get_file_flags_fr_file_try_get() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + uint32_t flags = 0; + status = stlite->get_file_flags(&fr_file_name, default_name_size, flags); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +/*------------------get_first_file()------------------*/ + +//bad params : max_name_size is 0 +static void storagelite_get_first_file_max_name_size_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + status = stlite->get_first_file(file_name, 0, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//bad params : max_name_size is bigger than allowed +static void storagelite_get_first_file_max_name_bigger_than_max() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size + 1; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//get_first when no file in storage +static void storagelite_get_first_file_no_file_in_storage() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {1}; + uint16_t file_name_buf_size = name_max_size; + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get_first with max_name_size smaller than files name size +static void storagelite_get_first_file_max_name_size_too_small() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = 1; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//valid flow : get_first with valid files +static void storagelite_get_first_file_valid_flow() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//get_first with max_name_size smaller than all files name size but one +static void storagelite_get_first_file_not_first() +{ + int status = STORAGELITE_SUCCESS; + uint8_t long_file_name[default_name_size * 2] = {0}; + uint16_t long_file_name_size = default_name_size * 2; + + status = stlite->set(long_file_name, long_file_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->set(&default_name, default_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size + 1] = {0}; + uint16_t file_name_buf_size = default_name_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +/*------------------get_next_file()------------------*/ + +//bad params : max_name_size is 0 +static void storagelite_get_next_file_max_name_size_zero() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + file_name_buf_size = 0; + status = stlite->get_next_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//get_next when no file in storage +static void storagelite_get_next_file_no_file_in_storage() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->get_next_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//get_next with max_name_size smaller than files name size +static void storagelite_get_next_file_max_name_size_too_small() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + file_name_buf_size = 1; + status = stlite->get_next_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BUFF_TOO_SMALL, status); + terminated(); +} + +//valid flow : get_next with valid files +static void storagelite_get_next_file_valid_flow() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size] = {0}; + uint16_t file_name_buf_size = name_max_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->get_next_file(file_name, file_name_buf_size + 1, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//get_next with an invalid handle +static void storagelite_get_next_file_invalid_handle() +{ + int status = STORAGELITE_SUCCESS; + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size + 1] = {0}; + uint16_t file_name_buf_size = default_name_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + handle = 15; + status = stlite->get_next_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_BAD_VALUE, status); + terminated(); +} + +//get_next with max_name_size smaller than all files name size but one +static void storagelite_get_next_file_not_first() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t long_file_name[default_name_size * 2] = {0}; + uint16_t long_file_name_size = default_name_size * 2; + + status = stlite->set(long_file_name, long_file_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->set(&default_name, default_name_size, NULL, 0, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t file_name_size = 0; + uint32_t handle = 0; + uint8_t file_name[default_name_size + 1] = {0}; + uint16_t file_name_buf_size = default_name_size; + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->get_first_file(file_name, file_name_buf_size, file_name_size, handle); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +/*------------------Factory Reset tests function------------------*/ + +//set FR file, after FR do get for the file +static void storagelite_factory_reset_get_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->factory_reset(); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&fr_file_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +//set files without FR, after FR do get for the file +static void storagelite_factory_reset_get_file_without_fr_flag() +{ + int status = STORAGELITE_SUCCESS; + + status = stlite->factory_reset(); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->get(&default_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, status); + terminated(); +} + +//set FR file, set the file again without FR, after FR run get on the file +static void storagelite_factory_reset_get_original_fr_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + uint8_t temp_data_buf[data_buf_size * 2] = {0}; + status = stlite->set(&fr_file_name, default_name_size, temp_data_buf, data_buf_size * 2, 0); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->factory_reset(); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&fr_file_name, default_name_size, temp_data_buf, data_buf_size * 2, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(data_buf_size, actual_len_bytes); + terminated(); +} + +//set FR file, set the file again with FR, after FR run get on the file +static void storagelite_factory_reset_get_modified_fr_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + uint8_t temp_data_buf[data_buf_size * 2] = {0}; + status = stlite->set(&fr_file_name, default_name_size, temp_data_buf, data_buf_size * 2, + StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->factory_reset(); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&fr_file_name, default_name_size, temp_data_buf, data_buf_size * 2, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + TEST_ASSERT_EQUAL(data_buf_size * 2, actual_len_bytes); + terminated(); +} + +//set FR file, remove it, after FR do get on the file +static void storagelite_factory_reset_get_removed_fr_file() +{ + int status = STORAGELITE_SUCCESS; + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&fr_file_name, default_name_size, data_buf, data_buf_size, StorageLite::update_backup_flag); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->remove(&fr_file_name, default_name_size); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + status = stlite->factory_reset(); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + size_t actual_len_bytes = 0; + status = stlite->get(&fr_file_name, default_name_size, data_buf, data_buf_size, actual_len_bytes); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + terminated(); +} + +/*------------------handlers------------------*/ + +utest::v1::status_t setup_handler(const Case *const source, const size_t index_of_case) +{ + int status = STORAGELITE_SUCCESS; + + stlite = new StorageLite(); + + status = stlite->init(&flash_bd); + TEST_ASSERT_EQUAL_MESSAGE(STORAGELITE_SUCCESS, status, "StorageLite::init failed\n"); + + return STATUS_CONTINUE; +} + +utest::v1::status_t setup_handler_set_file(const Case *const source, const size_t index_of_case) +{ + int status = STORAGELITE_SUCCESS; + + stlite = new StorageLite(); + + status = stlite->init(&flash_bd); + TEST_ASSERT_EQUAL_MESSAGE(STORAGELITE_SUCCESS, status, "StorageLite::init failed\n"); + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, 0); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + return STATUS_CONTINUE; +} + +utest::v1::status_t setup_handler_set_multiple_files(const Case *const source, const size_t index_of_case) +{ + int status = STORAGELITE_SUCCESS; + + stlite = new StorageLite(); + + status = stlite->init(&flash_bd); + TEST_ASSERT_EQUAL_MESSAGE(STORAGELITE_SUCCESS, status, "StorageLite::init failed\n"); + + uint8_t data_buf[data_buf_size] = {0}; + status = stlite->set(&default_name, default_name_size, data_buf, data_buf_size, 0); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + uint8_t new_file_name = default_name + 1; + status = stlite->set(&new_file_name, default_name_size, data_buf, data_buf_size, 0); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, status); + + return STATUS_CONTINUE; +} + +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[] = { + /*------------------set()------------------*/ + Case("storagelite_set_name_null_name_len zero", + setup_handler, storagelite_set_name_null_name_len_zero), + Case("storagelite_set_name_null_name_len_not_zero", + setup_handler, storagelite_set_name_null_name_len_not_zero), + Case("storagelite_set_name_len_zero_name_not_null", + setup_handler, storagelite_set_name_len_zero_name_not_null), + Case("storagelite_set_name_len_bigger_than_max", + setup_handler, storagelite_set_name_len_bigger_than_max), + Case("storagelite_set_invalid_flags", + setup_handler, storagelite_set_invalid_flags), + Case("storagelite_set_buf_size_not_zero_buf_null", + setup_handler, storagelite_set_buf_size_not_zero_buf_null), + Case("storagelite_set_buf_size_zero_buf_not_null", + setup_handler, storagelite_set_buf_size_zero_buf_not_null), + Case("storagelite_set_buf_size_zero_buf_null", + setup_handler, storagelite_set_buf_size_zero_buf_null), + Case("storagelite_set_two_files_same_params", + setup_handler, storagelite_set_two_files_same_params), + Case("storagelite_set_multithreded", + setup_handler, storagelite_set_multithreded), + /*------------------get()------------------*/ + Case("storagelite_get_name_null_name_len_zero", + setup_handler_set_file, storagelite_get_name_null_name_len_zero), + Case("storagelite_get_name_null_name_len_not_zero", + setup_handler_set_file, storagelite_get_name_null_name_len_not_zero), + Case("storagelite_get_name_len_zero_name_not_null", + setup_handler_set_file, storagelite_get_name_len_zero_name_not_null), + Case("storagelite_get_name_len_bigger_than_max", + setup_handler_set_file, storagelite_get_name_len_bigger_than_max), + Case("storagelite_get_buf_size_not_zero_buf_null", + setup_handler_set_file, storagelite_get_buf_size_not_zero_buf_null), + Case("storagelite_get_not_empty_file_buf_size_zero_buf_null", + setup_handler_set_file, storagelite_get_not_empty_file_buf_size_zero_buf_null), + Case("storagelite_get_empty_file_buf_size_zero_buf_null", + setup_handler_set_file, storagelite_get_empty_file_buf_size_zero_buf_null), + Case("storagelite_get_buf_size_insufficient", + setup_handler_set_file, storagelite_get_buf_size_insufficient), + Case("storagelite_get_non_existing_file", + setup_handler_set_file, storagelite_get_non_existing_file), + Case("storagelite_get_existing_file", + setup_handler_set_file, storagelite_get_existing_file), + Case("storagelite_get_removed_file", + setup_handler_set_file, storagelite_get_removed_file), + Case("storagelite_get_rollback_file", + setup_handler, storagelite_get_rollback_file), + Case("storagelite_get_corrupt_rollback_file", + setup_handler, storagelite_get_corrupt_rollback_file), + Case("storagelite_get_encrypt_file", + setup_handler, storagelite_get_encrypt_file), + /*------------------remove()------------------*/ + Case("storagelite_remove_name_null_name_len_zero", + setup_handler_set_file, storagelite_remove_name_null_name_len_zero), + Case("storagelite_remove_name_null_name_len_not_zero", + setup_handler_set_file, storagelite_remove_name_null_name_len_not_zero), + Case("storagelite_remove_name_not_null_name_len_zero", + setup_handler_set_file, storagelite_remove_name_not_null_name_len_zero), + Case("storagelite_remove_name_len_bigger_than_max", + setup_handler_set_file, storagelite_remove_name_len_bigger_than_max), + Case("storagelite_remove_existing_file", + setup_handler_set_file, storagelite_remove_existing_file), + Case("storagelite_remove_non_existing_file", + setup_handler, storagelite_remove_non_existing_file), + Case("storagelite_remove_removed_file", + setup_handler_set_file, storagelite_remove_removed_file), + Case("storagelite_remove_fr_file_try_get", + setup_handler, storagelite_remove_fr_file_try_get), + /*------------------get_item_size()------------------*/ + Case("storagelite_get_item_size_name_null_name_len_zero", + setup_handler_set_file, storagelite_get_item_size_name_null_name_len_zero), + Case("storagelite_get_item_size_name_null_name_len_not_zero", + setup_handler_set_file, storagelite_get_item_size_name_null_name_len_not_zero), + Case("storagelite_get_item_size_name_not_null_name_len_zero", + setup_handler_set_file, storagelite_get_item_size_name_not_null_name_len_zero), + Case("storagelite_get_item_size_name_len_bigger_than_max", + setup_handler_set_file, storagelite_get_item_size_name_len_bigger_than_max), + Case("storagelite_get_item_size_non_existing_file", + setup_handler_set_file, storagelite_get_item_size_non_existing_file), + Case("storagelite_get_item_size_existing_file", + setup_handler_set_file, storagelite_get_item_size_existing_file), + Case("storagelite_get_item_size_removed_file", + setup_handler_set_file, storagelite_get_item_size_removed_file), + Case("storagelite_get_item_size_empty_file", + setup_handler, storagelite_get_item_size_empty_file), + Case("storagelite_get_item_size_modified_file", + setup_handler_set_file, storagelite_get_item_size_modified_file), + Case("storagelite_get_item_size_fr_file_try_get", + setup_handler_set_file, storagelite_get_item_size_fr_file_try_get), + /*------------------file_exists()------------------*/ + Case("storagelite_file_exists_name_null_name_len_zero", + setup_handler_set_file, storagelite_file_exists_name_null_name_len_zero), + Case("storagelite_file_exists_name_null_name_len_not_zero", + setup_handler_set_file, storagelite_file_exists_name_null_name_len_not_zero), + Case("storagelite_file_exists_name_not_null_name_len_zero", + setup_handler_set_file, storagelite_file_exists_name_not_null_name_len_zero), + Case("storagelite_file_exists_name_len_bigger_than_max", + setup_handler_set_file, storagelite_file_exists_name_len_bigger_than_max), + Case("storagelite_file_exists_non_existing_file", + setup_handler_set_file, storagelite_file_exists_non_existing_file), + Case("storagelite_file_exists_existing_file", + setup_handler_set_file, storagelite_file_exists_existing_file), + Case("storagelite_file_exists_removed_file", + setup_handler_set_file, storagelite_file_exists_removed_file), + Case("storagelite_file_exists_fr_file_try_get", + setup_handler_set_file, storagelite_file_exists_fr_file_try_get), + /*------------------get_file_flags()------------------*/ + Case("storagelite_get_file_flags_name_null_name_len_zero", + setup_handler_set_file, storagelite_get_file_flags_name_null_name_len_zero), + Case("storagelite_get_file_flags_name_null_name_len_not_zero", + setup_handler_set_file, storagelite_get_file_flags_name_null_name_len_not_zero), + Case("storagelite_get_file_flags_name_not_null_name_len_zero", + setup_handler_set_file, storagelite_get_file_flags_name_not_null_name_len_zero), + Case("storagelite_get_file_flags_name_len_bigger_than_max", + setup_handler_set_file, storagelite_get_file_flags_name_len_bigger_than_max), + Case("storagelite_get_file_flags_non_existing_file", + setup_handler_set_file, storagelite_get_file_flags_non_existing_file), + Case("storagelite_get_file_flags_existing_file", + setup_handler_set_file, storagelite_get_file_flags_existing_file), + Case("storagelite_get_file_flags_removed_file", + setup_handler_set_file, storagelite_get_file_flags_removed_file), + Case("storagelite_get_file_flags_fr_file_try_get", + setup_handler_set_file, storagelite_get_file_flags_fr_file_try_get), + /*------------------get_first_file()------------------*/ + Case("storagelite_get_first_file_max_name_size_zero", + setup_handler_set_file, storagelite_get_first_file_max_name_size_zero), + Case("storagelite_get_first_file_max_name_bigger_than_max", + setup_handler_set_file, storagelite_get_first_file_max_name_bigger_than_max), + Case("storagelite_get_first_file_no_file_in_storage", + setup_handler, storagelite_get_first_file_no_file_in_storage), + Case("storagelite_get_first_file_max_name_size_too_small", + setup_handler_set_file, storagelite_get_first_file_max_name_size_too_small), + Case("storagelite_get_first_file_valid_flow", + setup_handler_set_file, storagelite_get_first_file_valid_flow), + Case("storagelite_get_first_file_not_first", + setup_handler, storagelite_get_first_file_not_first), + /*------------------get_next_file()------------------*/ + Case("storagelite_get_next_file_max_name_size_zero", + setup_handler_set_multiple_files, storagelite_get_next_file_max_name_size_zero), + Case("storagelite_get_next_file_no_file_in_storage", + setup_handler_set_file, storagelite_get_next_file_no_file_in_storage), + Case("storagelite_get_next_file_max_name_size_too_small", + setup_handler_set_multiple_files, storagelite_get_next_file_max_name_size_too_small), + Case("storagelite_get_next_file_valid_flow", + setup_handler_set_multiple_files, storagelite_get_next_file_valid_flow), + Case("storagelite_get_next_file_invalid_handle", + setup_handler_set_multiple_files, storagelite_get_next_file_invalid_handle), + Case("storagelite_get_next_file_not_first", + setup_handler_set_multiple_files, storagelite_get_next_file_not_first), + /*------------------factory_reset()------------------*/ + Case("storagelite_factory_reset_get_file", + setup_handler, storagelite_factory_reset_get_file), + Case("storagelite_factory_reset_get_file_without_fr_flag", + setup_handler, storagelite_factory_reset_get_file_without_fr_flag), + Case("storagelite_factory_reset_get_original_fr_file", + setup_handler, storagelite_factory_reset_get_original_fr_file), + Case("storagelite_factory_reset_get_modified_fr_file", + setup_handler, storagelite_factory_reset_get_modified_fr_file), + Case("storagelite_factory_reset_get_removed_fr_file", + setup_handler, storagelite_factory_reset_get_removed_fr_file), +}; + +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); +} diff --git a/features/filesystem/storagelite/TESTS/storagelite/host_tests/storagelite_reset.py b/features/filesystem/storagelite/TESTS/storagelite/host_tests/storagelite_reset.py new file mode 100644 index 00000000000..4c5fa66857e --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/host_tests/storagelite_reset.py @@ -0,0 +1,105 @@ +""" +mbed SDK +Copyright (c) 2017-2017 ARM Limited + +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. +""" +from __future__ import print_function + +from mbed_host_tests import BaseHostTest +from time import sleep +import random + + +class StorageLiteResetTest(BaseHostTest): + """This test resets the board a few times, in order to test StorageLite. + """ + + """Number of times to reset the device in this test""" + RESET_COUNT = 10 + RESET_DELAY_BASE = 4.0 + VALUE_PLACEHOLDER = "0" + + def setup(self): + """Register callbacks required for the test""" + self._error = False + generator = self.storagelite_reset_test() + generator.next() + + def run_gen(key, value, time): + """Run the generator, and fail testing if the iterator stops""" + if self._error: + return + try: + generator.send((key, value, time)) + except StopIteration: + self._error = True + + for resp in ("start", "read", "format_done", "init_done", "reset_complete"): + self.register_callback(resp, run_gen) + + def teardown(self): + """No work to do here""" + pass + + def storagelite_reset_test(self): + """Generator for running the reset test + + This function calls yield to wait for the next event from + the device. If the device gives the wrong response, then the + generator terminates by returing which raises a StopIteration + exception and fails the test. + """ + + # Wait for start token + key, value, time = yield + if key != "start": + return + + # Format the device before starting the test + self.send_kv("format", self.VALUE_PLACEHOLDER) + key, value, time = yield + if key != "format_done": + return + + for i in range(self.RESET_COUNT): + + self.send_kv("init", self.VALUE_PLACEHOLDER) + key, value, time = yield + self.log("Key from yield: %s" % key) + if key != "init_done": + return + + self.send_kv("run", self.VALUE_PLACEHOLDER) + sleep(self.RESET_DELAY_BASE + random.uniform(0.0, 2.0)) + + self.reset() + + # Wait for start token + key, value, time = yield + self.log("Key from yield: %s" % key) + if key != "reset_complete": + return + + + self.send_kv("__sync", "00000000-0000-000000000-000000000000") + + # Wait for start token + key, value, time = yield + if key != "start": + return + + self.send_kv("exit", "pass") + + yield # No more events expected + diff --git a/features/filesystem/storagelite/TESTS/storagelite/resilience/main.cpp b/features/filesystem/storagelite/TESTS/storagelite/resilience/main.cpp new file mode 100644 index 00000000000..a711b02b70f --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/resilience/main.cpp @@ -0,0 +1,228 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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 "StorageLite.h" +#include "Timer.h" +#include "HeapBlockDevice.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest/utest.h" +#include +#include +#include + + +#if !STORAGELITE_ENABLED +#error [NOT_SUPPORTED] StorageLite needs to be enabled for this test +#endif + +// FIXME: This test should use a persistent block device (i.e. MBED_TEST_BLOCKDEVICE and not +// MBED_TEST_SIM_BLOCKDEVICE). However, MBED_TEST_BLOCKDEVICE is currently not supported due to +// build issues, so test should only work in local mode (LOCAL_TEST). MBED_TEST_SIM_BLOCKDEVICE is still +// used in order to filter out inappropriate boards (little RAM etc.). +#if !defined(MBED_TEST_SIM_BLOCKDEVICE) && !defined(LOCAL_TEST) +#error [NOT_SUPPORTED] StorageLite test not supported on this platform +#endif + + +#define TEST_ASSERT_OR_EXIT(condition) \ + TEST_ASSERT(condition); if (!(condition)) {error("Assert failed");} + +#define TEST_ASSERT_EQUAL_OR_EXIT(expected, actual) \ + TEST_ASSERT_EQUAL(expected, actual); if ((int64_t)(expected) != (int64_t)(actual)) {error("Assert failed");} + + +// Define this if you want to run the test locally (requires SPIF/SD block device) +#undef LOCAL_TEST + +#ifndef MBED_TEST_SIM_BLOCKDEVICE +#define MBED_TEST_SIM_BLOCKDEVICE SPIFBlockDevice +// FIXME: use proper SPI defines here +#define MBED_TEST_SIM_BLOCKDEVICE_DECL SPIFBlockDevice bd(D11, D12, D13, D8); +#endif + +#ifdef LOCAL_TEST +#define TEST_SPIF +#include "SPIFBlockDevice.h" + +#else +#define STRINGIZE(x) STRINGIZE2(x) +#define STRINGIZE2(x) #x +#define INCLUDE(x) STRINGIZE(x.h) + +#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE) +#endif + +#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL +#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(64 * 512, 1, 1, 512) +#endif + +MBED_TEST_SIM_BLOCKDEVICE_DECL; + +using namespace utest::v1; + +const char *iter_file_name = "iter_file"; +const size_t num_files = 128; + +typedef enum { + CMD_STATUS_PASS, + CMD_STATUS_FAIL, + CMD_STATUS_CONTINUE, + CMD_STATUS_ERROR +} cmd_status_t; + +static StorageLite stlite; + +static void test_init() +{ + int result = stlite.init(&bd, num_files); + TEST_ASSERT_EQUAL_OR_EXIT(0, result); +} + +static void test_format() +{ + int result = stlite.reset(); + TEST_ASSERT_EQUAL_OR_EXIT(0, result); + int num_iters = 0; + result = stlite.set(iter_file_name, strlen(iter_file_name), &num_iters, sizeof(num_iters), 0); + TEST_ASSERT_EQUAL_OR_EXIT(0, result); +} + +static void test_run(bool verify) +{ + uint8_t *file_name, *get_buf, *set_buf; + size_t file_name_size = 16; + size_t data_size = 32; + size_t actual_data_size; + int result; + int curr_iter, start_iter, num_iters; + int file_ind; + + file_name = new uint8_t[file_name_size]; + get_buf = new uint8_t[data_size]; + set_buf = new uint8_t[data_size]; + + result = stlite.get(iter_file_name, strlen(iter_file_name), &num_iters, sizeof(num_iters), actual_data_size); + TEST_ASSERT_EQUAL_OR_EXIT(0, result); + + if (verify) { + start_iter = 0; + printf("Verifying iteration %d\n", num_iters); + } else { + num_iters++; + printf("Writing iteration %d\n", num_iters); + result = stlite.set(iter_file_name, strlen(iter_file_name), &num_iters, sizeof(num_iters), 0); + TEST_ASSERT_EQUAL_OR_EXIT(0, result); + start_iter = num_iters - 1; + } + + for (;;) { + for (curr_iter = start_iter; curr_iter < num_iters; curr_iter++) { + for (file_ind = 0; file_ind < curr_iter; file_ind++) { + sprintf((char *) file_name, "file%d", curr_iter * 256 + file_ind); + file_name_size = strlen((char *) file_name); + memset(set_buf, curr_iter, data_size / 2); + memset(set_buf + data_size / 2, file_ind, data_size / 2); + bool exists = true; + if ((file_ind % 3) == 1) { + result = stlite.file_exists(file_name, file_name_size); + if (result == STORAGELITE_NOT_FOUND) { + exists = false; + } else { + TEST_ASSERT_EQUAL_OR_EXIT(STORAGELITE_SUCCESS, result); + } + } + if (verify) { + if (!exists) { + continue; + } + result = stlite.get(file_name, file_name_size, get_buf, data_size, actual_data_size); + TEST_ASSERT_EQUAL_OR_EXIT(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL_OR_EXIT(data_size, actual_data_size); + TEST_ASSERT_EQUAL_OR_EXIT(0, memcmp(set_buf, get_buf, data_size)); + } else { + if (exists && ((file_ind % 3) == 1)) { + result = stlite.remove(file_name, file_name_size); + } else { + uint32_t flags = ((rand() % 2) & StorageLite::encrypt_flag) | + ((rand() % 2) & StorageLite::update_backup_flag) | + ((rand() % 2) & StorageLite::rollback_protect_flag); + result = stlite.set(file_name, file_name_size, set_buf, data_size, flags); + } + TEST_ASSERT_EQUAL_OR_EXIT(STORAGELITE_SUCCESS, result); + } + } + } + if (verify) { + break; + } + } + + delete[] file_name; + delete[] get_buf; + delete[] set_buf; +} + +static cmd_status_t handle_command(const char *key, const char *value) +{ + if (strcmp(key, "format") == 0) { + test_init(); + test_format(); + greentea_send_kv("format_done", 1); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "init") == 0) { + test_init(); + greentea_send_kv("init_done", 1); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "run") == 0) { + test_run(true); + test_run(false); + return CMD_STATUS_CONTINUE; + + } else if (strcmp(key, "exit") == 0) { + if (strcmp(value, "pass") != 0) { + return CMD_STATUS_FAIL; + } + test_init(); + test_run(true); + return CMD_STATUS_PASS; + + } else { + return CMD_STATUS_ERROR; + } +} + +int main() +{ + GREENTEA_SETUP(120, "storagelite_reset"); + + char key[10 + 1] = {}; + char value[128 + 1] = {}; + + greentea_send_kv("start", 1); + + // Handshake with host + cmd_status_t cmd_status = CMD_STATUS_CONTINUE; + while (CMD_STATUS_CONTINUE == cmd_status) { + memset(key, 0, sizeof(key)); + memset(value, 0, sizeof(value)); + greentea_parse_kv(key, value, sizeof(key) - 1, sizeof(value) - 1); + cmd_status = handle_command(key, value); + } + + GREENTEA_TESTSUITE_RESULT(CMD_STATUS_PASS == cmd_status); +} diff --git a/features/filesystem/storagelite/TESTS/storagelite/wear_level/main.cpp b/features/filesystem/storagelite/TESTS/storagelite/wear_level/main.cpp new file mode 100644 index 00000000000..0b47a2cff5c --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/wear_level/main.cpp @@ -0,0 +1,135 @@ +/* +* 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 "StorageLite.h" +#ifdef MBED_CONF_RTOS_PRESENT +#include "Thread.h" +#endif +#include "Timer.h" +#include "HeapBlockDevice.h" +#include "FlashSimBlockDevice.h" +#include "ExhaustibleBlockDevice.h" +#include "greentea-client/test_env.h" +#include "unity/unity.h" +#include "utest/utest.h" +#include +#include + +// Define this if you want to run the test locally (requires SPIF/SD block device) +#undef LOCAL_TEST + +#if !STORAGELITE_ENABLED +#error [NOT_SUPPORTED] StorageLite needs to be enabled for this test +#endif + +// MBED_TEST_SIM_BLOCKDEVICE is used here only to filter out inappropriate boards (little RAM etc.) +#if !defined(MBED_TEST_SIM_BLOCKDEVICE) && !defined(LOCAL_TEST) +#error [NOT_SUPPORTED] StorageLite test not supported on this platform +#endif + +using namespace utest::v1; + +static const size_t test_num_blocks = 64; +static const size_t max_erases = 64; +static const size_t block_size = 512; + +static uint64_t test_wear_leveling_size(size_t num_blocks) +{ + uint8_t *file_name, *get_buf, *set_buf; + size_t file_name_size = 32; + size_t data_size = 256; + size_t num_files = 8; + size_t actual_data_size; + int result; + + HeapBlockDevice heap_bd(num_blocks * block_size, 1, 1, block_size); + FlashSimBlockDevice flash_bd(&heap_bd); + ExhaustibleBlockDevice exhaust_bd(&flash_bd, max_erases); + + StorageLite stlite; + + result = stlite.init(&exhaust_bd, 128); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.reset(); + TEST_ASSERT_EQUAL(0, result); + + file_name = new uint8_t[file_name_size]; + set_buf = new uint8_t[data_size]; + get_buf = new uint8_t[data_size]; + + printf("Testing size %ld (%ld blocks * %ld bytes each). Max erases %ld.\n", + num_blocks * block_size, num_blocks, block_size, max_erases); + uint64_t cycles = 0; + for (;;) { + memset(file_name, '0' + cycles % num_files, file_name_size); + memset(set_buf, cycles % 256, data_size); + result = stlite.set(file_name, file_name_size, set_buf, data_size, 0); + if (result) { + TEST_ASSERT_EQUAL(STORAGELITE_WRITE_ERROR, result); + break; + } + + cycles++; + result = stlite.get(file_name, file_name_size, get_buf, data_size, actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(data_size, actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(set_buf, get_buf, data_size)); + } + + uint64_t gross_bytes = (data_size + file_name_size + 32) * cycles; + uint64_t net_bytes = data_size * cycles; + + printf("Total write cycles: %lld, gross bytes %lld, net bytes %lld\n", + cycles, gross_bytes, net_bytes); + + delete[] file_name; + delete[] get_buf; + delete[] set_buf; + + return cycles; +} + +static void test_wear_leveling() +{ + uint64_t cycles_1 = test_wear_leveling_size(test_num_blocks / 2); + uint64_t cycles_2 = test_wear_leveling_size(test_num_blocks); + TEST_ASSERT(cycles_2 > cycles_1 * 2); +} + + +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("StorageLite: Wear level test", test_wear_leveling, greentea_failure_handler), +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(400, "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); +} diff --git a/features/filesystem/storagelite/TESTS/storagelite/whitebox/main.cpp b/features/filesystem/storagelite/TESTS/storagelite/whitebox/main.cpp new file mode 100644 index 00000000000..e0bac6fbc5a --- /dev/null +++ b/features/filesystem/storagelite/TESTS/storagelite/whitebox/main.cpp @@ -0,0 +1,695 @@ +/* +* 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 "StorageLite.h" +#include "StorageLiteFS.h" +#ifdef MBED_CONF_RTOS_PRESENT +#include "Thread.h" +#endif +#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 "mbed_retarget.h" +#include +#include +#include +#include + +// Define this if you want to run the test locally (requires SPIF/SD block device) +#undef LOCAL_TEST + +#if !STORAGELITE_ENABLED +#error [NOT_SUPPORTED] StorageLite needs to be enabled for this test +#endif + +// MBED_TEST_SIM_BLOCKDEVICE is used here only to filter out inappropriate boards (little RAM etc.) +#if !defined(MBED_TEST_SIM_BLOCKDEVICE) && !defined(LOCAL_TEST) +#error [NOT_SUPPORTED] StorageLite test not supported on this platform +#endif + +#ifdef LOCAL_TEST + +#define TEST_SPIF +#undef TEST_SD + +#ifdef TEST_SPIF +#include "SPIFBlockDevice.h" +#ifdef TARGET_K82F +SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5); +#else +SPIFBlockDevice bd(D11, D12, D13, D8); +#endif +SlicingBlockDevice flash_bd(&bd, 0, 64 * 4096); + +#elif defined(TEST_SD) +#include "SDBlockDevice.h" +SDBlockDevice bd(PTE3, PTE1, PTE2, PTE4); +SlicingBlockDevice slice_bd(&bd, 0, 512 * 512); +FlashSimBlockDevice flash_bd(&slice_bd); + +#else +// Satisfy compiler +HeapBlockDevice bd(512, 1, 1, 4); +FlashSimBlockDevice flash_bd(&bd); +#endif + +#else +// Satisfy compiler +HeapBlockDevice bd(512, 1, 1, 4); +FlashSimBlockDevice flash_bd(&bd); +#endif + +using namespace utest::v1; + +typedef struct { + size_t size; + size_t read_size; + size_t prog_size; + size_t erase_size; +} bd_params_t; + + +static const char * const file1_name = "file1"; +static const char * const file1_val1 = "val1"; +static const char * const file2_name = "name of file2"; +static const char * const file2_val1 = "val3"; +static const char * const file2_val2 = "val2 of file 2"; +static const char * const file2_val3 = "Val1 value of file 2 "; +static const char * const file3_name = "This is the name of file3"; +static const char * const file3_val1 = "Data value of file 3 is the following"; +static const char * const file4_name = "This is the name of file4"; +static const char * const file4_val1 = "Is this the value of file 4?"; +static const char * const file4_val2 = "What the hell is the value of file 4, god damn it!"; + + +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 + }; + + int num_bds = sizeof(bd_params) / sizeof(bd_params_t); + uint8_t get_buf[256]; + size_t actual_name_size; + size_t actual_data_size; + int result; + uint32_t handle; + mbed::Timer timer; + int elapsed; + +#if defined(TEST_SPIF) || defined(TEST_SD) + 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++) { + 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; + // Required for deinit + flash_sim_bd.init(); + } else { + test_bd = &flash_sim_bd; + } + + StorageLite stlite; + + timer.reset(); + result = stlite.init(test_bd, 128); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + elapsed = timer.read_ms(); + printf("Elapsed time for init %d ms\n", elapsed); + + printf("StorageLite size is %u bytes\n", stlite.size()); + + timer.reset(); + result = stlite.reset(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + elapsed = timer.read_ms(); + printf("Elapsed time for reset is %d ms\n", elapsed); + + result = stlite.set(file1_name, file1_val1, + strlen(file1_val1), StorageLite::encrypt_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.set(file2_name, file2_val1, + strlen(file2_val1), StorageLite::update_backup_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.set(file2_name, strlen(file2_name), file2_val2, + strlen(file2_val2), StorageLite::update_backup_flag | StorageLite::rollback_protect_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + timer.reset(); + result = stlite.set(file2_name, file2_val3, + strlen(file2_val3), StorageLite::encrypt_flag | StorageLite::rollback_protect_flag); + elapsed = timer.read_ms(); + printf("Elapsed time for set is %d ms\n", elapsed); + + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.set(file3_name, file3_val1, + strlen(file3_val1), StorageLite::update_backup_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.get(file3_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file3_val1), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file3_val1, get_buf, strlen(file3_val1))); + + for (int j = 0; j < 2; j++) { + result = stlite.set(file4_name, file4_val1, + strlen(file4_val1), StorageLite::encrypt_flag | StorageLite::update_backup_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.set(file4_name, strlen(file4_name), file4_val2, + strlen(file4_val2), StorageLite::encrypt_flag); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + + result = stlite.remove(file3_name); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + for (int i = 0; i < 2; i++) { + printf("%s deinit/init\n", i ? "After" : "Before"); + result = stlite.get(file1_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file1_val1), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file1_val1, get_buf, strlen(file1_val1))); + + timer.reset(); + result = stlite.get(file2_name, strlen(file2_name), 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(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file2_val3), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file2_val3, get_buf, strlen(file2_val3))); + + result = stlite.get(file3_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, result); + + result = stlite.get(file4_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file4_val2), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file4_val2, get_buf, strlen(file4_val2))); + + result = stlite.get_first_file(get_buf, sizeof(get_buf), actual_name_size, handle); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file1_name), actual_name_size); + TEST_ASSERT_EQUAL(0, memcmp(file1_name, get_buf, strlen(file1_name))); + + result = stlite.get_next_file(get_buf, sizeof(get_buf), actual_name_size, handle); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file2_name), actual_name_size); + TEST_ASSERT_EQUAL(0, memcmp(file2_name, get_buf, strlen(file2_name))); + + result = stlite.get_next_file(get_buf, sizeof(get_buf), actual_name_size, handle); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file4_name), actual_name_size); + TEST_ASSERT_EQUAL(0, memcmp(file4_name, get_buf, strlen(file4_name))); + + result = stlite.get_next_file(get_buf, sizeof(get_buf), actual_name_size, handle); + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, result); + + result = stlite.deinit(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + timer.reset(); + result = stlite.init(test_bd); + elapsed = timer.read_ms(); + printf("Elapsed time for init is %d ms\n", elapsed); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + + timer.reset(); + result = stlite.factory_reset(); + elapsed = timer.read_ms(); + printf("Elapsed time for factory reset is %d ms\n", elapsed); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + printf("Factory reset Set/get tests\n"); + for (int i = 0; i < 2; i++) { + printf("%s deinit/init\n", i ? "After" : "Before"); + result = stlite.get(file1_name, strlen(file1_name), get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, result); + + result = stlite.get(file2_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file2_val2), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file2_val2, get_buf, strlen(file2_val2))); + + result = stlite.get(file3_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file3_val1), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file3_val1, get_buf, strlen(file3_val1))); + + result = stlite.get(file4_name, get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(strlen(file4_val1), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file4_val1, get_buf, strlen(file4_val1))); + + result = stlite.deinit(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.init(test_bd); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + + result = stlite.deinit(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } +} + +static void white_box_fs_test() +{ + uint8_t get_buf[256]; + int result; + mbed::File file; + +#if !defined(TEST_SPIF) && !defined(TEST_SD) + HeapBlockDevice heap_bd(4096 * 4, 1, 1, 4096); + FlashSimBlockDevice flash_bd(&heap_bd); +#endif + + StorageLite stlite; + + result = stlite.init(&flash_bd); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + + result = stlitefs.mount(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + + result = stlitefs.reformat(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + + result = file.open(&stlitefs, file1_name, O_WRONLY); + TEST_ASSERT_EQUAL(0, result); + + result = file.write(file1_val1, strlen(file1_val1)); + TEST_ASSERT_EQUAL(strlen(file1_val1), result); + + result = file.close(); + TEST_ASSERT_EQUAL(0, result); + + result = file.open(&stlitefs, file2_name, O_WRONLY); + TEST_ASSERT_EQUAL(0, result); + + result = file.write(file2_val1, strlen(file2_val1)); + TEST_ASSERT_EQUAL(strlen(file2_val1), result); + + result = file.write(file2_val2, strlen(file2_val2)); + TEST_ASSERT_EQUAL(-ENOSYS, result); + + result = file.close(); + TEST_ASSERT_EQUAL(0, result); + + result = stlitefs.remove(file2_name); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + for (int i = 0; i < 2; i++) { + printf("%s unmount/mount\n", i ? "After" : "Before"); + + size_t actual_data_size; + result = stlite.get(file1_name, strlen(file1_name), get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(0, result); + TEST_ASSERT_EQUAL(strlen(file1_val1), actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(file1_val1, get_buf, strlen(file1_val1))); + + result = file.open(&stlitefs, file1_name, O_RDONLY); + TEST_ASSERT_EQUAL(0, result); + + result = file.read(get_buf, sizeof(get_buf)); + TEST_ASSERT_EQUAL(strlen(file1_val1), result); + TEST_ASSERT_EQUAL(0, memcmp(file1_val1, get_buf, strlen(file1_val1))); + + result = file.close(); + TEST_ASSERT_EQUAL(0, result); + + result = stlite.get(file2_name, strlen(file2_name), get_buf, + sizeof(get_buf), actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, result); + + result = file.open(&stlitefs, file2_name, O_RDONLY); + TEST_ASSERT_EQUAL(-ENOENT, result); + + result = stlitefs.unmount(); + TEST_ASSERT_EQUAL(0, result); + + result = stlitefs.mount(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + } + + result = stlitefs.reformat(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + for (int i = 0; i < 2; i++) { + printf("%s unmount/mount\n", i ? "After" : "Before"); + + result = file.open(&stlitefs, file1_name, O_RDONLY); + TEST_ASSERT_EQUAL(-ENOENT, result); + + result = stlitefs.unmount(); + TEST_ASSERT_EQUAL(0, result); + + result = stlitefs.mount(&flash_bd); + TEST_ASSERT_EQUAL(0, result); + } +} + +static void multi_set_test() +{ + char *file_name; + uint8_t *get_buf, *set_buf; + size_t file_name_size = 32; + size_t data_size = 512; + size_t num_files = 32; + size_t set_iters = 3; + size_t actual_data_size; + int result; + mbed::Timer timer; + mbed::File file; + int elapsed; + size_t i; + uint32_t flags; + uint8_t file_ind; + + timer.start(); +#if !defined(TEST_SPIF) && !defined(TEST_SD) + HeapBlockDevice heap_bd(4096 * 64, 1, 1, 4096); + FlashSimBlockDevice flash_bd(&heap_bd); +#endif + + StorageLite stlite; + + timer.reset(); + result = stlite.init(&flash_bd, 128); + elapsed = timer.read_ms(); + printf("Elapsed time for initial init is %d ms\n", elapsed); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + StorageLiteFS stlitefs("stfs", &stlite, StorageLite::encrypt_flag); + + file_name = new char[file_name_size + 1]; + get_buf = new uint8_t[data_size]; + set_buf = new uint8_t[data_size]; + + srand(1); + for (i = 0; i < file_name_size; i++) { + // printable characters only + file_name[i] = rand() % 223 + 32; + } + file_name[file_name_size] = '\0'; + + for (i = 0; i < data_size; i++) { + set_buf[i] = rand() % 256; + } + + for (int st_type = 0; st_type < 2; st_type++) { + int max_set_time = 0, total_set_time = 0; + int max_get_time = 0, total_get_time = 0; + + timer.reset(); + if (st_type) { + result = stlitefs.reformat(&flash_bd); + } else { + result = stlite.reset(); + } + elapsed = timer.read_ms(); + printf("Elapsed time for %s is %d ms\n", st_type ? "reformat" : "reset", elapsed); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + for (i = 0; i < set_iters; i++) { + for (file_ind = 0; file_ind < num_files; file_ind++) { + file_name[0] = '0' + file_ind; + set_buf[0] = file_ind * (i + 1); + timer.reset(); + if (st_type) { + result = file.open(&stlitefs, (const char *) file_name, O_WRONLY); + TEST_ASSERT_EQUAL(0, result); + + actual_data_size = file.write(set_buf, data_size); + + result = file.close(); + } else { + flags = StorageLite::encrypt_flag; + if (i == (file_ind % set_iters)) { + flags |= StorageLite::update_backup_flag; + } + if (rand() % 2) { + flags |= StorageLite::rollback_protect_flag; + } + result = stlite.set(file_name, set_buf, data_size, flags); + } + elapsed = timer.read_ms(); + TEST_ASSERT_EQUAL(0, result); + if (elapsed > max_set_time) { + max_set_time = elapsed; + } + total_set_time += elapsed; + } + } + + for (file_ind = 0; file_ind < num_files; file_ind++) { + file_name[0] = '0' + file_ind; + set_buf[0] = file_ind * set_iters; + timer.reset(); + if (st_type) { + result = file.open(&stlitefs, (const char *) file_name, O_RDONLY); + TEST_ASSERT_EQUAL(0, result); + + actual_data_size = file.read(get_buf, data_size); + + result = file.close(); + } else { + result = stlite.get(file_name, get_buf, data_size, actual_data_size); + } + elapsed = timer.read_ms(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(data_size, actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(set_buf, get_buf, data_size)); + if (elapsed > max_get_time) { + max_get_time = elapsed; + } + total_get_time += elapsed; + } + + printf("%s time: Total (%d * %d times) - %d ms, Average - %d ms, Max - %d ms\n", + st_type ? "open+write+close" : "set", set_iters, num_files, total_set_time, + total_set_time / (set_iters * num_files), max_set_time); + printf("%s time: Total (%d times) - %d ms, Average - %d ms, Max - %d ms\n", + st_type ? "open+read+close" : "get", num_files, total_get_time, + total_get_time / num_files, max_get_time); + + if (st_type) { + result = stlitefs.unmount(); + } else { + result = stlite.deinit(); + } + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + timer.reset(); + if (st_type) { + result = stlitefs.mount(&flash_bd); + } else { + result = stlite.init(&flash_bd); + } + elapsed = timer.read_ms(); + printf("Elapsed time for %s is %d ms\n", st_type ? "mount" : "init", elapsed); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + if (st_type) { + continue; + } + + timer.reset(); + result = stlite.factory_reset(); + elapsed = timer.read_ms(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + printf("Elapsed time for factory reset is %d ms\n", elapsed); + + for (file_ind = 0; file_ind < num_files; file_ind++) { + file_name[0] = '0' + file_ind; + set_buf[0] = file_ind * ((file_ind % set_iters) + 1); + result = stlite.get(file_name, get_buf, data_size, actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(data_size, actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(set_buf, get_buf, data_size)); + } + + result = stlite.deinit(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + + delete[] file_name; + delete[] get_buf; + delete[] set_buf; +} + +static void error_inject_test() +{ + char *file_name; + uint8_t *get_buf, *set_buf, *exists; + size_t file_name_size = 32; + size_t data_size = 128; + size_t num_files = 63; + size_t actual_data_size; + int result; + uint32_t flags; + uint8_t set_iters = 3; + uint8_t i, file_ind; + + // Don't use a non volatile BD here (won't work in this test) + HeapBlockDevice bd(4096 * 32, 1, 1, 4096); + FlashSimBlockDevice flash_bd(&bd); + + StorageLite stlite; + + result = stlite.init(&flash_bd, 128); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.reset(); + TEST_ASSERT_EQUAL(0, result); + + file_name = new char[file_name_size + 1]; + get_buf = new uint8_t[data_size]; + set_buf = new uint8_t[data_size]; + exists = new uint8_t[num_files]; + + memset(exists, 0, num_files); + + for (i = 0; i < set_iters; i++) { + for (file_ind = 0; file_ind < num_files; file_ind++) { + memset(file_name, '0' + file_ind, file_name_size); + file_name[file_name_size + 1] = '\0'; + memset(set_buf, file_ind * i, data_size); + flags = ((rand() % 2) & StorageLite::encrypt_flag) | + ((rand() % 2) & StorageLite::update_backup_flag) | + ((rand() % 2) & StorageLite::rollback_protect_flag); + if ((file_ind != (num_files - 1)) && exists[file_ind] && !(rand() % 3)) { + result = stlite.remove(file_name, file_name_size); + exists[file_ind] = 0; + } else { + result = stlite.set(file_name, file_name_size, set_buf, data_size, flags); + exists[file_ind] = 1; + } + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + } + + for (uint8_t get_iter = 0; get_iter < 2; get_iter++) { + for (file_ind = 0; file_ind < num_files; file_ind++) { + memset(file_name, '0' + file_ind, file_name_size); + file_name[file_name_size + 1] = '\0'; + if (file_ind == (num_files - 1)) { + // last file will hold the previous version at the second iteration (after being crippled) + memset(set_buf, file_ind * (set_iters - get_iter - 1), data_size); + } else { + memset(set_buf, file_ind * (set_iters - 1), data_size); + } + result = stlite.get(file_name, file_name_size, get_buf, data_size, actual_data_size); + if (exists[file_ind]) { + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + TEST_ASSERT_EQUAL(data_size, actual_data_size); + TEST_ASSERT_EQUAL(0, memcmp(set_buf, get_buf, data_size)); + } else { + TEST_ASSERT_EQUAL(STORAGELITE_NOT_FOUND, result); + } + } + + if (get_iter) { + break; + } + + // Cripple the last file + size_t offset = stlite.size() - stlite.free_size() - 16; + uint8_t x; + bd.read(&x, offset, 1); + x++; + bd.program(&x, offset, 1); + + result = stlite.get(file_name, file_name_size, get_buf, data_size, actual_data_size); + TEST_ASSERT_EQUAL(STORAGELITE_DATA_CORRUPT, result); + + result = stlite.deinit(); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + + result = stlite.init(&flash_bd); + TEST_ASSERT_EQUAL(STORAGELITE_SUCCESS, result); + } + + delete[] file_name; + delete[] get_buf; + delete[] set_buf; +} + + + +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("StorageLite: White box test", white_box_test, greentea_failure_handler), + Case("StorageLite: White box FS test", white_box_fs_test, greentea_failure_handler), + Case("StorageLite: Multiple set test", multi_set_test, greentea_failure_handler), + Case("StorageLite: 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); +} diff --git a/features/filesystem/storagelite/mbed_lib.json b/features/filesystem/storagelite/mbed_lib.json new file mode 100644 index 00000000000..9b9306a9864 --- /dev/null +++ b/features/filesystem/storagelite/mbed_lib.json @@ -0,0 +1,11 @@ +{ + "name": "storagelite", + "macros": ["MBEDTLS_CIPHER_MODE_CTR", "MBEDTLS_CMAC_C"], + "config": { + "max_files": { + "macro_name": "STORAGELITE_MAX_FILES", + "value": 64, + "help": "Maximal number of files" + } + } +} diff --git a/features/filesystem/storagelite/source/StorageLite.cpp b/features/filesystem/storagelite/source/StorageLite.cpp new file mode 100644 index 00000000000..a135a936f57 --- /dev/null +++ b/features/filesystem/storagelite/source/StorageLite.cpp @@ -0,0 +1,1866 @@ +/* + * 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. + */ + +// ----------------------------------------------------------- Includes ----------------------------------------------------------- + +#include "StorageLite.h" + +#if STORAGELITE_ENABLED + +#include "aes.h" +#include "cmac.h" +#include "sha256.h" +#include "entropy.h" +#include "DeviceKey.h" +#include "BufferedBlockDevice.h" +#include "nvstore.h" +#include "mbed_critical.h" +#include "mbed_assert.h" +#include "mbed_wait_api.h" +#include +#include +#include + + +// --------------------------------------------------------- Definitions ---------------------------------------------------------- + +// Record flags are part of the data_size_and_flags field (both in RAM table and record header) +// External flags - flags given by the user in set operation +// Internal flags - flags added by us (like deleted flag) +static const uint32_t all_rec_flags_mask = 0xFFF00000UL; +static const uint32_t ext_rec_flags_mask = 0xFF000000UL; +static const uint32_t rec_data_size_mask = 0x000FFFFFUL; +// External flags +const uint32_t StorageLite::rollback_protect_flag = 0x80000000UL; +const uint32_t StorageLite::encrypt_flag = 0x40000000UL; +const uint32_t StorageLite::update_backup_flag = 0x20000000UL; +// Internal flags +static const uint32_t delete_flag = 0x00100000UL; + +// RAM flags are part of the RAM table entry. We use the low bits of the offset for flags, +// as offset needs to be aligned to 16 bytes (leaving us room for 4 flags) +static const uint32_t ram_delete_flag = 0x00000001UL; +static const uint32_t ram_is_backup_flag = 0x00000002UL; +static const uint32_t ram_has_backup_flag = 0x00000004UL; +static const uint32_t ram_rb_protect_flag = 0x00000008UL; +static const uint32_t ram_flags_mask = 0x0000000FUL; +static const uint32_t ram_offset_mask = 0xFFFFFFF0UL; + +static const size_t nonce_size = 8; +static const size_t cmac_size = 16; +static const size_t full_sha256_size = 32; + +typedef struct { + uint32_t data_size_and_flags; + uint16_t name_size; + uint16_t nvstore_key; + uint8_t nonce[nonce_size]; + uint8_t cmac[cmac_size]; +} record_header_t; + +typedef struct { + uint32_t hash; + uint32_t offset_and_flags; +} ram_table_entry_t; + +static const uint32_t min_prog_align_size = 16; + +static const unsigned char *enc_key_salt = (const unsigned char *) "StorageLite Enc "; +static const unsigned char *auth_key_salt = (const unsigned char *) "StorageLite Auth"; + +static const uint32_t enc_block_size = 16; + +static const uint16_t master_rec_format_rev = 0; +static const uint8_t *master_rec_file_name = (const uint8_t *) "StorageLite"; + +typedef struct { + uint16_t version; + uint16_t format_rev; + uint32_t reserved1; + uint64_t reserved2; +} master_record_data_t; + +// Internal status codes (not to be exposed to the user) +typedef enum { + STORAGELITE_ERASED = -64, +} storagelite_internal_status_e; + +typedef enum { + STORAGELITE_AREA_STATE_NONE = 0, + STORAGELITE_AREA_STATE_EMPTY, + STORAGELITE_AREA_STATE_VALID, +} area_state_e; + +static const uint32_t work_buf_size = 64 /* sizeof(record_header_t) */; + +// An invalid NVStore key, marking an allocation of a new one for rollback protection +static const uint16_t invalid_nvstore_key = 0xFFFF; + +// -------------------------------------------------- Local Functions Declaration ---------------------------------------------------- + +// -------------------------------------------------- Functions Implementation ---------------------------------------------------- + +static inline uint32_t align_up(uint32_t val, uint32_t size) +{ + return (((val - 1) / size) + 1) * size; +} + +static inline uint32_t record_size(uint32_t name_size, uint32_t data_size) +{ + // Name should be padded to encryption block size (so data starts from such a block) + return sizeof(record_header_t) + align_up(name_size, enc_block_size) + data_size; +} + +// CMAC & SHA256 Calculation functions - start/update and finish + +int calc_cmac(mbedtls_cipher_context_t *ctx, bool& start, const unsigned char *input, size_t ilen, + uint8_t *auth_key) +{ + int ret; + + if (start) { + const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); + + mbedtls_cipher_init(ctx); + + if ((ret = mbedtls_cipher_setup(ctx, cipher_info)) != 0 ) { + goto exit; + } + + ret = mbedtls_cipher_cmac_starts(ctx, auth_key, cmac_size * 8); + if (ret != 0) { + goto exit; + } + start = 0; + } + + ret = mbedtls_cipher_cmac_update(ctx, input, ilen); + if (ret != 0) { + goto exit; + } + + return 0; + +exit: + mbedtls_cipher_free(ctx); + + return ret; +} + +int finish_cmac(mbedtls_cipher_context_t *ctx, unsigned char *output, bool& finished) +{ + int ret; + + ret = mbedtls_cipher_cmac_finish(ctx, output); + + mbedtls_cipher_free(ctx); + + finished = true; + + return ret; +} + +int calc_sha256(mbedtls_sha256_context *ctx, bool& start, const unsigned char *input, size_t ilen) +{ + int ret; + + if (start) { + mbedtls_sha256_init(ctx); + + ret = mbedtls_sha256_starts_ret(ctx, 0); + if (ret != 0) { + goto exit; + } + start = false; + } + + ret = mbedtls_sha256_update_ret(ctx, input, ilen); + if (ret != 0) { + goto exit; + } + + return 0; + +exit: + mbedtls_sha256_free(ctx); + + return ret; +} + +int finish_sha256(mbedtls_sha256_context *ctx, unsigned char *output, bool& finished) +{ + int ret; + + ret = mbedtls_sha256_finish_ret(ctx, output); + + mbedtls_sha256_free(ctx); + + finished = true; + + return ret; +} + +int calc_hash(const unsigned char *input, size_t ilen, uint32_t& hash) +{ + bool hash_calc_start = true, hash_calc_finished = false; + mbedtls_sha256_context hash_ctx; + uint8_t full_sha[full_sha256_size]; + int ret; + + ret = calc_sha256(&hash_ctx, hash_calc_start, input, ilen); + if (ret) { + return ret; + } + + ret = finish_sha256(&hash_ctx, full_sha, hash_calc_finished); + if (ret) { + return ret; + } + memcpy(&hash, full_sha, sizeof(hash)); + + return 0; +} + +// Encrypt/decrypt a chunk of data (in place) +// (it is ensured that buffer has room for aligned data) +int encrypt_decrypt_chunk(mbedtls_aes_context& enc_aes_ctx, bool& start, uint8_t *buf, + uint32_t chunk_size, int encrypt, const uint8_t *encrypt_key, + uint8_t *nonce, uint8_t *iv) +{ + size_t aes_offs = 0; + uint32_t aligned_chunk_size = align_up(chunk_size, enc_block_size); + uint8_t stream_block[enc_block_size]; + + if (start) { + mbedtls_aes_init(&enc_aes_ctx); + mbedtls_aes_setkey_enc(&enc_aes_ctx, encrypt_key, enc_block_size * 8); + + memcpy(iv, nonce, nonce_size); + memset(iv + nonce_size, 0, nonce_size); + start = false; + } + + if (encrypt && (chunk_size < aligned_chunk_size)) { + memset(buf + chunk_size, 0, aligned_chunk_size - chunk_size); + } + return mbedtls_aes_crypt_ctr(&enc_aes_ctx, aligned_chunk_size, &aes_offs, iv, + stream_block, buf, buf); +} + + + +// Class member functions + +StorageLite::StorageLite() : _bd(0), _user_bd(0), _buff_bd(0), _init_done(false), _init_attempts(0), + _active_area(0), _max_files(0), _active_area_version(0), _free_space_offset(0), _size(0), + _mutex(0), _entropy(0), _num_ram_table_entries(0), _ram_table(0), _prog_align_size(0), + _work_buf(0), _encrypt_key(0), _auth_key(0), _variant_bd_erase_unit_size(false) +{ +} + +StorageLite::~StorageLite() +{ + deinit(); + delete _mutex; +} + +size_t StorageLite::get_max_files() const +{ + return _max_files; +} + +int StorageLite::read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf) +{ + int ret = _bd->read(buf, _area_params[area].address + offset, size); + + if (ret) { + return STORAGELITE_READ_ERROR; + } + + return STORAGELITE_SUCCESS; +} + +int StorageLite::write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf) +{ + int ret = _bd->program(buf, _area_params[area].address + offset, size); + if (ret) { + return STORAGELITE_WRITE_ERROR; + } + + return STORAGELITE_SUCCESS; +} + +int StorageLite::erase_area(uint8_t area) +{ + int ret = _bd->erase(_area_params[area].address, _area_params[area].size); + if (ret) { + return STORAGELITE_WRITE_ERROR; + } + return STORAGELITE_SUCCESS; +} + +int StorageLite::erase_erase_unit(uint8_t area, uint32_t offset) +{ + uint32_t bd_offset = _area_params[area].address + offset; + uint32_t eu_size = _bd->get_erase_size(bd_offset); + + int ret = _bd->erase(bd_offset, eu_size); + if (ret) { + return STORAGELITE_WRITE_ERROR; + } + return STORAGELITE_SUCCESS; +} + +void StorageLite::calc_area_params() +{ + size_t bd_size = _bd->size(); + + memset(_area_params, 0, sizeof(_area_params)); + size_t area_0_size = 0; + bd_size_t prev_erase_unit_size = _bd->get_erase_size(area_0_size); + _variant_bd_erase_unit_size = 0; + + while (area_0_size < bd_size / 2) { + bd_size_t erase_unit_size = _bd->get_erase_size(area_0_size); + _variant_bd_erase_unit_size |= (erase_unit_size != prev_erase_unit_size); + area_0_size += erase_unit_size; + } + + _area_params[0].address = 0; + _area_params[0].size = area_0_size; + _area_params[1].address = area_0_size; + _area_params[1].size = bd_size - area_0_size; +} + + +// This function, reading a record from the BD, is used for multiple purposes: +// - Init (scan all records, no need to return file name and data) +// - Get (return file data) +// - Get first/next file (check whether name matches, return name if so) +// Problem is that there's not enough memory for either file name or data. So we only work +// on a small work buffer, on which we should perform all authentication, decryption and +// hash calculations in a "rolling" manner. Hence the complexity of the function. +int StorageLite::read_record(uint8_t area, uint32_t offset, void *name_buf, uint16_t name_buf_size, + uint16_t& actual_name_size, void *data_buf, uint32_t data_buf_size, + uint32_t& actual_data_size, bool copy_name, bool allow_partial_name, + bool check_expected_name, bool copy_data, bool calc_hash, uint32_t& hash, + uint32_t& flags, uint16_t& nvstore_key, uint32_t& next_offset) +{ + int os_ret, ret; + record_header_t header; + bool cmac_calc_start = true, cmac_calc_finished = false; + bool hash_calc_start = true, hash_calc_finished = !calc_hash; + bool enc_start = true; + mbedtls_cipher_context_t auth_ctx; + mbedtls_sha256_context hash_ctx; + mbedtls_aes_context enc_aes_ctx; + uint32_t total_size, name_size, data_size; + uint8_t *user_name_ptr, *user_data_ptr; + uint8_t *bd_name_ptr, *bd_data_ptr; + uint8_t nonce[nonce_size]; + uint8_t iv[enc_block_size]; + + ret = STORAGELITE_SUCCESS; + + os_ret = read_area(area, offset, sizeof(header), &header); + if (os_ret) { + return STORAGELITE_READ_ERROR; + } + + // Time saver - if header part seems erased, notify caller and exit (no point parsing) + memset(_work_buf, _bd->get_erase_value(), sizeof(header)); + if (!memcmp(_work_buf, &header, sizeof(header))) { + return STORAGELITE_ERASED; + } + + offset += sizeof(header); + + name_size = header.name_size; + data_size = header.data_size_and_flags & rec_data_size_mask; + flags = header.data_size_and_flags & all_rec_flags_mask; + nvstore_key = header.nvstore_key; + memcpy(nonce, header.nonce, nonce_size); + + if ((!name_size) || (name_size >= max_name_size) || (data_size >= max_data_size)) { + return STORAGELITE_DATA_CORRUPT; + } + + uint32_t padded_name_size = align_up(name_size, enc_block_size); + total_size = data_size + padded_name_size; + + if (offset + total_size >= _size) { + return STORAGELITE_DATA_CORRUPT; + } + + actual_data_size = data_size; + actual_name_size = name_size; + + if (copy_data && (actual_data_size > data_buf_size)) { + // Although actual data size is not authenticated yet, we can't risk clobbering the user + // buffer, therefore turn copy data flag off right now, but keep working as we may + // finally have a data corrupt error. + copy_data = false; + ret = STORAGELITE_BUFF_TOO_SMALL; + } + + if (copy_name && !allow_partial_name && (actual_name_size > name_buf_size)) { + // Same here + copy_name = false; + ret = STORAGELITE_BUFF_TOO_SMALL; + } + + // Calculate CMAC on header (excluding CMAC itself) + os_ret = calc_cmac(&auth_ctx, cmac_calc_start, (const uint8_t *)&header, + sizeof(record_header_t) - cmac_size, _auth_key); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + + if (check_expected_name) { + if (name_buf_size != name_size) { + // No point checking for name if size already doesn't match. Keep working in order + // to find more serious errors. + ret = STORAGELITE_NOT_FOUND; + } + } + + bd_name_ptr = _work_buf; + user_name_ptr = static_cast (name_buf); + bd_data_ptr = _work_buf; + user_data_ptr = static_cast (data_buf); + + while (total_size) { + uint32_t chunk_size = std::min(total_size, work_buf_size); + os_ret = read_area(area, offset, chunk_size, _work_buf); + if (os_ret) { + ret = STORAGELITE_READ_ERROR; + goto end; + } + + // CMAC calculation on current read chunk + os_ret = calc_cmac(&auth_ctx, cmac_calc_start, _work_buf, chunk_size, _auth_key); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + + total_size -= chunk_size; + offset += chunk_size; + + // This means we still work on the name part in the record + if (name_size) { + uint32_t name_chunk_size = std::min(name_size, chunk_size); + uint32_t padded_name_chunk_size = align_up(name_chunk_size, enc_block_size); + if (check_expected_name) { + if (memcmp(user_name_ptr, bd_name_ptr, name_chunk_size)) { + ret = STORAGELITE_NOT_FOUND; + } + } else if (copy_name) { + uint32_t name_copy_size = std::min((uint32_t)name_buf_size, name_chunk_size); + memcpy(user_name_ptr, bd_name_ptr, name_copy_size); + name_buf_size -= name_copy_size; + // If we're out of buffer, don't copy the name in the next iteration + copy_name = !!name_copy_size; + } + + if (calc_hash) { + calc_sha256(&hash_ctx, hash_calc_start, bd_name_ptr, name_chunk_size); + if (name_size == name_chunk_size) { + uint8_t full_sha[full_sha256_size]; + finish_sha256(&hash_ctx, full_sha, hash_calc_finished); + memcpy(&hash, full_sha, sizeof(hash)); + } + } + + user_name_ptr += name_chunk_size; + name_size -= name_chunk_size; + + // Check whether we moved from the name part to the data part + if (!name_size) { + bd_data_ptr = bd_name_ptr + padded_name_chunk_size; + chunk_size -= padded_name_chunk_size; + } + } + + // This means we now work on the data part in the record + if (!name_size && data_size && copy_data) { + uint32_t data_chunk_size = std::min(data_size, chunk_size); + if (flags & StorageLite::encrypt_flag) { + os_ret = encrypt_decrypt_chunk(enc_aes_ctx, enc_start, bd_data_ptr, data_chunk_size, + false, _encrypt_key, nonce, iv); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + } + // Copy data to user buffer + memcpy(user_data_ptr, bd_data_ptr, data_chunk_size); + + user_data_ptr += data_chunk_size; + data_size -= data_chunk_size; + bd_data_ptr = _work_buf; + } + } + + // Now authenticate the record (recycle _work_buf for calculated CMAC) + os_ret = finish_cmac(&auth_ctx, _work_buf, cmac_calc_finished); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + if (memcmp(header.cmac, _work_buf, cmac_size)) { + ret = STORAGELITE_DATA_CORRUPT; + goto end; + } + + next_offset = align_up(offset, _prog_align_size); + + // In case of rollback protection, check that the same CMAC is stored in NVStore + if (flags & StorageLite::rollback_protect_flag) { + NVStore& nvstore = NVStore::get_instance(); + uint16_t actual_cmac_size; + os_ret = nvstore.get(nvstore_key, cmac_size, _work_buf, actual_cmac_size); + if (os_ret) { + ret = STORAGELITE_RB_PROTECT_ERROR; + goto end; + } + if (memcmp(header.cmac, _work_buf, cmac_size)) { + ret = STORAGELITE_RB_PROTECT_ERROR; + goto end; + } + } + +end: + if (!cmac_calc_finished) { + mbedtls_cipher_free(&auth_ctx); + } + if (!hash_calc_finished) { + mbedtls_sha256_free(&hash_ctx); + } + return ret; +} + +int StorageLite::write_record(uint8_t area, uint32_t offset, const void *name_buf, + uint16_t name_buf_size, const void *data_buf, uint32_t data_buf_size, + uint32_t flags, uint16_t nvstore_key, uint32_t& next_offset) +{ + record_header_t header; + int os_ret, ret; + NVStore& nvstore = NVStore::get_instance(); + uint32_t orig_offset = offset; + const uint8_t *user_name_ptr, *user_data_ptr; + uint8_t *bd_name_ptr, *bd_data_ptr; + uint32_t name_size = name_buf_size; + uint32_t data_size = data_buf_size; + mbedtls_cipher_context_t auth_ctx; + mbedtls_aes_context enc_aes_ctx; + bool cmac_calc_start = true, cmac_calc_finished = false; + bool enc_start = true; + uint8_t iv[enc_block_size]; + + ret = STORAGELITE_SUCCESS; + + uint32_t total_size = record_size(name_size, data_size); + // Before writing the record, check that it doesn't cross unerased erase units + ret = check_erase_before_write(area, offset, total_size); + if (ret) { + return ret; + } + + header.data_size_and_flags = flags | data_size; + header.name_size = name_size; + // Allocate a new NVStore key in case of rollback protection, and if no such key was given before. + // Otherwise use the one given as an argument. + if ((flags & StorageLite::rollback_protect_flag) && (nvstore_key == invalid_nvstore_key)) { + os_ret = nvstore.allocate_key(nvstore_key, NVSTORE_STORAGELITE_OWNER); + if (os_ret) { + return STORAGELITE_OS_ERROR; + } + } + header.nvstore_key = nvstore_key; + + // generate a new random nonce + os_ret = mbedtls_entropy_func(_entropy, header.nonce, nonce_size); + if (os_ret) { + return STORAGELITE_OS_ERROR; + } + + os_ret = calc_cmac(&auth_ctx, cmac_calc_start, (const uint8_t *) &header, + sizeof(record_header_t) - cmac_size, _auth_key); + if (os_ret) { + return STORAGELITE_OS_ERROR; + } + + // Data needs to be encrypted first (if applicable), then signed with CMAC. + // This means we have to write the header LAST. So write all blocks first, then go on and write + // the header. + + bd_name_ptr = _work_buf; + bd_data_ptr = _work_buf; + user_name_ptr = static_cast (name_buf); + user_data_ptr = static_cast (data_buf); + offset += sizeof(record_header_t); + total_size -= sizeof(record_header_t); + + while (total_size) { + uint32_t chunk_size = std::min(total_size, work_buf_size); + uint32_t padded_name_chunk_size = 0; + + // This means we still work on the name part in the record + if (name_size) { + uint32_t name_chunk_size = std::min((uint32_t)name_size, chunk_size); + padded_name_chunk_size = align_up(name_chunk_size, enc_block_size); + memcpy(bd_name_ptr, user_name_ptr, name_chunk_size); + bd_name_ptr += name_chunk_size; + if (padded_name_chunk_size > name_chunk_size) { + uint32_t pad_size = padded_name_chunk_size - name_chunk_size; + memset(bd_name_ptr, 0, pad_size); + bd_name_ptr += pad_size; + } + + user_name_ptr += name_chunk_size; + name_size -= name_chunk_size; + // Check whether we moved from the name part to the data part + if (!name_size) { + bd_data_ptr = bd_name_ptr; + } + } + + // This means we now work on the data part in the record + if (!name_size && data_size) { + uint32_t data_chunk_size = std::min(data_size, chunk_size - padded_name_chunk_size); + // Copy data to bd buffer + memcpy(bd_data_ptr, user_data_ptr, data_chunk_size); + if (flags & StorageLite::encrypt_flag) { + os_ret = encrypt_decrypt_chunk(enc_aes_ctx, enc_start, bd_data_ptr, data_chunk_size, + true, _encrypt_key, header.nonce, iv); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + } + user_data_ptr += data_chunk_size; + data_size -= data_chunk_size; + } + + os_ret = calc_cmac(&auth_ctx, cmac_calc_start, _work_buf, chunk_size, _auth_key); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + + uint32_t aligned_chunk_size = align_up(chunk_size, _prog_align_size); + if (aligned_chunk_size > chunk_size) { + // Pad with non-blank values, so blank checking at init doesn't get confused + memset(_work_buf + chunk_size, 0x5A, aligned_chunk_size - chunk_size); + } + + // Program data + os_ret = write_area(area, offset, aligned_chunk_size, _work_buf); + if (os_ret) { + ret = STORAGELITE_WRITE_ERROR; + goto end; + } + + total_size -= chunk_size; + offset += chunk_size; + bd_name_ptr = _work_buf; + bd_data_ptr = _work_buf; + } + + // Finalize CMAC calculation + os_ret = finish_cmac(&auth_ctx, header.cmac, cmac_calc_finished); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + + // Now program the header + os_ret = write_area(area, orig_offset, sizeof(record_header_t), &header); + if (os_ret) { + return STORAGELITE_WRITE_ERROR; + } + + // All's written, need to flush underlying BD + os_ret = _bd->sync(); + if (os_ret) { + return STORAGELITE_WRITE_ERROR; + } + + // In case of rollback protection, store CMAC in NVStore + if (flags & StorageLite::rollback_protect_flag) { + os_ret = nvstore.set(header.nvstore_key, cmac_size, header.cmac); + if (os_ret) { + ret = STORAGELITE_OS_ERROR; + goto end; + } + } + + next_offset = align_up(offset, _prog_align_size); + +end: + if (!cmac_calc_finished) { + mbedtls_cipher_free(&auth_ctx); + } + return ret; +} + +int StorageLite::write_master_record(uint8_t area, uint16_t version, uint32_t& next_offset) +{ + master_record_data_t master_rec; + + master_rec.version = version; + master_rec.format_rev = master_rec_format_rev; + master_rec.reserved1 = 0; + master_rec.reserved2 = 0; + return write_record(area, 0, static_cast (master_rec_file_name), + sizeof(master_rec_file_name), &master_rec, sizeof(master_rec), 0, 0, + next_offset); +} + +int StorageLite::copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t& to_next_offset) +{ + int os_ret, ret; + uint32_t from_next_offset, hash, flags, actual_data_size; + uint32_t total_size; + uint16_t actual_name_size, chunk_size, nvstore_key; + + // We need to validate the record before we copy it (as it may have been altered) + ret = read_record(from_area, from_offset, 0, 0, actual_name_size, 0, 0, actual_data_size, + false, false, false, false, false, hash, flags, nvstore_key, + from_next_offset); + // Currently ignore rollback protect errors (only return them on get) + if ((ret != STORAGELITE_SUCCESS) && (ret != STORAGELITE_RB_PROTECT_ERROR)) { + return ret; + } + total_size = from_next_offset - from_offset; + + ret = check_erase_before_write(1 - from_area, to_offset, total_size); + if (ret) { + return ret; + } + + while (total_size) { + chunk_size = std::min(total_size, work_buf_size); + os_ret = read_area(from_area, from_offset, chunk_size, _work_buf); + if (os_ret) { + return STORAGELITE_READ_ERROR; + } + + os_ret = write_area(1 - from_area, to_offset, chunk_size, _work_buf); + if (os_ret) { + return STORAGELITE_WRITE_ERROR; + } + + from_offset += chunk_size; + to_offset += chunk_size; + total_size -= chunk_size; + } + + to_next_offset = align_up(to_offset, _prog_align_size); + return STORAGELITE_SUCCESS; +} + +int StorageLite::copy_all_records(bool is_backup, uint8_t from_area, uint32_t to_offset, + uint32_t& to_next_offset) +{ + int ret; + int ind; + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + + // Initialize in case table is empty + to_next_offset = to_offset; + + // Go over ram table and copy all entries to opposite area + for (ind = 0; ind < _num_ram_table_entries; ind++) { + uint32_t from_offset = ram_table[ind].offset_and_flags & ram_offset_mask; + uint32_t ram_flags = ram_table[ind].offset_and_flags & ram_flags_mask; + if (!from_offset) { + continue; + } + if ((is_backup && !(ram_flags & ram_is_backup_flag)) || + (!is_backup && (ram_flags & ram_is_backup_flag))) { + continue; + } + ret = copy_record(from_area, from_offset, to_offset, to_next_offset); + if (ret != STORAGELITE_SUCCESS) { + return ret; + } + to_offset = to_next_offset; + } + return STORAGELITE_SUCCESS; +} + +int StorageLite::garbage_collection(bool factory_reset_mode) +{ + uint32_t to_offset, to_next_offset; + int ret; + + to_offset = align_up(record_size(sizeof(master_rec_file_name), sizeof(master_record_data_t)), + _prog_align_size); + + // Garbage collection process has the following steps: + // 1. Build RAM table (backup only records) + // 2. Copy all records (with is_backup flag set) + // 3. Build RAM table (all records) + // 4. Copy all records (with is_backup flag not set) + // 5. Switch active area + // 6. Build RAM table (all records) + // Factory reset does the same, excluding steps 3 & 4 + + ret = build_ram_table(true); + if (ret) { + return ret; + } + + ret = copy_all_records(true, _active_area, to_offset, to_next_offset); + if (ret) { + return ret; + } + to_offset = to_next_offset; + + if (!factory_reset_mode) { + ret = build_ram_table(false); + if (ret) { + return ret; + } + + ret = copy_all_records(false, _active_area, to_offset, to_next_offset); + if (ret) { + return ret; + } + to_offset = to_next_offset; + } + _free_space_offset = to_next_offset; + + // Now we can switch to the new active area + _active_area = 1 - _active_area; + + // Now write master record, with version incremented by 1. + _active_area_version++; + ret = write_master_record(_active_area, _active_area_version, to_offset); + if (ret) { + return ret; + } + + // Build ram table with the new area + ret = build_ram_table(false); + if (ret) { + return ret; + } + + // Erase first erase unit (with master record) of standby area + if (erase_erase_unit(1 - _active_area, 0)) { + return STORAGELITE_WRITE_ERROR; + } + + return ret; +} + +int StorageLite::find_and_set(uint8_t area, uint32_t offset, const void *file_name, + uint16_t file_name_size, const void *data_buf, uint32_t data_buf_size, + uint32_t flags, uint32_t& next_offset) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + int os_ret, ret = STORAGELITE_SUCCESS; + uint32_t hash, ret_hash, prev_flags; + uint32_t ram_flags; + uint32_t actual_data_size; + uint16_t ind, first_free; + uint16_t actual_name_size; + uint16_t prev_nvstore_key, new_nvstore_key; + bool free_nvstore_key = false; + const uint8_t *fname = static_cast (file_name); + + os_ret = calc_hash(const_cast (fname), file_name_size, hash); + if (os_ret) { + return STORAGELITE_OS_ERROR; + } + + ret = STORAGELITE_NOT_FOUND; + first_free = (uint16_t) -1; + for (ind = 0; ind < _num_ram_table_entries; ind++) { + entry = &ram_table[ind]; + uint32_t curr_offset = entry->offset_and_flags & ram_offset_mask; + if (!curr_offset) { + // recycle first hole (deleted entry) in ram table + if (first_free == (uint16_t) -1) { + first_free = ind; + } + continue; + } + if (hash != entry->hash) { + continue; + } + ret = read_record(_active_area, curr_offset, const_cast (fname), + file_name_size, actual_name_size, 0, 0, actual_data_size, + false, false, true, false, false, ret_hash, prev_flags, prev_nvstore_key, + next_offset); + // not found return code here means that hash doesn't belong to name. Continue searching. + if (ret != STORAGELITE_NOT_FOUND) { + break; + } + } + + // Called remove API without finding the record - fail the operation + if ((flags & delete_flag) && (ret == STORAGELITE_NOT_FOUND)) { + return ret; + } + + // Fail if got a severe error + if ((ret != STORAGELITE_SUCCESS) && (ret != STORAGELITE_RB_PROTECT_ERROR) && + (ret != STORAGELITE_NOT_FOUND)) { + return ret; + } + + // Didn't find the a matching file name, create a new entry + if (ind == _num_ram_table_entries) { + if (first_free == (uint16_t) -1) { + // No holes found, need to enlarge ram table + if (_num_ram_table_entries == _max_files) { + return STORAGELITE_MAX_FILES_REACHED; + } + _num_ram_table_entries++; + } else { + // found a hole, use it + ind = first_free; + } + ram_table[ind].offset_and_flags = 0; + prev_flags = 0; + // This will tell write_record to allocate a new nvstore key in case rollback protection + // is required. + new_nvstore_key = invalid_nvstore_key; + } else { + new_nvstore_key = prev_nvstore_key; + // File can't change its rollback protection state (this is a security breach). + // Align flag of current operation to the previous state. + flags &= ~rollback_protect_flag; + if (ram_table[ind].offset_and_flags & ram_rb_protect_flag) { + flags |= rollback_protect_flag; + } + } + + // Need to free previous NVStore key in case we delete a file that has rollback protection. + // Keep in mind that we only want to do it if the record has no factory backup + // (we don't want to delete keys for records that can be restored by factory reset). + if ((prev_flags & rollback_protect_flag) && + !(ram_table[ind].offset_and_flags & ram_has_backup_flag)) { + free_nvstore_key = true; + new_nvstore_key = invalid_nvstore_key; + } + + // Write record + ret = write_record(area, offset, file_name, file_name_size, data_buf, data_buf_size, flags, + new_nvstore_key, next_offset); + if (ret) { + return ret; + } + + // Translate record flags to RAM flags + ram_flags_and_offset_by_rec_flags(flags, ind, true, ram_flags, offset); + + // Update RAM table + ram_table[ind].hash = hash; + ram_table[ind].offset_and_flags = offset | ram_flags; + + if (free_nvstore_key) { + // Free NVStore key + NVStore& nvstore = NVStore::get_instance(); + os_ret = nvstore.remove(prev_nvstore_key); + if ((os_ret != NVSTORE_SUCCESS) && (os_ret != NVSTORE_NOT_FOUND)) { + return STORAGELITE_OS_ERROR; + } + } + + return STORAGELITE_SUCCESS; +} + + +int StorageLite::do_get(const void *file_name, uint16_t file_name_size, bool copy_data, + void *data_buf, uint32_t data_buf_size, uint32_t& actual_data_size, + uint32_t& flags) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + const uint8_t *fname = static_cast (file_name); + int os_ret, ret = STORAGELITE_SUCCESS; + uint32_t next_offset; + uint32_t hash, ret_hash; + uint16_t ind; + uint16_t actual_name_size; + uint16_t nvstore_key; + + if (!_init_done) { + return STORAGELITE_UNINITIALIZED; + } + + if (!file_name_size || !file_name) { + return STORAGELITE_BAD_VALUE; + } + + if (data_buf_size && !data_buf) { + return STORAGELITE_BAD_VALUE; + } + + if ((file_name_size > max_name_size) || (data_buf_size > max_data_size)) { + return STORAGELITE_BAD_VALUE; + } + + os_ret = calc_hash(const_cast (fname), file_name_size, hash); + if (os_ret) { + return STORAGELITE_OS_ERROR; + } + + _mutex->lock(); + + ret = STORAGELITE_NOT_FOUND; + for (ind = 0; ind < _num_ram_table_entries; ind++) { + entry = &ram_table[ind]; + uint32_t curr_offset = entry->offset_and_flags & ram_offset_mask; + if (!curr_offset) { + continue; + } + if (hash != entry->hash) { + continue; + } + ret = read_record(_active_area, curr_offset, const_cast (fname), + file_name_size, actual_name_size, data_buf, data_buf_size, + actual_data_size, false, false, true, copy_data, false, ret_hash, + flags, nvstore_key, next_offset); + // not found return code here means that hash doesn't belong to name, so continue searching. + // In all other cases, we either found the name or got a severe error, so stop. + if (ret != STORAGELITE_NOT_FOUND) { + break; + } + } + + _mutex->unlock(); + + if (ret == STORAGELITE_ERASED) { + return STORAGELITE_DATA_CORRUPT; + } + + // Following can happen - a record that has backup, but working copy is deleted. + // In this case, we will find the record, but it will have the delete flag. Treat as not found. + if (flags & delete_flag) { + return STORAGELITE_NOT_FOUND; + } + return ret; +} + +int StorageLite::get(const void *file_name, size_t file_name_size, void *data_buf, + size_t data_buf_size, size_t& actual_data_size) +{ + uint32_t flags; + uint32_t actual_data_size_32; + int ret; + ret = do_get(file_name, file_name_size, true, data_buf, data_buf_size, actual_data_size_32, flags); + actual_data_size = (size_t) actual_data_size_32; + return ret; +} + +int StorageLite::get(const char *file_name, void *data_buf, + size_t data_buf_size, size_t& actual_data_size) +{ + return get(file_name, strlen(file_name), data_buf, data_buf_size, actual_data_size); +} + +int StorageLite::get_file_size(const void *file_name, size_t file_name_size, + size_t& actual_data_size) +{ + uint32_t flags; + uint32_t actual_data_size_32; + int ret; + ret = do_get(file_name, file_name_size, false, 0, 0, actual_data_size_32, flags); + actual_data_size = (size_t) actual_data_size_32; + return ret; +} + +int StorageLite::get_file_size(const char *file_name, size_t& actual_data_size) +{ + return get_file_size(file_name, strlen(file_name), actual_data_size); +} + +int StorageLite::get_file_flags(const void *file_name, size_t file_name_size, + uint32_t& flags) +{ + uint32_t actual_data_size; + int ret = do_get(file_name, file_name_size, false, 0, 0, actual_data_size, flags); + flags &= ext_rec_flags_mask; + return ret; +} + +int StorageLite::get_file_flags(const char *file_name, uint32_t& flags) +{ + return get_file_flags(file_name, strlen(file_name), flags); +} + +int StorageLite::file_exists(const void *file_name, size_t file_name_size) +{ + uint32_t flags, actual_data_size; + return do_get(file_name, file_name_size, false, 0, 0, actual_data_size, flags); +} + +int StorageLite::file_exists(const char *file_name) +{ + return file_exists(file_name, strlen(file_name)); +} + +int StorageLite::do_set(const void *file_name, uint16_t file_name_size, + const void *data_buf, uint32_t data_buf_size, uint32_t flags) +{ + int ret = STORAGELITE_SUCCESS; + uint32_t rec_size; + uint32_t next_offset; + + if (!_init_done) { + return STORAGELITE_UNINITIALIZED; + } + + if (!file_name) { + return STORAGELITE_BAD_VALUE; + } + + if (!file_name_size && !(flags & delete_flag)) { + return STORAGELITE_BAD_VALUE; + } + + if ((file_name_size > max_name_size) || (data_buf_size > max_data_size)) { + return STORAGELITE_BAD_VALUE; + } + + if (!data_buf && data_buf_size) { + return STORAGELITE_BAD_VALUE; + } + + if (!data_buf_size) { + data_buf = 0; + } + + rec_size = align_up(record_size(file_name_size, data_buf_size), _prog_align_size); + + _mutex->lock(); + + // If we cross the area limit, we need to invoke GC. + if (_free_space_offset + rec_size >= _size) { + ret = garbage_collection(false); + if (ret) { + goto end; + } + // If we still don't have room, return an error. + if (_free_space_offset + rec_size >= _size) { + ret = STORAGELITE_NO_SPACE_ON_BD; + goto end; + } + } + + // Now write the record + ret = find_and_set(_active_area, _free_space_offset, file_name, file_name_size, data_buf, + data_buf_size, flags, next_offset); + _free_space_offset = next_offset; + +end: + _mutex->unlock(); + + return ret; +} + +int StorageLite::set(const void *file_name, size_t file_name_size, const void *data_buf, + size_t data_buf_size, uint32_t flags) +{ + if (flags & ~ext_rec_flags_mask) { + return STORAGELITE_BAD_VALUE; + } + + return do_set(file_name, file_name_size, data_buf, data_buf_size, flags); +} + +int StorageLite::set(const char *file_name, const void *data_buf, + size_t data_buf_size, uint32_t flags) +{ + return set(file_name, strlen(file_name), data_buf, data_buf_size, flags); +} + +int StorageLite::remove(const void *file_name, size_t file_name_size) +{ + if (!file_name_size) { + return STORAGELITE_BAD_VALUE; + } + + return do_set(file_name, file_name_size, 0, 0, delete_flag); +} + +int StorageLite::remove(const char *file_name) +{ + return remove(file_name, strlen(file_name)); +} + + +int StorageLite::build_ram_table(bool backup_only) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + record_header_t *header; + uint32_t offset = 0, next_offset = 0; + int os_ret, ret = STORAGELITE_SUCCESS; + uint32_t hash; + uint32_t flags; + uint32_t ram_flags; + uint32_t actual_data_size; + uint8_t name[min_prog_align_size]; + uint16_t actual_name_size; + uint16_t ind; + uint16_t nvstore_key; + bool exists; + + _num_ram_table_entries = 0; + + while (offset < _free_space_offset) { + ret = read_record(_active_area, offset, name, sizeof(name), actual_name_size, + 0, 0, actual_data_size, true, true, false, false, true, hash, + flags, nvstore_key, next_offset); + // Currently ignore rollback protect errors + if ((ret != STORAGELITE_SUCCESS) && (ret != STORAGELITE_RB_PROTECT_ERROR)) { + _free_space_offset = offset; + return ret; + } + + uint32_t save_offset = offset; + offset = next_offset; + + // ignore master record at offset 0 + if (!save_offset) { + continue; + } + + // Ignore records not fitting required backup copy mode + if (backup_only && !(flags & update_backup_flag)) { + continue; + } + + // Now we need to iterate over all cached records and check if current file name already + // exists + exists = 0; + for (ind = 0; ind < _num_ram_table_entries; ind++) { + if (ram_table[ind].hash != hash) { + continue; + } + os_ret = read_area(_active_area, ram_table[ind].offset_and_flags & ram_offset_mask, + sizeof(record_header_t), _work_buf); + if (os_ret) { + return STORAGELITE_READ_ERROR; + } + header = (record_header_t *) _work_buf; + if (header->name_size != actual_name_size) { + continue; + } + + os_ret = read_area(_active_area, (ram_table[ind].offset_and_flags & ram_offset_mask) + + sizeof(record_header_t), work_buf_size, _work_buf); + if (os_ret) { + return STORAGELITE_READ_ERROR; + } + uint16_t cmp_size = std::min((uint16_t)min_prog_align_size, actual_name_size); + if (!memcmp(name, _work_buf, cmp_size)) { + exists = true; + break; + } + } + + // Translate record flags to RAM flags + ram_flags_and_offset_by_rec_flags(flags, ind, exists, ram_flags, save_offset); + + if (!exists) { + // Didn't find a matching name, add record to the end of ram table + if (flags & delete_flag) { + continue; + } + ind = _num_ram_table_entries; + if (_num_ram_table_entries + 1 == _max_files) { + return STORAGELITE_MAX_FILES_REACHED; + } + _num_ram_table_entries++; + } + + // offset 0 means that the file has no backup - entry can be deleted now + if (!save_offset) { + // record deleted - move all following entries back one slot + for (int j = ind; j < _num_ram_table_entries; j++) { + ram_table[j].offset_and_flags = ram_table[j + 1].offset_and_flags; + ram_table[j].hash = ram_table[j + 1].hash; + } + _num_ram_table_entries--; + } else { + // update record parameters + ram_table[ind].offset_and_flags = save_offset | ram_flags; + ram_table[ind].hash = hash; + } + } + + _free_space_offset = next_offset; + return ret; +} + +int StorageLite::free_rollback_protect_keys(bool non_backup_only) +{ + ram_table_entry_t *entry, *ram_table = (ram_table_entry_t *) _ram_table; + NVStore& nvstore = NVStore::get_instance(); + uint32_t next_offset = 0; + int ret, os_ret; + uint32_t hash; + uint32_t flags; + uint32_t actual_data_size; + uint16_t actual_name_size; + uint16_t ind; + uint16_t nvstore_key; + + ret = STORAGELITE_SUCCESS; + for (ind = 0; ind < _num_ram_table_entries; ind++) { + entry = &ram_table[ind]; + uint32_t curr_offset = entry->offset_and_flags & ram_offset_mask; + uint32_t ram_flags = entry->offset_and_flags & ram_flags_mask; + if (!curr_offset) { + continue; + } + if (non_backup_only && (ram_flags & ram_has_backup_flag)) { + continue; + } + ret = read_record(_active_area, curr_offset, 0, 0, actual_name_size, + 0, 0, actual_data_size, + false, false, false, false, false, hash, flags, nvstore_key, + next_offset); + // Ignore rollback protect errors + if ((ret != STORAGELITE_SUCCESS) && (ret != STORAGELITE_RB_PROTECT_ERROR)) { + return ret; + } + if (!(flags & rollback_protect_flag)) { + continue; + } + os_ret = nvstore.remove(nvstore_key); + if ((os_ret != NVSTORE_SUCCESS) && (os_ret != NVSTORE_NOT_FOUND)) { + return STORAGELITE_OS_ERROR; + } + } + return ret; +} + + +int StorageLite::init(BlockDevice *bd, size_t max_files) +{ + ram_table_entry_t *ram_table; + area_state_e area_state[STORAGELITE_NUM_AREAS]; + uint32_t init_attempts_val; + uint32_t next_offset; + uint32_t flags, hash; + uint32_t actual_data_size; + int os_ret; + int ret = STORAGELITE_SUCCESS; + uint16_t versions[STORAGELITE_NUM_AREAS]; + uint16_t actual_name_size; + uint16_t nvstore_key; + + if (_init_done) { + // Null BD signals the use of current one + if (!bd || (bd == _user_bd)) { + return STORAGELITE_SUCCESS; + } + // Reinitializing with a different BD - need to deinit first + deinit(); + } + MBED_ASSERT(bd); + + // This handles the case that init function is called by more than one thread concurrently. + // Only the one who gets the value of 1 in _init_attempts_val will proceed, while others will + // wait until init is finished. + init_attempts_val = core_util_atomic_incr_u32(&_init_attempts, 1); + if (init_attempts_val != 1) { + while (!_init_done) { + wait_ms(1); + } + return STORAGELITE_SUCCESS; + } + + _max_files = max_files; + ram_table = new ram_table_entry_t[_max_files]; + _ram_table = ram_table; + _num_ram_table_entries = 0; + + NVStore& nvstore = NVStore::get_instance(); + os_ret = nvstore.init(); + MBED_ASSERT(!os_ret); + + uint16_t curr_max_keys = nvstore.get_max_keys(); + + if ((size_t)(curr_max_keys - NVSTORE_NUM_PREDEFINED_KEYS) < max_files) { + // Need to have sufficient number of NVStore keys for rollback protection + // FIXME: Is this the correct way to do that? + nvstore.set_max_keys(max_files + NVSTORE_NUM_PREDEFINED_KEYS); + os_ret = nvstore.init(); + MBED_ASSERT(!os_ret); + } + + if (!_mutex) { + _mutex = new PlatformMutex; + } + + os_ret = bd->init(); + MBED_ASSERT(!os_ret); + + // Underlying BD size must fit into 32 bits + MBED_ASSERT((uint32_t)bd->size() == bd->size()); + + // Underlying BD must have flash attributes, i.e. have an erase value + MBED_ASSERT(bd->get_erase_value() != -1); + + _size = (size_t) -1; + _user_bd = bd; + + // If the BD's program (or read) size is larger than header size, then this would make our + // read_record and write_record code much more complex (as we'll have to read/write chunks + // of the data along with the header). If this is the case, use the buffered block device + // to reduce the underlying BD read and program sizes + if (!(sizeof(record_header_t) % bd->get_program_size())) { + // common (and easier) case, use the underlying BD directly + _bd = bd; + } else { + // uncommon case, use the buffered BD adaptor on top of underlying BD + bd->deinit(); + _buff_bd = new BufferedBlockDevice(bd); + _buff_bd->init(); + _bd = _buff_bd; + } + + _prog_align_size = align_up(_bd->get_program_size(), min_prog_align_size); + _work_buf = new uint8_t[work_buf_size]; + _encrypt_key = new uint8_t[enc_block_size]; + _auth_key = new uint8_t[enc_block_size]; + + mbed::DeviceKey& devkey = mbed::DeviceKey::get_instance(); + os_ret = devkey.generate_derived_key(enc_key_salt, sizeof(enc_key_salt), _encrypt_key, DEVICE_KEY_16BYTE); + MBED_ASSERT(os_ret == mbed::DEVICEKEY_SUCCESS); + os_ret = devkey.generate_derived_key(auth_key_salt, sizeof(auth_key_salt), _auth_key, DEVICE_KEY_16BYTE); + MBED_ASSERT(os_ret == mbed::DEVICEKEY_SUCCESS); + + _entropy = new mbedtls_entropy_context; + mbedtls_entropy_init((mbedtls_entropy_context *)_entropy); + + calc_area_params(); + + for (uint8_t area = 0; area < STORAGELITE_NUM_AREAS; area++) { + area_state[area] = STORAGELITE_AREA_STATE_NONE; + versions[area] = 0; + + _size = std::min(_size, _area_params[area].size); + + // Check validity of master record + master_record_data_t master_rec; + ret = read_record(area, 0, 0, 0, actual_name_size, + &master_rec, sizeof(master_rec), actual_data_size, + false, false, false, true, false, hash, flags, nvstore_key, next_offset); + MBED_ASSERT((ret == STORAGELITE_SUCCESS) || (ret == STORAGELITE_BUFF_TOO_SMALL) || + (ret == STORAGELITE_DATA_CORRUPT) || (ret == STORAGELITE_ERASED)); + if (ret == STORAGELITE_BUFF_TOO_SMALL) { + // Buf too small error means that we have a corrupt master record - + // treat it as such + ret = STORAGELITE_DATA_CORRUPT; + } + + // Master record seems to be erased. Now check if entire erase unit is erased + if (ret == STORAGELITE_ERASED) { + bool erased; + os_ret = is_erase_unit_erased(area, 0, erased); + MBED_ASSERT(!os_ret); + if (erased) { + area_state[area] = STORAGELITE_AREA_STATE_EMPTY; + continue; + } + ret = STORAGELITE_DATA_CORRUPT; + } + + // We have a non valid master record, in a non-empty area. Just erase the area. + if (ret == STORAGELITE_DATA_CORRUPT) { + os_ret = erase_erase_unit(area, 0); + MBED_ASSERT(!os_ret); + area_state[area] = STORAGELITE_AREA_STATE_EMPTY; + continue; + } + versions[area] = master_rec.version; + + area_state[area] = STORAGELITE_AREA_STATE_VALID; + + // Unless both areas are valid (a case handled later), getting here means + // that we found our active area. + _active_area = area; + _active_area_version = versions[area]; + } + + // In case we have two empty areas, arbitrarily use area 0 as the active one. + if ((area_state[0] == STORAGELITE_AREA_STATE_EMPTY) && (area_state[1] == STORAGELITE_AREA_STATE_EMPTY)) { + _active_area = 0; + _active_area_version = 1; + ret = write_master_record(_active_area, _active_area_version, _free_space_offset); + MBED_ASSERT(ret == STORAGELITE_SUCCESS); + _num_ram_table_entries = 0; + _init_done = true; + // Nothing more to do here if active area is empty + return STORAGELITE_SUCCESS; + } + + // In case we have two valid areas, choose the one having the higher version (or 0 + // in case of wrap around). Erase the other one. + if ((area_state[0] == STORAGELITE_AREA_STATE_VALID) && (area_state[1] == STORAGELITE_AREA_STATE_VALID)) { + if ((versions[0] > versions[1]) || (!versions[0])) { + _active_area = 0; + } else { + _active_area = 1; + } + _active_area_version = versions[_active_area]; + os_ret = erase_erase_unit(1 - _active_area, 0); + MBED_ASSERT(!os_ret); + } + + // Currently set free space offset pointer to the end of free space. + // Ram table build process needs it, but will update it. + _free_space_offset = _size; + ret = build_ram_table(false); + // The "max files reached" error is here in order not to fail process on init based on + // previous data. Allow the user to format the storage. Postpone this problem to the following set + // operation. + MBED_ASSERT((ret == STORAGELITE_SUCCESS) || (ret == STORAGELITE_DATA_CORRUPT) || + (ret == STORAGELITE_ERASED) || (ret == STORAGELITE_MAX_FILES_REACHED)); + + if (ret == STORAGELITE_ERASED) { + // Space after last valid record seems to be erased. Now check if it really is + // (in its erase unit) + bool erased; + os_ret = is_erase_unit_erased(_active_area, _free_space_offset, erased); + MBED_ASSERT(!os_ret); + if (!erased) { + // Not erased - means it's corrupt + ret = STORAGELITE_DATA_CORRUPT; + } + } + + // If we have a corrupt record somewhere, perform garbage collection to salvage + // all preceding records + if (ret == STORAGELITE_DATA_CORRUPT) { + ret = garbage_collection(false); + MBED_ASSERT(ret == STORAGELITE_SUCCESS); + } + + _init_done = true; + return STORAGELITE_SUCCESS; +} + +int StorageLite::deinit() +{ + if (_init_done) { + _mutex->lock(); + if (_buff_bd) { + _buff_bd->deinit(); + delete _buff_bd; + _buff_bd = 0; + } else { + _bd->deinit(); + } + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + mbedtls_entropy_free((mbedtls_entropy_context *)_entropy); + delete (mbedtls_entropy_context *)_entropy; + delete[] ram_table; + delete[] _work_buf; + delete[] _encrypt_key; + delete[] _auth_key; + _mutex->unlock(); + // Do not delete the mutex. Keep it alive until class is destroyed (allows + // it to protect cases of deinit and init) + } + + _init_attempts = 0; + _init_done = 0; + + return STORAGELITE_SUCCESS; +} + +int StorageLite::reset() +{ + uint8_t area; + int os_ret, ret; + NVStore& nvstore = NVStore::get_instance(); + + if (!_init_done) { + return STORAGELITE_UNINITIALIZED; + } + + _mutex->lock(); + // Before deleting all info, free all rollback protect keys, stored in NVStore + // Don't check the return value, as this could fail due to previous data being corrupt. + nvstore.free_all_keys_by_owner(NVSTORE_STORAGELITE_OWNER); + + // Erase both areas, and reinitialize the module. + for (area = 0; area < STORAGELITE_NUM_AREAS; area++) { + // Erase master records for both areas + os_ret = erase_erase_unit(area, 0); + if (os_ret) { + ret = STORAGELITE_WRITE_ERROR; + goto end; + } + } + + // Now reinitialize the module + deinit(); + ret = init(_user_bd, _max_files); + +end: + _mutex->unlock(); + return ret; +} + +int StorageLite::factory_reset() +{ + int ret = 0; + + _mutex->lock(); + + // We move back to factory backup copies, so free all rollback protection keys belonging + // to non factory backups + ret = free_rollback_protect_keys(true); + if (ret) { + _mutex->unlock(); + return ret; + } + + // Just invoke garbage collection in factory reset mode + ret = garbage_collection(true); + _mutex->unlock(); + + return ret; +} + +size_t StorageLite::size() const +{ + if (!_init_done) { + return 0; + } + return _size; +} + +size_t StorageLite::free_size() const +{ + if (!_init_done) { + return 0; + } + return _size - _free_space_offset; +} + +int StorageLite::get_first_file(void *file_name, size_t file_name_buf_size, + size_t& actual_file_name_size, uint32_t& handle) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t ind; + uint32_t actual_data_size, hash, flags; + uint32_t next_offset; + int ret; + uint16_t actual_file_name_size_16; + uint16_t nvstore_key; + + if (!_init_done) { + return STORAGELITE_UNINITIALIZED; + } + + _mutex->lock(); + + // find first nonzero entry + for (ind = 0; ind < _num_ram_table_entries; ind++) { + if (ram_table[ind].offset_and_flags && !(ram_table[ind].offset_and_flags & ram_delete_flag)) { + break; + } + } + + if (ind == _num_ram_table_entries) { + _mutex->unlock(); + return STORAGELITE_NOT_FOUND; + } + + handle = ind; + ret = read_record(_active_area, ram_table[ind].offset_and_flags & ram_offset_mask, + file_name, file_name_buf_size, actual_file_name_size_16, + 0, 0, actual_data_size, + true, false, false, false, false, hash, flags, nvstore_key, next_offset); + _mutex->unlock(); + actual_file_name_size = actual_file_name_size_16; + return ret; +} + +int StorageLite::get_next_file(void *file_name, size_t file_name_buf_size, + size_t& actual_file_name_size, uint32_t& handle) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t ind; + uint32_t actual_data_size, hash, flags; + uint32_t next_offset; + int ret; + uint16_t actual_file_name_size_16; + uint16_t nvstore_key; + + if (!_init_done) { + return STORAGELITE_UNINITIALIZED; + } + + if (handle >= _num_ram_table_entries) { + return STORAGELITE_BAD_VALUE; + } + + _mutex->lock(); + + // find first nonzero entry + for (ind = handle + 1; ind < _num_ram_table_entries; ind++) { + if (ram_table[ind].offset_and_flags && !(ram_table[ind].offset_and_flags & ram_delete_flag)) { + break; + } + } + + if (ind == _num_ram_table_entries) { + _mutex->unlock(); + return STORAGELITE_NOT_FOUND; + } + + handle = ind; + ret = read_record(_active_area, ram_table[ind].offset_and_flags & ram_offset_mask, + file_name, file_name_buf_size, actual_file_name_size_16, + 0, 0, actual_data_size, + true, false, false, false, false, hash, flags, nvstore_key, next_offset); + _mutex->unlock(); + actual_file_name_size = actual_file_name_size_16; + return ret; +} + +void StorageLite::offset_in_erase_unit(uint8_t area, uint32_t offset, + uint32_t& offset_from_start, uint32_t& dist_to_end) +{ + uint32_t bd_offset = _area_params[area].address + offset; + if (!_variant_bd_erase_unit_size) { + uint32_t eu_size = _bd->get_erase_size(); + offset_from_start = bd_offset % eu_size; + dist_to_end = eu_size - offset_from_start; + return; + } + + uint32_t agg_offset = 0; + while (bd_offset < agg_offset + _bd->get_erase_size(agg_offset)) { + agg_offset += _bd->get_erase_size(agg_offset); + } + offset_from_start = bd_offset - agg_offset; + dist_to_end = _bd->get_erase_size(agg_offset) - offset_from_start; + +} + +int StorageLite::is_erase_unit_erased(uint8_t area, uint32_t offset, bool& erased) +{ + uint32_t offset_from_start, dist; + offset_in_erase_unit(area, offset, offset_from_start, dist); + uint8_t buf[sizeof(record_header_t)], blanks[sizeof(record_header_t)]; + memset(blanks, _bd->get_erase_value(), sizeof(blanks)); + + while (dist) { + uint32_t chunk = std::min(dist, (uint32_t) sizeof(buf)); + int ret = read_area(area, offset, chunk, buf); + if (ret) { + return STORAGELITE_READ_ERROR; + } + if (memcmp(buf, blanks, chunk)) { + erased = false; + return STORAGELITE_SUCCESS; + } + offset += chunk; + dist -= chunk; + } + erased = true; + return STORAGELITE_SUCCESS; +} + +int StorageLite::check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size) +{ + // In order to save init time, we don't check that the entire area is erased. + // Instead, whenever reaching an erase unit start, check that it's erased, and if not - + // erase it. This is very not likely to happen (assuming area was initialized + // by StorageLite). This can be achieved as all records (except for the master record + // in offset 0) are written in an ascending order. + + if (!offset) { + // Master record in offset 0 is a special case - don't check it + return STORAGELITE_SUCCESS; + } + + while (size) { + uint32_t dist, offset_from_start; + int ret; + offset_in_erase_unit(area, offset, offset_from_start, dist); + uint32_t chunk = std::min(size, dist); + + if (!offset_from_start) { + // We're at the start of an erase unit. Here (and only here), check if it's erased. + bool erased; + ret = is_erase_unit_erased(area, offset, erased); + if (ret) { + return STORAGELITE_READ_ERROR; + } + if (!erased) { + ret = erase_erase_unit(area, offset); + if (ret) { + return STORAGELITE_WRITE_ERROR; + } + } + } + offset += chunk; + size -= chunk; + } + return STORAGELITE_SUCCESS; +} + +void StorageLite::ram_flags_and_offset_by_rec_flags(uint32_t flags, uint16_t ram_table_ind, + bool check_in_table, uint32_t& ram_flags, + uint32_t& offset) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + + ram_flags = 0; + if (flags & delete_flag) { + ram_flags |= ram_delete_flag; + } + if (flags & update_backup_flag) { + ram_flags |= ram_is_backup_flag | ram_has_backup_flag; + } + if (flags & rollback_protect_flag) { + ram_flags |= ram_rb_protect_flag; + } + if (check_in_table) { + if (ram_table[ram_table_ind].offset_and_flags & ram_has_backup_flag) { + ram_flags |= ram_has_backup_flag; + } + ram_flags &= ~ram_rb_protect_flag; + if (ram_table[ram_table_ind].offset_and_flags & ram_rb_protect_flag) { + ram_flags |= ram_rb_protect_flag; + } + } + if ((ram_flags & ram_delete_flag) && !(ram_flags & ram_has_backup_flag)) { + offset = 0; + ram_flags = 0; + } +} + +#endif // STORAGELITE_ENABLED diff --git a/features/filesystem/storagelite/source/StorageLite.h b/features/filesystem/storagelite/source/StorageLite.h new file mode 100644 index 00000000000..500e436914c --- /dev/null +++ b/features/filesystem/storagelite/source/StorageLite.h @@ -0,0 +1,646 @@ +/* + * 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. + */ + +#ifndef MBED_STORAGELITE_H +#define MBED_STORAGELITE_H + +#if !defined(MBEDTLS_CONFIG_FILE) +#include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif +#include "nvstore.h" + +#define STORAGELITE_ENABLED 1 + +#if (!NVSTORE_ENABLED) || !defined(MBEDTLS_ENTROPY_C) +#undef STORAGELITE_ENABLED +#define STORAGELITE_ENABLED 0 +#endif + +#if (STORAGELITE_ENABLED) || defined(DOXYGEN_ONLY) +#include +#include +#include "platform/NonCopyable.h" +#include "PlatformMutex.h" +#include "BlockDevice.h" + +typedef enum { + STORAGELITE_SUCCESS = 0, + STORAGELITE_READ_ERROR = -1, + STORAGELITE_WRITE_ERROR = -2, + STORAGELITE_NOT_FOUND = -3, + STORAGELITE_DATA_CORRUPT = -4, + STORAGELITE_BAD_VALUE = -5, + STORAGELITE_BUFF_TOO_SMALL = -6, + STORAGELITE_NO_SPACE_ON_BD = -7, + STORAGELITE_OS_ERROR = -8, + STORAGELITE_UNINITIALIZED = -9, + STORAGELITE_MAX_FILES_REACHED = -10, + STORAGELITE_NOT_SUPPORTED = -11, + STORAGELITE_RB_PROTECT_ERROR = -12, +} storagelite_status_e; + +#ifndef STORAGELITE_MAX_FILES +#define STORAGELITE_MAX_FILES 64 +#endif + +// defines 2 areas - active and nonactive, not configurable +#define STORAGELITE_NUM_AREAS 2 + +/** StorageLite class + * + * Key value storage over a block device + */ + +class StorageLite : mbed::NonCopyable { +public: + + StorageLite(); + + virtual ~StorageLite(); + + /** + * @brief Returns maximal number of files. + * + * @returns Number of files. + */ + size_t get_max_files() const; + + /** + * @brief Returns file data, given file name (as a binary blob). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[out] actual_data_size Actual data size (bytes). + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_BUFF_TOO_SMALL Not enough memory in user buffer. + */ + int get(const void *file_name, size_t file_name_size, void *data_buf, + size_t data_buf_size, size_t& actual_data_size); + + /** + * @brief Returns file data, given file name (as a string). + * + * @param[in] file_name File name buffer (string). + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[out] actual_data_size Actual data size (bytes). + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_BUFF_TOO_SMALL Not enough memory in user buffer. + */ + int get(const char *file_name, void *data_buf, + size_t data_buf_size, size_t& actual_data_size); + + /** + * @brief Returns file size, given file name (as a binary blob). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[out] actual_data_size Actual data size (bytes). + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int get_file_size(const void *file_name, size_t file_name_size, size_t& actual_data_size); + + /** + * @brief Returns file size, given file name (as a string). + * + * @param[in] file_name File name buffer (string). + * @param[out] actual_data_size Actual data size (bytes). + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int get_file_size(const char *file_name, size_t& actual_data_size); + + /** + * @brief Returns file flags, given file name (as a binary blob). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[out] flags Flags. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int get_file_flags(const void *file_name, size_t file_name_size, uint32_t& flags); + + /** + * @brief Returns file flags, given file name (as a string). + * + * @param[in] file_name File name buffer (string). + * @param[out] flags Flags. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int get_file_flags(const char *file_name, uint32_t& flags); + + /** + * @brief Checks whether file exists, given file name (as a binary blob). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * + * @returns STORAGELITE_SUCCESS Success (file found). + * STORAGELITE_NOT_FOUND File was not found. + */ + int file_exists(const void *file_name, size_t file_name_size); + + /** + * @brief Checks whether file exists, given file name (as a string). + * + * @param[in] file_name File name buffer (string). + * + * @returns STORAGELITE_SUCCESS Success (file found). + * STORAGELITE_NOT_FOUND File was not found. + */ + int file_exists(const char *file_name); + + /** + * @brief Sets file data, given file name (as a binary blob) and data. + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_WRITE_ERROR Physical error writing data. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_NO_SPACE_ON_BD Not enough space on BD. + */ + int set(const void *file_name, size_t file_name_size, const void *data_buf, + size_t data_buf_size, uint32_t flags); + + /** + * @brief Sets file data, given file name (as a string) and data. + * + * @param[in] file_name File name buffer (string). + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_WRITE_ERROR Physical error writing data. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_NO_SPACE_ON_BD Not enough space on BD. + */ + int set(const char *file_name, const void *data_buf, + size_t data_buf_size, uint32_t flags); + + /** + * @brief Remove a file, given a file name (as a binary blob). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int remove(const void *file_name, size_t file_name_size); + + /** + * @brief Remove a file, given a file name (as a string). + * + * @param[in] file_name File name buffer (string). + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + */ + int remove(const char *file_name); + + /** + * @brief Returns name of first file and a search handle for next iterations. + * + * @param[in] file_name File name buffer. + * @param[in] file_name_buf_size File name buffer size. + * @param[out] actual_file_name_size Actual file name size. + * @param[out] handle Iteration handle. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found (no existing files). + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_BUFF_TOO_SMALL Not enough memory in user buffer. + */ + int get_first_file(void *file_name, size_t file_name_buf_size, + size_t& actual_file_name_size, uint32_t& handle); + + /** + * @brief Returns name of next file given iteration handle (handle is updated). + * Function return is STORAGELITE_NOT_FOUND at the end of the search. + * + * @param[in] file_name File name buffer. + * @param[in] file_name_buf_size File name buffer size. + * @param[out] actual_file_name_size Actual file name size. + * @param[out] handle Iteration handle. + * + * @returns STORAGELITE_SUCCESS Success. + * STORAGELITE_NOT_FOUND File was not found (past last file). + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_DATA_CORRUPT Data is corrupt. + * STORAGELITE_BAD_VALUE Bad value in any of the parameters. + * STORAGELITE_BUFF_TOO_SMALL Not enough memory in user buffer. + */ + int get_next_file(void *file_name, size_t file_name_buf_size, + size_t& actual_file_name_size, uint32_t& handle); + + /** + * @brief Initializes StorageLite component. + * + * @param[in] bd Underlying block device. + * @param[in] max_files Maximal number of files. + * + * @returns STORAGELITE_SUCCESS Initialization completed successfully. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_WRITE_ERROR Physical error writing data (on recovery). + */ + int init(BlockDevice *bd, size_t max_files = STORAGELITE_MAX_FILES); + + /** + * @brief Deinitializes StorageLite component. + * Warning: This function is not thread safe and should not be called + * concurrently with other StorageLite functions. + * + * @returns STORAGELITE_SUCCESS Deinitialization completed successfully. + */ + int deinit(); + + /** + * @brief Reset StorageLite areas (delete all data). + * + * @returns STORAGELITE_SUCCESS Reset completed successfully. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_WRITE_ERROR Physical error writing data. + */ + int reset(); + + /** + * @brief Reset StorageLite to factory defaults. i.e. copy all files defined as + * backup copies on top of their active copies. + * + * @returns STORAGELITE_SUCCESS Reset completed successfully. + * STORAGELITE_READ_ERROR Physical error reading data. + * STORAGELITE_WRITE_ERROR Physical error writing data. + */ + int factory_reset(); + + /** + * @brief Return StorageLite size (area size). + * + * @returns StorageLite size. + */ + size_t size() const; + + /** + * @brief Return StorageLite free size. + * + * @returns StorageLite free size. + */ + size_t free_size() const; + + static const uint32_t rollback_protect_flag; + static const uint32_t encrypt_flag; + static const uint32_t update_backup_flag; + + static const size_t max_name_size = 1024; + static const size_t max_data_size = 256 * 1024; + +private: + typedef struct { + uint32_t address; + size_t size; + } storagelite_area_data_t; + + BlockDevice *_bd; + BlockDevice *_user_bd, *_buff_bd; + bool _init_done; + uint32_t _init_attempts; + uint8_t _active_area; + uint16_t _max_files; + uint16_t _active_area_version; + uint32_t _free_space_offset; + size_t _size; + PlatformMutex *_mutex; + void *_entropy; + uint16_t _num_ram_table_entries; + void *_ram_table; + storagelite_area_data_t _area_params[STORAGELITE_NUM_AREAS]; + uint32_t _prog_align_size; + uint8_t *_work_buf; + uint8_t *_encrypt_key; + uint8_t *_auth_key; + bool _variant_bd_erase_unit_size; + + /** + * @brief Read a block from an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to read. + * @param[in] buf Output buffer. + * + * @returns 0 for success, nonzero for failure. + */ + int read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf); + + /** + * @brief Write a block to an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to write. + * @param[in] buf Input buffer. + * + * @returns 0 for success, non-zero for failure. + */ + int write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf); + + /** + * @brief Erase an area. + * + * @param[in] area Area. + * + * @returns 0 for success, nonzero for failure. + */ + int erase_area(uint8_t area); + + /** + * @brief Erase an erase unit. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * + * @returns 0 for success, non-zero for failure. + */ + int erase_erase_unit(uint8_t area, uint32_t offset); + + /** + * @brief Calculate addresses and sizes of areas. + */ + void calc_area_params(); + + /** + * @brief Read an StorageLite record from a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[in] name_buf file name buffer. + * @param[in] name_buf_size file name buffer size (bytes). + * @param[out] actual_name_size Actual file name size (bytes). + * @param[in] data_buf Data buffer. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[out] actual_data_size Actual data size (bytes). + * @param[in] copy_name Copy name to user buffer. + * @param[in] allow_partial_name Allow returning partial name to name buffer. + * @param[in] check_expected_name Check whether file name belongs to this record. + * @param[in] copy_data Copy data to user buffer. + * @param[in] calc_hash Calculate hash (on file name). + * @param[out] hash Calculated hash. + * @param[out] flags Record flags. + * @param[out] nvstore_key NVstore key (in rollback protection case). + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int read_record(uint8_t area, uint32_t offset, void *name_buf, uint16_t name_buf_size, + uint16_t& actual_name_size, void *data_buf, uint32_t data_buf_size, + uint32_t& actual_data_size, bool copy_name, bool allow_partial_name, + bool check_expected_name, bool copy_data, bool calc_hash, uint32_t& hash, + uint32_t& flags, uint16_t& nvstore_key, uint32_t& next_offset); + + /** + * @brief Write an StorageLite record to a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[in] name_buf Name. + * @param[in] name_buf_size Name size (bytes). + * @param[in] data_buf Data buffer. + * @param[in] data_buf_size Data size (bytes). + * @param[in] flags Record flags. + * @param[in] nvstore_key NVStore key to store in rollback protection case. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int write_record(uint8_t area, uint32_t offset, const void *name_buf, + uint16_t name_buf_size, const void *data_buf, uint32_t data_buf_size, + uint32_t flags, uint16_t nvstore_key, uint32_t& next_offset); + + /** + * @brief Write a master record of a given area. + * + * @param[in] area Area. + * @param[in] version Area version. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int write_master_record(uint8_t area, uint16_t version, uint32_t& next_offset); + + /** + * @brief Copy a record from one area to the opposite one. + * + * @param[in] from_area Area to copy record from. + * @param[in] from_offset Offset in source area. + * @param[in] to_offset Offset in destination area. + * @param[out] to_next_offset Offset of next record in destination area. + * + * @returns 0 for success, nonzero for failure. + */ + int copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t& to_next_offset); + + /** + * @brief As part of GC process, copy all records in RAM table to the opposite area. + * + * @param[in] is_backup Copy records according to is_backup flag in RAM table. + * @param[in] from_area Area to copy record from. + * @param[in] to_offset Offset in destination area. + * @param[out] to_next_offset Offset of next record in destination area. + * + * @returns 0 for success, nonzero for failure. + */ + int copy_all_records(bool is_backup, uint8_t from_area, uint32_t to_offset, uint32_t& to_next_offset); + + /** + * @brief Garbage collection (compact all records from active area to the standby one). + * + * @param[in] factory_reset_mode Only copy backup copies of files (factory reset). + * + * @returns 0 for success, nonzero for failure. + */ + int garbage_collection(bool factory_reset_mode); + + + /** + * @brief Find a file according to name and set it. + * + * @param[in] area Area to write to. + * @param[in] offset Offset in target area. + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int find_and_set(uint8_t area, uint32_t offset, const void *file_name, + uint16_t file_name_size, const void *data_buf, uint32_t data_buf_size, + uint32_t flags, uint32_t& next_offset); + /** + * @brief Actual logics of get API (covers also all other get APIs). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[in] copy_data Copy data to user buffer. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[out] actual_data_size Actual data size (bytes). + * @param[out] flags Flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_get(const void *file_name, uint16_t file_name_size, bool copy_data, + void *data_buf, uint32_t data_buf_size, uint32_t& actual_data_size, + uint32_t& flags); + + /** + * @brief Actual logics of set API (covers also the remove API). + * + * @param[in] file_name File name buffer (binary). + * @param[in] file_name_size File name buffer size. + * @param[in] data_buf Buffer to store data on. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_set(const void *file_name, uint16_t file_name_size, + const void *data_buf, uint32_t data_buf_size, uint32_t flags); + + /** + * @brief Build RAM table and update _free_space_offset (scanning all the records in the area). + * + * @param[in] backup_only Bring only records with update backup flag set. + * + * @returns 0 for success, nonzero for failure. + */ + int build_ram_table(bool backup_only); + + /** + * @brief Free all allocated rollback protect keys (or only non backup ones). + * + * @param[in] non_backup_only Free just the ones that are not backup copies. + * + * @returns 0 for success, nonzero for failure. + */ + int free_rollback_protect_keys(bool non_backup_only); + + /** + * @brief Calculate offset from start of erase unit. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[out] offset_from_start Offset from start of erase unit. + * @param[out] dist_to_end Distance to end of erase unit. + * + * @returns offset in erase unit. + */ + void offset_in_erase_unit(uint8_t area, uint32_t offset, uint32_t& offset_from_start, + uint32_t& dist_to_end); + + /** + * @brief Check whether erase unit is erased (from offset until end of unit). + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[out] erased Unit is erased. + * + * @returns 0 for success, nonzero for failure. + */ + int is_erase_unit_erased(uint8_t area, uint32_t offset, bool& erased); + + /** + * @brief Before writing a record, check whether we are crossing an erase unit. + * If we do, check if it's erased, and erase it if not. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Write size. + * + * @returns 0 for success, nonzero for failure. + */ + int check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size); + + /** + * @brief Manipulate RAM flags and offset according to record flags. + * + * @param[in] flags Record flags. + * @param[in] ram_table_ind RAM table index. + * @param[in] check_in_table Check value in RAM table. + * @param[out] ram_flags Modified RAM flags. + * @param[out] offset Modified offset. + * + * @returns none. + */ + void ram_flags_and_offset_by_rec_flags(uint32_t flags, uint16_t ram_table_ind, bool check_in_table, + uint32_t& ram_flags, uint32_t& offset); + +}; +/** @}*/ + +#endif // STORAGELITE_ENABLED + +#endif diff --git a/features/filesystem/storagelite/source/StorageLiteFS.cpp b/features/filesystem/storagelite/source/StorageLiteFS.cpp new file mode 100644 index 00000000000..c03af689d94 --- /dev/null +++ b/features/filesystem/storagelite/source/StorageLiteFS.cpp @@ -0,0 +1,286 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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 "StorageLiteFS.h" + +#if STORAGELITE_ENABLED + +#include "mbed_retarget.h" +#include "string.h" +#include "stdlib.h" + +typedef struct { + char *file_name; + int open_flags; +} file_handle_data_t; + +// Convert basic StorageLite error numbers to errno based ones +static int convert_to_errno(int err) +{ + switch (err) { + case STORAGELITE_SUCCESS: + return 0; + case STORAGELITE_READ_ERROR: + return -EIO; + case STORAGELITE_WRITE_ERROR: + return -EIO; + case STORAGELITE_NOT_FOUND: + return -ENOENT; + case STORAGELITE_DATA_CORRUPT: + return -EILSEQ; + case STORAGELITE_BAD_VALUE: + return -EINVAL; + case STORAGELITE_BUFF_TOO_SMALL: + return -ENOMEM; + case STORAGELITE_NO_SPACE_ON_BD: + return -ENOSPC; + case STORAGELITE_OS_ERROR: + return -EINTR; // FIXME: Find a better error + case STORAGELITE_UNINITIALIZED: + return -ENXIO; // FIXME: Find a better error + case STORAGELITE_MAX_FILES_REACHED: + return -ENFILE; + case STORAGELITE_NOT_SUPPORTED: + return -ENOSYS; + default: + return -err; + } +} + +StorageLiteFS::StorageLiteFS(const char *name, StorageLite *stlite, uint32_t flags) + : FileSystem(name), _stlite(stlite), _flags(flags), _bd(0) +{ + MBED_ASSERT(stlite); + mount(_bd); +} + +StorageLiteFS::~StorageLiteFS() +{ + unmount(); +} + +int StorageLiteFS::mount(BlockDevice *bd) +{ + _bd = bd; + int ret = _stlite->init(bd); + return convert_to_errno(ret); +} + +int StorageLiteFS::unmount() +{ + int ret = _stlite->deinit(); + _bd = 0; + return convert_to_errno(ret); +} + +int StorageLiteFS::reformat(BlockDevice *bd) +{ + if (bd) { + _bd = bd; + } + + int ret = _stlite->init(_bd); + if (ret) { + return convert_to_errno(ret); + } + + ret = _stlite->reset(); + if (ret) { + return convert_to_errno(ret); + } + + return 0; +} + +int StorageLiteFS::remove(const char *filename) +{ + int ret = _stlite->remove(filename); + return convert_to_errno(ret); +} + +int StorageLiteFS::stat(const char *name, struct stat *st) +{ + size_t file_size; + int ret = _stlite->get_file_size(name, file_size); + st->st_size = file_size; + return convert_to_errno(ret); +} + +int StorageLiteFS::statvfs(const char *name, struct statvfs *st) +{ + memset(st, 0, sizeof(struct statvfs)); + bd_size_t bsize = _bd->get_erase_size(); + + st->f_bsize = bsize; + st->f_frsize = bsize; + st->f_blocks = _stlite->size() / bsize; + st->f_bfree = _stlite->free_size() / bsize; + st->f_bavail = st->f_bfree; + st->f_namemax = StorageLite::max_name_size; + + return 0; +} + +int StorageLiteFS::file_open(mbed::fs_file_t *file, const char *path, int flags) +{ + file_handle_data_t *handle = new (std::nothrow) file_handle_data_t; + if (!handle) { + return -ENOMEM; + } + + // We don't support opening for read-write, nor for appending + if ((flags & O_RDWR) || (flags & O_APPEND)) { + return -EINVAL; + } + + if ((flags & 1) == O_RDONLY) { + // When open for reading, we need to check if file exists right now + int ret = _stlite->file_exists(path); + if (ret != STORAGELITE_SUCCESS) { + return convert_to_errno(ret); + } + } else { + if (flags & O_EXCL) { + int ret = _stlite->file_exists(path); + // Fail if file exists + if (ret == STORAGELITE_SUCCESS) { + return -EEXIST; + } + if (ret != STORAGELITE_NOT_FOUND) { + return convert_to_errno(ret); + } + } + } + + handle->file_name = new (std::nothrow) char[strlen(path) + 1]; + if (!handle->file_name) { + return -ENOMEM; + } + strcpy(handle->file_name, path); + handle->open_flags = flags; + + *file = handle; + return 0; +} + +int StorageLiteFS::file_close(mbed::fs_file_t file) +{ + file_handle_data_t *handle = (file_handle_data_t *) file; + + if (!handle) { + return -EINVAL; + } + + // Special case - opened for write, but didn't write anything before closing. + // Just create an empty file + if (((handle->open_flags & 1) == O_WRONLY) && handle->file_name) { + int ret = _stlite->set(handle->file_name, 0, 0, _flags); + if (ret) { + return convert_to_errno(ret); + } + } + + if (handle->file_name) { + delete[] handle->file_name; + } + delete handle; + + return 0; +} + +ssize_t StorageLiteFS::file_read(mbed::fs_file_t file, void *buffer, size_t len) +{ + file_handle_data_t *handle = (file_handle_data_t *) file; + + if (!handle) { + return -EINVAL; + } + + // We only support one (correct) read/write operation after open + if (((handle->open_flags & 1) == O_WRONLY) || !handle->file_name) { + return -ENOSYS; + } + + size_t num_read; + int ret = _stlite->get(handle->file_name, buffer, len, num_read); + if (ret) { + return convert_to_errno(ret); + } + + // This indicates that the action is complete + delete[] handle->file_name; + handle->file_name = 0; + + return num_read; +} + +ssize_t StorageLiteFS::file_write(mbed::fs_file_t file, const void *buffer, size_t len) +{ + file_handle_data_t *handle = (file_handle_data_t *) file; + + if (!handle) { + return -EINVAL; + } + + // We only support one (correct) read/write operation after open + if (((handle->open_flags & 1) == O_RDONLY) || !handle->file_name) { + return -ENOSYS; + } + + int ret = _stlite->set(handle->file_name, buffer, len, _flags); + if (ret) { + return convert_to_errno(ret); + } + + // This indicates that the action is complete + delete[] handle->file_name; + handle->file_name = 0; + + return len; +} + +off_t StorageLiteFS::file_seek(mbed::fs_file_t file, off_t offset, int whence) +{ + return -ENOSYS; +} + +off_t StorageLiteFS::file_tell(mbed::fs_file_t file) +{ + return -ENOSYS; +} + +off_t StorageLiteFS::file_size(mbed::fs_file_t file) +{ + file_handle_data_t *handle = (file_handle_data_t *) file; + + if (!handle) { + return -EINVAL; + } + + // We only support one (correct) read/write operation after open + if (((handle->open_flags & 1) == O_WRONLY) || !handle->file_name) { + return -ENOSYS; + } + + size_t file_size; + int ret = _stlite->get_file_size(handle->file_name, file_size); + if (ret) { + return 0; + } + + return file_size; +} + +#endif // STORAGELITE_ENABLED diff --git a/features/filesystem/storagelite/source/StorageLiteFS.h b/features/filesystem/storagelite/source/StorageLiteFS.h new file mode 100644 index 00000000000..d35420598a1 --- /dev/null +++ b/features/filesystem/storagelite/source/StorageLiteFS.h @@ -0,0 +1,190 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_STORAGELITEFS_H +#define MBED_STORAGELITEFS_H + +#include "StorageLite.h" + +#if (STORAGELITE_ENABLED) || defined(DOXYGEN_ONLY) + +#include "FileSystem.h" +#include "BlockDevice.h" + +/** + * StorageLiteFS, FileSystem interface for the StorageLite feature + */ +class StorageLiteFS : public mbed::FileSystem { +public: + + /** + * @brief StorageLiteFS constructor. + * + * @param[in] name Name to add filesystem to tree as. + * @param[in] stlite StorageLite instance. + * @param[in] flags StorageLite flags given when creating files. + * + * @return none. + */ + StorageLiteFS(const char *name = 0, StorageLite *stlite = 0, + uint32_t flags = StorageLite::encrypt_flag); + + virtual ~StorageLiteFS(); + + /** + * @brief Mounts a filesystem to a block device + * + * @param[in] bd Underlying block device. + * + * @return 0 for success, nonzero for failure. + */ + virtual int mount(BlockDevice *bd); + + /** + * @brief Unmounts a filesystem from the underlying block device + * + * @return 0 for success, nonzero for failure. + */ + virtual int unmount(); + + /** + * @brief Reformats a filesystem, results in an empty and mounted filesystem + * + * @param[in] bd Underlying block device. + * BlockDevice to reformat and mount. If NULL, the mounted + * block device will be used. + * Note: if mount fails, bd must be provided. + * Default: NULL + * + * @return 0 for success, nonzero for failure. + */ + virtual int reformat(BlockDevice *bd); + + /** + * @brief Remove a file from the filesystem. + * + * @param[in] path The name of the file to remove. + * + * @return 0 for success, nonzero for failure. + */ + virtual int remove(const char *path); + + /** + * @brief Store information about the file in a stat structure + * + * @param[in] path The name of the file to find information about + * @param[in] st The stat buffer to write to + * + * @return 0 for success, nonzero for failure. + */ + virtual int stat(const char *path, struct stat *st); + + /** + * @brief Store information about the mounted filesystem in a statvfs structure + * + * @param[in] path The name of the file to find information about + * @param[in] buf The stat buffer to write to + * + * @return 0 for success, nonzero for failure. + */ + virtual int statvfs(const char *path, struct statvfs *buf); + +protected: + /** + * @brief Open a file on the filesystem + * + * @param[in] file Destination for the handle to a newly created file + * @param[in] path The name of the file to open + * @param[in] flags The flags to open the file in, one of O_RDONLY, O_WRONLY, O_RDWR, + * bitwise or'd with one of O_CREAT, O_TRUNC, O_APPEND + * + * @return 0 for success, nonzero for failure. + */ + virtual int file_open(mbed::fs_file_t *file, const char *path, int flags); + + /** + * @brief Close a file + * + * @param[in] file File handle + * + * @return 0 for success, nonzero for failure. + */ + virtual int file_close(mbed::fs_file_t file); + + /** + * @brief Read the contents of a file into a buffer + * + * @param[in] file File handle + * @param[in] buffer The buffer to read in to + * @param[in] size The number of bytes to read + * + * @return The number of bytes read, negative error on failure + */ + virtual ssize_t file_read(mbed::fs_file_t file, void *buffer, size_t size); + + /** + * @brief Write the contents of a buffer to a file + * + * @param[in] file File handle + * @param[in] buffer The buffer to write from + * @param[in] size The number of bytes to write + * + * @return The number of bytes written, negative error on failure + */ + virtual ssize_t file_write(mbed::fs_file_t file, const void *buffer, size_t size); + + /** + * @brief Move the file position to a given offset from from a given location + * + * @param[in] file File handle + * @param[in] offset The offset from whence to move to + * @param[in] whence The start of where to seek + * SEEK_SET to start from beginning of file, + * SEEK_CUR to start from current position in file, + * SEEK_END to start from end of file + * + * @return Always 0 (unsupported) + */ + virtual off_t file_seek(mbed::fs_file_t file, off_t offset, int whence); + + /** + * @brief Get the file position of the file + * + * @param[in] file File handle + * + * @return Always 0 (unsupported) + */ + virtual off_t file_tell(mbed::fs_file_t file); + + /** + * @brief Get the size of the file + * + * @param[in] file File handle + * + * @return Always 0 (unsupported) + */ + virtual off_t file_size(mbed::fs_file_t file); + +private: + StorageLite *_stlite; + uint32_t _flags; + BlockDevice *_bd; +}; + +/** @}*/ + +#endif // STORAGELITE_ENABLED + +#endif diff --git a/features/nvstore/source/nvstore.h b/features/nvstore/source/nvstore.h index 7665d37904c..ee161cd355c 100644 --- a/features/nvstore/source/nvstore.h +++ b/features/nvstore/source/nvstore.h @@ -57,7 +57,12 @@ typedef enum { typedef enum { NVSTORE_UNSPECIFIED_OWNER = 0, + // All owners (by features) should be specified here. + + NVSTORE_STORAGELITE_OWNER = 1, + + // Should not exceed this NVSTORE_MAX_OWNERS = 16 } nvstore_owner_e;