diff --git a/libraries/KVStore/FileSystemStore.cpp b/libraries/KVStore/FileSystemStore.cpp new file mode 100644 index 00000000..b719951f --- /dev/null +++ b/libraries/KVStore/FileSystemStore.cpp @@ -0,0 +1,636 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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 "FileSystemStore.h" +// #include "kv_config.h" +#include "Dir.h" +#include "File.h" +#include "BlockDevice.h" +#include + +#define FSST_REVISION 1 +#define FSST_MAGIC 0x46535354 // "FSST" hex 'magic' signature + +#define FSST_DEFAULT_FOLDER_PATH "kvstore" //default FileSystemStore folder path on fs + +// Only write once flag is supported, other two are kept in storage but ignored +static const uint32_t supported_flags = mbed::KVStore::WRITE_ONCE_FLAG | mbed::KVStore::REQUIRE_CONFIDENTIALITY_FLAG | + mbed::KVStore::REQUIRE_REPLAY_PROTECTION_FLAG; + +using namespace mbed; + +static const char *get_filesystemstore_folder_path() { // FIXME the user could pass this in the constructor + return "kvstore"; +} + + +namespace { + +// incremental set handle +typedef struct { + char *key; + uint32_t create_flags; + size_t data_size; + File *file_handle; +} inc_set_handle_t; + +// iterator handle +typedef struct { + void *dir_handle; + char *prefix; +} key_iterator_handle_t; + +} // anonymous namespace + +// Local Functions +static char *string_ndup(const char *src, size_t size); + + +// Class Functions +FileSystemStore::FileSystemStore(FileSystem *fs) : _fs(fs), + _is_initialized(false), _cfg_fs_path(NULL), _cfg_fs_path_size(0), + _full_path_key(NULL), _cur_inc_data_size(0), _cur_inc_set_handle(NULL) +{ + +} + +int FileSystemStore::init() +{ + int status = KVSTORE_SUCCESS; + + _mutex.lock(); + const char *temp_path = get_filesystemstore_folder_path(); + if (temp_path == NULL) { + _cfg_fs_path_size = strlen(FSST_DEFAULT_FOLDER_PATH); + _cfg_fs_path = string_ndup(FSST_DEFAULT_FOLDER_PATH, _cfg_fs_path_size); + } else { + _cfg_fs_path_size = strlen(temp_path); + _cfg_fs_path = string_ndup(temp_path, _cfg_fs_path_size); + } + + _full_path_key = new char[_cfg_fs_path_size + KVStore::MAX_KEY_SIZE + 1]; + memset(_full_path_key, 0, (_cfg_fs_path_size + KVStore::MAX_KEY_SIZE + 1)); + strncpy(_full_path_key, _cfg_fs_path, _cfg_fs_path_size); + _full_path_key[_cfg_fs_path_size] = '/'; + _cur_inc_data_size = 0; + _cur_inc_set_handle = NULL; + Dir kv_dir; + + if (kv_dir.open(_fs, _cfg_fs_path) != 0) { + // tr_info("KV Dir: %s, doesnt exist - creating new.. ", _cfg_fs_path); //TBD verify ERRNO NOEXIST + if (_fs->mkdir(_cfg_fs_path,/* which flags ? */0777) != 0) { + // tr_error("KV Dir: %s, mkdir failed.. ", _cfg_fs_path); //TBD verify ERRNO NOEXIST + status = KVSTORE_ERROR_FAILED_OPERATION; + goto exit_point; + } + } else { + // tr_info("KV Dir: %s, exists(verified) - now closing it", _cfg_fs_path); + if (kv_dir.close() != 0) { + // tr_error("KV Dir: %s, dir_close failed", _cfg_fs_path); //TBD verify ERRNO NOEXIST + } + } + + _is_initialized = true; +exit_point: + + _mutex.unlock(); + + return status; + +} + +int FileSystemStore::deinit() +{ + _mutex.lock(); + _is_initialized = false; + delete[] _cfg_fs_path; + delete[] _full_path_key; + _mutex.unlock(); + return KVSTORE_SUCCESS; + +} + +int FileSystemStore::reset() +{ + int status = KVSTORE_SUCCESS; + Dir kv_dir; + struct dirent dir_ent; + + _mutex.lock(); + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + kv_dir.open(_fs, _cfg_fs_path); + + while (kv_dir.read(&dir_ent) != 0) { + if (dir_ent.d_type != DT_REG) { + continue; + } + // Build File's full path name and delete it (even if write-onced) + _build_full_path_key(dir_ent.d_name); + _fs->remove(_full_path_key); + } + + kv_dir.close(); + +exit_point: + _mutex.unlock(); + return status; +} + +int FileSystemStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags) +{ + int status = KVSTORE_SUCCESS; + set_handle_t handle; + + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + if ((!is_valid_key(key)) || ((buffer == NULL) && (size > 0))) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + status = set_start(&handle, key, size, create_flags); + if (status != KVSTORE_SUCCESS) { + // tr_error("FSST Set set_start Failed: %d", status); + goto exit_point; + } + + status = set_add_data(handle, buffer, size); + if (status != KVSTORE_SUCCESS) { + // tr_error("FSST Set set_add_data Failed: %d", status); + set_finalize(handle); + goto exit_point; + } + + status = set_finalize(handle); + if (status != KVSTORE_SUCCESS) { + // tr_error("FSST Set set_finalize Failed: %d", status); + goto exit_point; + } + +exit_point: + + return status; +} + +int FileSystemStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset) +{ + int status = KVSTORE_SUCCESS; + + File kv_file; + size_t kv_file_size = 0; + size_t value_actual_size = 0; + + _mutex.lock(); + + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + key_metadata_t key_metadata; + + if ((status = _verify_key_file(key, &key_metadata, &kv_file)) != KVSTORE_SUCCESS) { + // tr_debug("File Verification failed, status: %d", status); + goto exit_point; + } + + kv_file_size = kv_file.size() - key_metadata.metadata_size; + // Actual size is the minimum of buffer_size and remainder of data in file (file's data size - offset) + value_actual_size = buffer_size; + if (offset > kv_file_size) { + status = KVSTORE_ERROR_INVALID_SIZE; + goto exit_point; + } else if ((kv_file_size - offset) < buffer_size) { + value_actual_size = kv_file_size - offset; + } + + if ((buffer == NULL) && (value_actual_size > 0)) { + status = KVSTORE_ERROR_INVALID_DATA_DETECTED; + goto exit_point; + } + + if (actual_size != NULL) { + *actual_size = value_actual_size; + } + + kv_file.seek(key_metadata.metadata_size + offset, SEEK_SET); + // Read remainder of data + kv_file.read(buffer, value_actual_size); + +exit_point: + if ((status == KVSTORE_SUCCESS) || + (status == KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + kv_file.close(); + } + _mutex.unlock(); + + return status; +} + +int FileSystemStore::get_info(const char *key, info_t *info) +{ + int status = KVSTORE_SUCCESS; + File kv_file; + + _mutex.lock(); + + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + key_metadata_t key_metadata; + + if ((status = _verify_key_file(key, &key_metadata, &kv_file)) != KVSTORE_SUCCESS) { + // tr_debug("File Verification failed, status: %d", status); + goto exit_point; + } + + if (info != NULL) { + info->size = kv_file.size() - key_metadata.metadata_size; + info->flags = key_metadata.user_flags; + } + +exit_point: + if ((status == KVSTORE_SUCCESS) || + (status == KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + kv_file.close(); + } + _mutex.unlock(); + + return status; +} + +int FileSystemStore::remove(const char *key) +{ + File kv_file; + key_metadata_t key_metadata; + + _mutex.lock(); + + int status = KVSTORE_SUCCESS; + + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + /* If File Exists and is Valid, then check its Write Once Flag to verify its disabled before removing */ + /* If File exists and is not valid, or is Valid and not Write-Onced then remove it */ + if ((status = _verify_key_file(key, &key_metadata, &kv_file)) == KVSTORE_SUCCESS) { + if (key_metadata.user_flags & KVStore::WRITE_ONCE_FLAG) { + kv_file.close(); + // tr_error("File: %s, Exists but write protected", _full_path_key); + status = KVSTORE_ERROR_WRITE_PROTECTED; + goto exit_point; + } + } else if ((status == KVSTORE_ERROR_ITEM_NOT_FOUND) || + (status == KVSTORE_ERROR_INVALID_ARGUMENT)) { + goto exit_point; + } + kv_file.close(); + + if (0 != _fs->remove(_full_path_key)) { + status = KVSTORE_ERROR_FAILED_OPERATION; + } + +exit_point: + _mutex.unlock(); + return status; +} + +// Incremental set API +int FileSystemStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags) +{ + int status = KVSTORE_SUCCESS; + inc_set_handle_t *set_handle = NULL; + File *kv_file; + key_metadata_t key_metadata; + int key_len = 0; + + if (create_flags & ~supported_flags) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + // Only a single key file can be incrementaly editted at a time + _mutex.lock(); + + kv_file = new File; + + if (handle == NULL) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + /* If File Exists and is Valid, then check its Write Once Flag to verify its disabled before setting */ + /* If File exists and is not valid, or is Valid and not Write-Onced then erase it */ + status = _verify_key_file(key, &key_metadata, kv_file); + + if (status == KVSTORE_ERROR_INVALID_ARGUMENT) { + // tr_error("File Verification failed, status: %d", status); + goto exit_point; + } + + if (status == KVSTORE_SUCCESS) { + if (key_metadata.user_flags & KVStore::WRITE_ONCE_FLAG) { + kv_file->close(); + status = KVSTORE_ERROR_WRITE_PROTECTED; + goto exit_point; + } + } + + /* For Success (not write_once) and for corrupted data close file before recreating it as a new file */ + if (status != KVSTORE_ERROR_ITEM_NOT_FOUND) { + kv_file->close(); + } + + if ((status = kv_file->open(_fs, _full_path_key, O_WRONLY | O_CREAT | O_TRUNC)) != KVSTORE_SUCCESS) { + // tr_info("set_start failed to open: %s, for writing, err: %d", _full_path_key, status); + status = KVSTORE_ERROR_FAILED_OPERATION ; + goto exit_point; + } + _cur_inc_data_size = 0; + + set_handle = new inc_set_handle_t; + set_handle->create_flags = create_flags; + set_handle->data_size = final_data_size; + set_handle->file_handle = kv_file; + key_len = strlen(key); + set_handle->key = string_ndup(key, key_len); + *handle = (set_handle_t)set_handle; + _cur_inc_set_handle = *handle; + + key_metadata.magic = FSST_MAGIC; + key_metadata.metadata_size = sizeof(key_metadata_t); + key_metadata.revision = FSST_REVISION; + key_metadata.user_flags = create_flags; + kv_file->write(&key_metadata, sizeof(key_metadata_t)); + +exit_point: + if (status != KVSTORE_SUCCESS) { + delete kv_file; + _mutex.unlock(); + } + return status; +} + +int FileSystemStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size) +{ + int status = KVSTORE_SUCCESS; + size_t added_data = 0; + inc_set_handle_t *set_handle = (inc_set_handle_t *)handle; + File *kv_file; + + if (((value_data == NULL) && (data_size > 0)) || (handle == NULL) || (handle != _cur_inc_set_handle)) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + // Single key incrementally edited, can be edited from multiple threads - lock to protect + _inc_data_add_mutex.lock(); + if ((_cur_inc_data_size + data_size) > set_handle->data_size) { + // tr_warning("Added Data(%d) will exceed set_start final size(%d) - not adding data to file: %s", + // _cur_inc_data_size + data_size, set_handle->data_size, _full_path_key); + status = KVSTORE_ERROR_INVALID_SIZE; + goto exit_point; + } + + kv_file = set_handle->file_handle; + + added_data = kv_file->write(value_data, data_size); + if (added_data != data_size) { + status = KVSTORE_ERROR_FAILED_OPERATION ; + } + _cur_inc_data_size += added_data; + +exit_point: + if (status != KVSTORE_ERROR_INVALID_ARGUMENT) { + _inc_data_add_mutex.unlock(); + } + + return status; +} + +int FileSystemStore::set_finalize(set_handle_t handle) +{ + int status = KVSTORE_SUCCESS; + inc_set_handle_t *set_handle = NULL; + + if ((handle == NULL) || (handle != _cur_inc_set_handle)) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + set_handle = (inc_set_handle_t *)handle; + + if (set_handle->key == NULL) { + status = KVSTORE_ERROR_INVALID_DATA_DETECTED; + } else { + if (_cur_inc_data_size != set_handle->data_size) { + // tr_error("Accumulated Data (%d) size doesn't match set_start final size (%d) - file: %s", _cur_inc_data_size, + // set_handle->data_size, _full_path_key); + status = KVSTORE_ERROR_INVALID_SIZE; + _fs->remove(_full_path_key); + } + delete[] set_handle->key; + } + + set_handle->file_handle->close(); + delete set_handle->file_handle; + delete set_handle; + _cur_inc_data_size = 0; + _cur_inc_set_handle = NULL; + +exit_point: + if (status != KVSTORE_ERROR_INVALID_ARGUMENT) { + _mutex.unlock(); + } + + return status; +} + +int FileSystemStore::iterator_open(iterator_t *it, const char *prefix) +{ + int status = KVSTORE_SUCCESS; + Dir *kv_dir = NULL; + key_iterator_handle_t *key_it = NULL; + + if (it == NULL) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + key_it = new key_iterator_handle_t; + key_it->dir_handle = NULL; + key_it->prefix = NULL; + if (prefix != NULL) { + key_it->prefix = string_ndup(prefix, KVStore::MAX_KEY_SIZE); + } + + kv_dir = new Dir; + if (kv_dir->open(_fs, _cfg_fs_path) != 0) { + // tr_error("KV Dir: %s, doesnt exist", _cfg_fs_path); //TBD verify ERRNO NOEXIST + delete kv_dir; + if (key_it->prefix != NULL) { + delete[] key_it->prefix; + } + delete key_it; + status = KVSTORE_ERROR_ITEM_NOT_FOUND; + goto exit_point; + } + + key_it->dir_handle = kv_dir; + + *it = (iterator_t)key_it; + +exit_point: + _mutex.unlock(); + + return status; +} + +int FileSystemStore::iterator_next(iterator_t it, char *key, size_t key_size) +{ + Dir *kv_dir; + struct dirent kv_dir_ent; + int status = KVSTORE_ERROR_ITEM_NOT_FOUND; + key_iterator_handle_t *key_it; + size_t key_name_size = KVStore::MAX_KEY_SIZE; + if (key_size < key_name_size) { + key_name_size = key_size; + } + + _mutex.lock(); + if (false == _is_initialized) { + status = KVSTORE_ERROR_NOT_READY; + goto exit_point; + } + + key_it = (key_iterator_handle_t *)it; + + if ((key_it->prefix != NULL) && (key_name_size < strlen(key_it->prefix))) { + status = KVSTORE_ERROR_INVALID_SIZE; + goto exit_point; + } + + kv_dir = (Dir *)key_it->dir_handle; + + while (kv_dir->read(&kv_dir_ent) != 0) { + if (kv_dir_ent.d_type != DT_REG) { + continue; + } + + if ((key_it->prefix == NULL) || + (strncmp(kv_dir_ent.d_name, key_it->prefix, strlen(key_it->prefix)) == 0)) { + if (key_name_size < strlen(kv_dir_ent.d_name)) { + status = KVSTORE_ERROR_INVALID_SIZE; + break; + } + strncpy(key, kv_dir_ent.d_name, key_name_size); + key[key_name_size - 1] = '\0'; + status = KVSTORE_SUCCESS; + break; + } + } + +exit_point: + _mutex.unlock(); + return status; +} + +int FileSystemStore::iterator_close(iterator_t it) +{ + int status = KVSTORE_SUCCESS; + key_iterator_handle_t *key_it = (key_iterator_handle_t *)it; + + _mutex.lock(); + if (key_it == NULL) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + if (key_it->prefix != NULL) { + delete[] key_it->prefix; + } + + if (key_it->dir_handle != NULL) { + ((Dir *)(key_it->dir_handle))->close(); + delete ((Dir *)(key_it->dir_handle)); + } + delete key_it; + +exit_point: + _mutex.unlock(); + return status; +} + +int FileSystemStore::_verify_key_file(const char *key, key_metadata_t *key_metadata, File *kv_file) +{ + int status = KVSTORE_SUCCESS; + + if (!is_valid_key(key)) { + status = KVSTORE_ERROR_INVALID_ARGUMENT; + goto exit_point; + } + + _build_full_path_key(key); + + if (0 != kv_file->open(_fs, _full_path_key, O_RDONLY)) { + status = KVSTORE_ERROR_ITEM_NOT_FOUND; + goto exit_point; + } + + //Read Metadata + kv_file->read(key_metadata, sizeof(key_metadata_t)); + + if ((key_metadata->magic != FSST_MAGIC) || + (key_metadata->revision > FSST_REVISION)) { + status = KVSTORE_ERROR_INVALID_DATA_DETECTED; + goto exit_point; + } + +exit_point: + return status; +} + +int FileSystemStore::_build_full_path_key(const char *key_src) +{ + strncpy(&_full_path_key[_cfg_fs_path_size + 1/* for path's \ */], key_src, KVStore::MAX_KEY_SIZE); + _full_path_key[(_cfg_fs_path_size + KVStore::MAX_KEY_SIZE)] = '\0'; + return 0; +} + +// Local Functions +static char *string_ndup(const char *src, size_t size) +{ + char *string_copy = new char[size + 1]; + strncpy(string_copy, src, size); + string_copy[size] = '\0'; + return string_copy; +} diff --git a/libraries/KVStore/FileSystemStore.h b/libraries/KVStore/FileSystemStore.h new file mode 100644 index 00000000..12bf4418 --- /dev/null +++ b/libraries/KVStore/FileSystemStore.h @@ -0,0 +1,271 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * 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_FILE_SYSTEM_STORE_H +#define MBED_FILE_SYSTEM_STORE_H + +#include "KVStore.h" +#include "FileSystem.h" + +namespace mbed { + +/** FileSystemStore for Secure Store. + * This class implements the KVStore interface to + * create a key value store over FileSystem. + * + * @code + * ... + * @endcode + */ +class FileSystemStore : public KVStore { + +public: + /** Create FileSystemStore - A Key Value API on top of FS + * + * @param fs File system (FAT/LITTLE) on top of which FileSystemStore is adding KV API + */ + FileSystemStore(FileSystem *fs); + + /** Destroy FileSystemStore instance + * + */ + virtual ~FileSystemStore() {} + + /** + * @brief Initialize FileSystemStore, checking validity of + * KVStore writing folder and if it doesn't exist, creating it. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + */ + virtual int init(); + + /** + * @brief Deinitialize FileSystemStore, release and free resources. + * + * @returns KVSTORE_SUCCESS Success. + */ + virtual int deinit(); + + /** + * @brief Reset FileSystemStore contents (clear all keys) + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + */ + virtual int reset(); + + /** + * @brief Set one FileSystemStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); + + /** + * @brief Get one FileSystemStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size. + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupted. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0); + + /** + * @brief Get information of a given key. The returned info contains size and flags + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupted. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get_info(const char *key, info_t *info); + + /** + * @brief Remove a FileSystemStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int remove(const char *key); + + /** + * @brief Start an incremental FileSystemStore set sequence. This operation is blocking other operations. + * Any get/set/remove/iterator operation will be blocked until set_finalize is called. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); + + /** + * @brief Add data to incremental FileSystemStore set sequence. This operation is blocking other operations. + * Any get/set/remove operation will be blocked until set_finalize is called. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data Value data to add. + * @param[in] data_size Value data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); + + /** + * @brief Finalize an incremental FileSystemStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_FAILED_OPERATION Underlying file system failed operation. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int set_finalize(set_handle_t handle); + + /** + * @brief Start an iteration over FileSystemStore keys. + * There are no issues with any other operations while iterator is open. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL); + + /** + * @brief Get next key in iteration. + * There are no issues with any other operations while iterator is open. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_ITEM_NOT_FOUND No more keys found. + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size); + + /** + * @brief Close iteration. + * + * @param[in] it Iterator handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_close(iterator_t it); + +#if !defined(DOXYGEN_ONLY) +private: + + // Key metadata + typedef struct { + uint32_t magic; + uint16_t metadata_size; + uint16_t revision; + uint32_t user_flags; + } key_metadata_t; + + /** + * @brief Build Full name class member from Key, as a combination of FSST folder and key name + * + * @param[in] key_src key file name + * + * @returns 0 on success or a negative error code on failure + */ + int _build_full_path_key(const char *key_src); + + /** + * @brief Verify Key file metadata validity and open it if valid + * + * @param[in] key In validated key file name. + * @param[in] key_metadata Returned key file metadata. + * @param[in] kv_file Opened KV file handle (unless file doesn't exist) + * + * @returns 0 on success or a negative error code on failure + */ + int _verify_key_file(const char *key, key_metadata_t *key_metadata, File *kv_file); + + FileSystem *_fs; + PlatformMutex _mutex; + PlatformMutex _inc_data_add_mutex; + + bool _is_initialized; + char *_cfg_fs_path; /* FileSystemStore path name on FileSystem */ + size_t _cfg_fs_path_size; /* Size of configured FileSystemStore path name on FileSystem */ + char *_full_path_key; /* Full name of Key file currently working on */ + size_t _cur_inc_data_size; /* Amount of data added to Key file so far, during incremental add data */ + set_handle_t _cur_inc_set_handle; /* handle of currently key file under incremental set process */ +#endif +}; + + +} //namespace mbed +#endif //MBED_FILE_SYSTEM_STORE_H diff --git a/libraries/KVStore/KVStore.h b/libraries/KVStore/KVStore.h new file mode 100644 index 00000000..08e0bae6 --- /dev/null +++ b/libraries/KVStore/KVStore.h @@ -0,0 +1,229 @@ +/* + * 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_KVSTORE_H +#define MBED_KVSTORE_H + +#include +#include + +#define KVSTORE_SUCCESS 0 +#define KVSTORE_ERROR_READ_FAILED 283 +#define KVSTORE_ERROR_WRITE_FAILED 284 +#define KVSTORE_ERROR_INVALID_DATA_DETECTED 258 +#define KVSTORE_ERROR_INVALID_SIZE 261 +#define KVSTORE_ERROR_INVALID_ARGUMENT 257 +#define KVSTORE_ERROR_ITEM_NOT_FOUND 263 +#define KVSTORE_ERROR_MEDIA_FULL 267 +#define KVSTORE_ERROR_WRITE_PROTECTED 274 +#define KVSTORE_ERROR_OUT_OF_RESOURCES 288 +#define KVSTORE_ERROR_NOT_READY 270 +#define KVSTORE_ERROR_FAILED_OPERATION 271 + +namespace mbed { + +/** KVStore class + * + * Interface class for Key Value Storage + */ +class KVStore { +public: + enum create_flags { + WRITE_ONCE_FLAG = (1 << 0), + REQUIRE_CONFIDENTIALITY_FLAG = (1 << 1), + RESERVED_FLAG = (1 << 2), + REQUIRE_REPLAY_PROTECTION_FLAG = (1 << 3), + }; + + static const uint32_t MAX_KEY_SIZE = 128; + + typedef struct _opaque_set_handle *set_handle_t; + + typedef struct _opaque_key_iterator *iterator_t; + + /** + * Holds key information + */ + typedef struct info { + /** + * The key size + */ + size_t size; + /* + * The Key flags, possible flags combination: + * WRITE_ONCE_FLAG, + * REQUIRE_CONFIDENTIALITY_FLAG, + * REQUIRE_REPLAY_PROTECTION_FLAG + */ + uint32_t flags; + } info_t; + + virtual ~KVStore() {}; + + /** + * @brief Initialize KVStore + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int init() = 0; + + /** + * @brief Deinitialize KVStore + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int deinit() = 0; + + + /** + * @brief Reset KVStore contents (clear all keys) + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int reset() = 0; + + /** + * @brief Set one KVStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags) = 0; + + /** + * @brief Get one KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size (NULL to pass nothing). + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0) = 0; + + /** + * @brief Get information of a given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure (NULL to pass nothing). + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int get_info(const char *key, info_t *info = NULL) = 0; + + /** + * @brief Remove a KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int remove(const char *key) = 0; + + + /** + * @brief Start an incremental KVStore set sequence. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags) = 0; + + /** + * @brief Add data to incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data Value data to add. + * @param[in] data_size Value data size. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size) = 0; + + /** + * @brief Finalize an incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int set_finalize(set_handle_t handle) = 0; + + /** + * @brief Start an iteration over KVStore keys. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL) = 0; + + /** + * @brief Get next key in iteration. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size) = 0; + + /** + * @brief Close iteration. + * + * @param[in] it Iterator handle. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + virtual int iterator_close(iterator_t it) = 0; + + /** Convenience function for checking key validity. + * Key must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @param[in] key Key buffer. + * + * @returns KVSTORE_SUCCESS on success or an error code on failure + */ + bool is_valid_key(const char *key) const + { + if (!key || !strlen(key) || (strlen(key) > MAX_KEY_SIZE)) { + return false; + } + + if (strpbrk(key, " */?:;\"|<>\\")) { + return false; + } + return true; + } + +}; +/** @}*/ + +} // namespace mbed + +#endif diff --git a/libraries/KVStore/MbedCRC.h b/libraries/KVStore/MbedCRC.h new file mode 100644 index 00000000..ce6b3f53 --- /dev/null +++ b/libraries/KVStore/MbedCRC.h @@ -0,0 +1,892 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * 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_CRC_API_H +#define MBED_CRC_API_H + +// #include "cmsis.h" +// #include "hal/crc_api.h" +#ifdef DEVICE_CRC +#include "device.h" +#endif +// #include "platform/mbed_assert.h" + +#ifdef __cplusplus + +// #include "platform/SingletonPtr.h" +// #include "platform/PlatformMutex.h" + +// #include + +// namespace mbed { +/** \addtogroup drivers-public-api */ +/** @{*/ +/** + * \defgroup drivers_MbedCRC MbedCRC class + * @{ + */ + +// extern SingletonPtr mbed_crc_mutex; + +/** CRC Polynomial value + * + * Different polynomial values supported + */ +typedef enum crc_polynomial { + POLY_7BIT_SD = 0x09, ///< x7+x3+1 + POLY_8BIT_CCITT = 0x07, ///< x8+x2+x+1 + POLY_16BIT_CCITT = 0x1021, ///< x16+x12+x5+1 + POLY_16BIT_IBM = 0x8005, ///< x16+x15+x2+1 + POLY_32BIT_ANSI = 0x04C11DB7 ///< x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 +} crc_polynomial_t; + +/** CRC mode selection + */ +enum class CrcMode { + HARDWARE, /// Use hardware (if available), else table-based computation + TABLE, /// Use table-based computation (if table available), else bitwise + BITWISE /// Always use bitwise manual computation +}; + +#ifndef DOXYGEN_ONLY +namespace impl { +template +class MbedCRC; + +constexpr bool have_crc_table(uint32_t polynomial, uint8_t width) +{ +#if MBED_CRC_TABLE_SIZE > 0 + return (polynomial == POLY_32BIT_ANSI && width == 32) || + (polynomial == POLY_16BIT_IBM && width == 16) || + (polynomial == POLY_16BIT_CCITT && width == 16) || + (polynomial == POLY_8BIT_CCITT && width == 8) || + (polynomial == POLY_7BIT_SD && width == 7); +#else + return false; +#endif +} + +constexpr CrcMode choose_crc_mode(uint32_t polynomial, uint8_t width, CrcMode mode_limit) +{ + return +#if DEVICE_CRC + mode_limit == CrcMode::HARDWARE && HAL_CRC_IS_SUPPORTED(polynomial, width) ? CrcMode::HARDWARE : +#endif + mode_limit <= CrcMode::TABLE && have_crc_table(polynomial, width) ? CrcMode::TABLE : + CrcMode::BITWISE; +} +#endif // DOXYGEN_ONLY + +} // namespace impl + +/** CRC object provides CRC generation through hardware or software + * + * CRC sums can be generated using three different methods: hardware, software ROM tables + * and bitwise computation. The mode used is normally selected automatically based on required + * polynomial and hardware capabilities. Any polynomial in standard form (`x^3 + x + 1`) + * can be used for computation, but custom ones can affect the performance. + * + * First choice is the hardware mode. The supported polynomials are hardware specific, and + * you need to consult your MCU manual to discover them. Next, ROM polynomial tables + * are tried (you can find list of supported polynomials here ::crc_polynomial). If the selected + * configuration is supported, it will accelerate the software computations. If ROM tables + * are not available for the selected polynomial, then CRC is computed at run time bit by bit + * for all data input. + * + * If desired, the mode can be manually limited for a given instance by specifying the mode_limit + * template parameter. This might be appropriate to ensure a table is not pulled in for a + * non-speed-critical CRC, or to avoid the hardware set-up overhead if you know you will be + * calling `compute` with very small data sizes. + * + * @note Synchronization level: Thread safe + * + * @tparam polynomial CRC polynomial value in hex + * @tparam width CRC polynomial width + * @tparam mode_limit Maximum amount of acceleration to use + * + * Example: Compute CRC data + * @code + * + * #include "mbed.h" + * + * int main() { + * MbedCRC ct; + * + * char test[] = "123456789"; + * uint32_t crc = 0; + * + * printf("\nPolynomial = 0x%lx Width = %d \n", ct.get_polynomial(), ct.get_width()); + * + * ct.compute((void *)test, strlen((const char*)test), &crc); + * + * printf("The CRC of data \"123456789\" is : 0x%lx\n", crc); + * return 0; + * } + * @endcode + * Example: Compute CRC with data available in parts + * @code + * + * #include "mbed.h" + * int main() { + * MbedCRC ct; + * + * char test[] = "123456789"; + * uint32_t crc = 0; + * + * printf("\nPolynomial = 0x%lx Width = %d \n", ct.get_polynomial(), ct.get_width()); + * ct.compute_partial_start(&crc); + * ct.compute_partial((void *)&test, 4, &crc); + * ct.compute_partial((void *)&test[4], 5, &crc); + * ct.compute_partial_stop(&crc); + * printf("The CRC of data \"123456789\" is : 0x%lx\n", crc); + * return 0; + * } + * @endcode + */ +template +class MbedCRC { + impl::MbedCRC crc_impl; + +public: + /* Backwards compatibility */ + enum CrcMode { +#if DEVICE_CRC + HARDWARE = int(::CrcMode::HARDWARE), +#endif + TABLE = int(::CrcMode::TABLE), + BITWISE = int(::CrcMode::BITWISE) + }; + + typedef size_t crc_data_size_t; + + /** Lifetime of CRC object + * + * @param initial_xor Initial value/seed to Xor + * @param final_xor Final Xor value + * @param reflect_data + * @param reflect_remainder + * @note Default constructor without any arguments is valid only for supported CRC polynomials. :: crc_polynomial_t + * MbedCRC ct; --- Valid POLY_7BIT_SD + * MbedCRC <0x1021, 16> ct; --- Valid POLY_16BIT_CCITT + * MbedCRC ct; --- Invalid, compilation error + * MbedCRC ct (i,f,rd,rr) Constructor can be used for not supported polynomials + * MbedCRC sd(0, 0, false, false); Constructor can also be used for supported + * polynomials with different initial/final/reflect values + * + */ + constexpr + MbedCRC(uint32_t initial_xor, uint32_t final_xor, bool reflect_data, bool reflect_remainder) : + crc_impl(initial_xor, final_xor, reflect_data, reflect_remainder) + { + } + + /* Default values for different types of polynomials + */ + // *INDENT-OFF* + template = 0> + constexpr MbedCRC() : MbedCRC(0xFFFFFFFF, 0xFFFFFFFF, true, true) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, true, true) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0xFFFF, 0, false, false) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, false, false) + { + } + + template = 0> + constexpr MbedCRC() : MbedCRC(0, 0, false, false) + { + } + // *INDENT-ON* + + /** Compute CRC for the data input + * Compute CRC performs the initialization, computation and collection of + * final CRC. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC is the output value + * @return 0 on success, negative error code on failure + */ + int32_t compute(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + return crc_impl.compute(buffer, size, crc); + } + + /** Compute partial CRC for the data input. + * + * CRC data if not available fully, CRC can be computed in parts with available data. + * + * In case of hardware, intermediate values and states are saved by hardware. Mutex + * locking is used to serialize access to hardware CRC. + * + * In case of software CRC, previous CRC output should be passed as argument to the + * current compute_partial call. Please note the intermediate CRC value is maintained by + * application and not the driver. + * + * @pre: Call `compute_partial_start` to start the partial CRC calculation. + * @post: Call `compute_partial_stop` to get the final CRC value. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC value is intermediate CRC value filled by API. + * @return 0 on success or a negative error code on failure + * @note: CRC as output in compute_partial is not final CRC value, call `compute_partial_stop` + * to get final correct CRC value. + */ + int32_t compute_partial(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + return crc_impl.compute_partial(buffer, size, crc); + } + + /** Compute partial start, indicate start of partial computation. + * + * This API should be called before performing any partial computation + * with compute_partial API. + * + * @param crc Initial CRC value set by the API + * @return 0 on success or a negative in case of failure + * @note: CRC is an out parameter and must be reused with compute_partial + * and `compute_partial_stop` without any modifications in application. + */ + int32_t compute_partial_start(uint32_t *crc) + { + return crc_impl.compute_partial_start(crc); + } + + /** Get the final CRC value of partial computation. + * + * CRC value available in partial computation is not correct CRC, as some + * algorithms require remainder to be reflected and final value to be XORed + * This API is used to perform final computation to get correct CRC value. + * + * @param crc CRC result + * @return 0 on success or a negative in case of failure. + */ + int32_t compute_partial_stop(uint32_t *crc) + { + return crc_impl.compute_partial_stop(crc); + } + + /** Get the current CRC polynomial. + * + * @return Polynomial value + */ + static constexpr uint32_t get_polynomial() + { + return polynomial; + } + + /** Get the current CRC width + * + * @return CRC width + */ + static constexpr uint8_t get_width() + { + return width; + } +}; + +#if !defined(DOXYGEN_ONLY) +/* Internal implementation - basically same as public, but actual mode locked in */ +namespace impl { + +template +class MbedCRC { +public: + typedef size_t crc_data_size_t; + + constexpr + MbedCRC(uint32_t initial_xor, uint32_t final_xor, bool reflect_data, bool reflect_remainder) : + _initial_value(adjust_initial_value(initial_xor, reflect_data)), + _final_xor(final_xor), + _reflect_data(reflect_data), + _reflect_remainder(reflect_remainder) + { + static_assert(width <= 32, "Max 32-bit CRC supported"); + } + + /** Compute CRC for the data input + * Compute CRC performs the initialization, computation and collection of + * final CRC. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC is the output value + * @return 0 on success, negative error code on failure + */ + int32_t compute(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + int32_t status; + + status = compute_partial_start(crc); + if (0 != status) { + return status; + } + + status = compute_partial(buffer, size, crc); + if (0 != status) { + return status; + } + + status = compute_partial_stop(crc); + return status; + } + + /** Compute partial CRC for the data input. + * + * CRC data if not available fully, CRC can be computed in parts with available data. + * + * In case of hardware, intermediate values and states are saved by hardware. Mutex + * locking is used to serialize access to hardware CRC. + * + * In case of software CRC, previous CRC output should be passed as argument to the + * current compute_partial call. Please note the intermediate CRC value is maintained by + * application and not the driver. + * + * @pre: Call `compute_partial_start` to start the partial CRC calculation. + * @post: Call `compute_partial_stop` to get the final CRC value. + * + * @param buffer Data bytes + * @param size Size of data + * @param crc CRC value is intermediate CRC value filled by API. + * @return 0 on success or a negative error code on failure + * @note: CRC as output in compute_partial is not final CRC value, call `compute_partial_stop` + * to get final correct CRC value. + */ + int32_t compute_partial(const void *buffer, crc_data_size_t size, uint32_t *crc) + { + const uint8_t *data = static_cast(buffer); + return do_compute_partial(data, size, crc); + } + + /** Compute partial start, indicate start of partial computation. + * + * This API should be called before performing any partial computation + * with compute_partial API. + * + * @param crc Initial CRC value set by the API + * @return 0 on success or a negative in case of failure + * @note: CRC is an out parameter and must be reused with compute_partial + * and `compute_partial_stop` without any modifications in application. + */ + int32_t compute_partial_start(uint32_t *crc) + { +#if DEVICE_CRC + if (mode == CrcMode::HARDWARE) { + lock(); + crc_mbed_config_t config; + config.polynomial = polynomial; + config.width = width; + config.initial_xor = _initial_value; + config.final_xor = _final_xor; + config.reflect_in = _reflect_data; + config.reflect_out = _reflect_remainder; + + hal_crc_compute_partial_start(&config); + } +#endif + + *crc = _initial_value; + return 0; + } + + /** Get the final CRC value of partial computation. + * + * CRC value available in partial computation is not correct CRC, as some + * algorithms require remainder to be reflected and final value to be XORed + * This API is used to perform final computation to get correct CRC value. + * + * @param crc CRC result + * @return 0 on success or a negative in case of failure. + */ + int32_t compute_partial_stop(uint32_t *crc) + { +#if DEVICE_CRC + if (mode == CrcMode::HARDWARE) { + *crc = hal_crc_get_result(); + unlock(); + return 0; + } +#endif + uint_fast32_t p_crc = *crc; + if (mode == CrcMode::BITWISE) { + if (_reflect_data) { + /* CRC has MSB in bottom bit of register */ + if (!_reflect_remainder) { + p_crc = reflect_crc(p_crc); + } + } else { + /* CRC has MSB in top bit of register */ + p_crc = _reflect_remainder ? reflect(p_crc) : shift_right(p_crc); + } + } else { // TABLE + /* CRC has MSB in bottom bit of register */ + if (!_reflect_remainder) { + p_crc = reflect_crc(p_crc); + } + } + + p_crc ^= _final_xor; + p_crc &= get_crc_mask(); + *crc = p_crc; + + return 0; + } + +private: + /** Guaranteed constexpr reflection (all toolchains) + * + * @note This should never be run-time evaluated - very inefficient + * @param Register value to be reflected (full 32-bit value) + * @return Reflected value (full 32-bit value) + */ + static constexpr uint32_t reflect_constant(uint32_t data) + { + /* Doing this hard way to keep it C++11 constexpr and hence ARM C 5 compatible */ + return ((data & 0x00000001) << 31) | + ((data & 0x00000002) << 29) | + ((data & 0x00000004) << 27) | + ((data & 0x00000008) << 25) | + ((data & 0x00000010) << 23) | + ((data & 0x00000020) << 21) | + ((data & 0x00000040) << 19) | + ((data & 0x00000080) << 17) | + ((data & 0x00000100) << 15) | + ((data & 0x00000200) << 13) | + ((data & 0x00000400) << 11) | + ((data & 0x00000800) << 9) | + ((data & 0x00001000) << 7) | + ((data & 0x00002000) << 5) | + ((data & 0x00004000) << 3) | + ((data & 0x00008000) << 1) | + ((data & 0x00010000) >> 1) | + ((data & 0x00020000) >> 3) | + ((data & 0x00040000) >> 5) | + ((data & 0x00080000) >> 7) | + ((data & 0x00100000) >> 9) | + ((data & 0x00200000) >> 11) | + ((data & 0x00400000) >> 13) | + ((data & 0x00800000) >> 15) | + ((data & 0x01000000) >> 17) | + ((data & 0x02000000) >> 19) | + ((data & 0x04000000) >> 21) | + ((data & 0x08000000) >> 23) | + ((data & 0x10000000) >> 25) | + ((data & 0x20000000) >> 27) | + ((data & 0x40000000) >> 29) | + ((data & 0x80000000) >> 31); + } + + /** General reflection + * + * @note This is used when we may need to perform run-time computation, so + * we need the possibility to produce the optimal run-time RBIT instruction. But + * if the compiler doesn't treat RBIT as a built-in, it's useful to have a C fallback + * for the constant case, avoiding runtime RBIT(0) computations. This is an + * optimization only available for some toolchains; others will always use runtime + * RBIT. If we require a constant expression, use reflect_constant instead. + * + * @param Register value to be reflected (full 32-bit value) + * @return Reflected value (full 32-bit value) + */ +#ifdef MSTD_HAS_IS_CONSTANT_EVALUATED + static constexpr uint32_t reflect(uint32_t data) + { + return mstd::is_constant_evaluated() ? reflect_constant(data) : __RBIT(data); + } +#else + static uint32_t reflect(uint32_t data) + { + return __RBIT(data); + } +#endif + + /** Data bytes may need to be reflected. + * + * @param data value to be reflected (bottom 8 bits) + * @return Reflected value (bottom 8 bits) + */ + static + uint_fast32_t reflect_byte(uint_fast32_t data) + { + return reflect(data) >> 24; + } + + /** Get the current CRC polynomial, reflected at bottom of register. + * + * @return Reflected polynomial value (so x^width term would be at bit -1) + */ + static constexpr uint32_t get_reflected_polynomial() + { + return shift_right(reflect_constant(polynomial)); + } + + /** Get the current CRC polynomial, at top of register. + * + * @return Shifted polynomial value (so x^width term would be at bit 32) + */ + static constexpr uint32_t get_top_polynomial() + { + return shift_left(polynomial); + } + + const uint32_t _initial_value; + const uint32_t _final_xor; + const bool _reflect_data; + const bool _reflect_remainder; + + // *INDENT-OFF* + using crc_table_t = std::conditional_t>; + // *INDENT-ON* + +#if MBED_CRC_TABLE_SIZE > 0 + /* Tables only actually defined for mode == TABLE, and certain polynomials - see below */ + static const crc_table_t _crc_table[MBED_CRC_TABLE_SIZE]; +#endif + + static constexpr uint32_t adjust_initial_value(uint32_t initial_xor, bool reflect_data) + { + if (mode == CrcMode::BITWISE) { + /* For bitwise calculation, CRC register is reflected if data is, to match input. + * (MSB at bottom of register). If not reflected, it is at the top of the register + * (MSB at top of register). + */ + return reflect_data ? reflect_crc(initial_xor) : shift_left(initial_xor); + } else if (mode == CrcMode::TABLE) { + /* For table calculation, CRC value is reflected, to match tables. + * (MSB at bottom of register). */ + return reflect_crc(initial_xor); + } else { // CrcMode::HARDWARE + return initial_xor; + } + } + + /** Acquire exclusive access to CRC hardware/software. + */ + static void lock() + { +// #if DEVICE_CRC +// if (mode == CrcMode::HARDWARE) { +// mbed_crc_mutex->lock(); +// } +// #endif + } + + /** Release exclusive access to CRC hardware/software. + */ + static void unlock() + { +// #if DEVICE_CRC +// if (mode == CrcMode::HARDWARE) { +// mbed_crc_mutex->unlock(); +// } +// #endif + } + + /** Get the CRC data mask. + * + * @return CRC data mask is generated based on current CRC width + */ + static constexpr uint32_t get_crc_mask() + { + return (uint32_t)((uint32_t)2U << (width - 1)) - 1U; + } + + /** CRC values may need to be reflected. + * + * @param CRC value to be reflected (width bits at bottom of 32-bit word) + * @return Reflected value (still at bottom of 32-bit word) + */ + static + uint32_t reflect_crc(uint32_t data) + { + return reflect(data) >> (32 - width); + } + + /** Register values may need to be shifted left. + * + * @param Register value to be shifted up (in bottom width bits) + * @return Shifted value (in top width bits) + */ + static constexpr uint32_t shift_left(uint32_t data) + { + return data << (32 - width); + } + + /** Register values may need to be shifted right. + * + * @param Register value to be shifted right (in top width bits) + * @return Shifted value (in bottom width bits) + */ + static constexpr uint32_t shift_right(uint32_t data) + { + return data >> (32 - width); + } + + /* Check to see if we can do assembler optimizations */ +#if (defined __GNUC__ || defined __clang__) && \ + (defined __arm__ || defined __ARM_ARCH) +#if (__ARM_ARCH_7M__ == 1U) || \ + (__ARM_ARCH_7EM__ == 1U) || \ + (__ARM_ARCH_8M_MAIN__ == 1U) || \ + (__ARM_ARCH_8_1M_MAIN__ == 1U) || \ + (__ARM_ARCH_7A__ == 1U) + /* ARM that has Thumb-2 - same unified assembly is good for either ARM or Thumb state (LSRS; IT CS; EORCS reg/imm) */ +#define MBED_CRC_ARM_THUMB2 1 +#define MBED_CRC_THUMB1 0 +#elif (__ARM_ARCH_6M__ == 1U) || \ + (__ARM_ARCH_8M_BASE__ == 1U) + /* Thumb-1-only ARM-M device - use Thumb-1 compatible assembly with branch (LSRS; BCC; EORS reg) */ +#define MBED_CRC_ARM_THUMB2 0 +#define MBED_CRC_THUMB1 1 +#else // __ARM_ARCH_xxx +#error "Unknown ARM architecture for CRC optimization" +#endif // __ARM_ARCH_xxx +#else // __arm__ || defined __ICC_ARM__ || defined __ARM_ARCH + /* Seem to be compiling for non-ARM, or an unsupported toolchain, so stick with C implementations */ +#define MBED_CRC_ARM_THUMB2 0 +#define MBED_CRC_THUMB1 0 +#endif + + // *INDENT-OFF* + /** Process 1 bit of non-reflected CRC + * + * Shift the p_crc register left 1 bit - if a one is shifted + * out, exclusive-or with the polynomial mask. + * + * Assembler optimizations can be applied here, to make + * use of the CPU's carry output from shifts. + * + * @param p_crc input register value + * @return updated register value + */ + static uint_fast32_t do_1_bit_normal(uint_fast32_t p_crc) + { +#if MBED_CRC_ARM_THUMB2 + __asm(".syntax unified\n\t" + "LSLS" "\t%[p_crc], %[p_crc], #1\n\t" + "IT" "\tCS\n\t" + "EORCS" "\t%[p_crc], %[poly]" + : [p_crc] "+&r" (p_crc) + : [poly] "rI" (get_top_polynomial()) + : "cc"); +#elif MBED_CRC_THUMB1 + __asm(".syntax unified\n\t" + "LSLS" "\t%[p_crc], %[p_crc], #1\n\t" + "BCC" "\t%=f\n\t" + "EORS" "\t%[p_crc], %[poly]\n" + "%=:" + : [p_crc] "+&l" (p_crc) + : [poly] "l" (get_top_polynomial()) + : "cc"); +#else + if (p_crc & 0x80000000) { + p_crc = (p_crc << 1) ^ get_top_polynomial(); + } else { + p_crc = (p_crc << 1); + } +#endif + return p_crc; + } + + /** Process 1 bit of reflected CRC + * + * Shift the p_crc register right 1 bit - if a one is shifted + * out, exclusive-or with the polynomial mask. + * + * Assembler optimizations can be applied here, to make + * use of the CPU's carry output from shifts. + * + * @param p_crc input register value + * @return updated register value + */ + static uint_fast32_t do_1_bit_reflected(uint_fast32_t p_crc) + { +#if MBED_CRC_ARM_THUMB2 + __asm(".syntax unified\n\t" + "LSRS" "\t%[p_crc], %[p_crc], #1\n\t" + "IT" "\tCS\n\t" + "EORCS" "\t%[p_crc], %[poly]" + : [p_crc] "+&r" (p_crc) + : [poly] "rI" (get_reflected_polynomial()) + : "cc"); +#elif MBED_CRC_THUMB1 + __asm(".syntax unified\n\t" + "LSRS" "\t%[p_crc], %[p_crc], #1\n\t" + "BCC" "\t%=f\n\t" + "EORS" "\t%[p_crc], %[poly]\n" + "%=:" + : [p_crc] "+&l" (p_crc) + : [poly] "l" (get_reflected_polynomial()) + : "cc"); +#else + if (p_crc & 1) { + p_crc = (p_crc >> 1) ^ get_reflected_polynomial(); + } else { + p_crc = (p_crc >> 1); + } +#endif + return p_crc; + } + // *INDENT-ON* + + /** Bitwise CRC computation. + * + * @param buffer data buffer + * @param size size of the data + * @param crc CRC value is filled in, but the value is not the final + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *crc) const + { + uint_fast32_t p_crc = *crc; + + if (_reflect_data) { + /* Everything is reflected to match data - MSB of polynomial at bottom of 32-bit register */ + for (crc_data_size_t byte = 0; byte < size; byte++) { + p_crc ^= data[byte]; + + // Perform modulo-2 division, a bit at a time + for (unsigned int bit = 8; bit > 0; --bit) { + p_crc = do_1_bit_reflected(p_crc); + } + } + } else { + /* Polynomial is shifted to put MSB of polynomial at top of 32-bit register */ + for (crc_data_size_t byte = 0; byte < size; byte++) { + p_crc ^= (uint_fast32_t) data[byte] << 24; + + // Perform modulo-2 division, a bit at a time + for (unsigned int bit = 8; bit > 0; --bit) { + p_crc = do_1_bit_normal(p_crc); + } + } + } + + *crc = p_crc; + + return 0; + } + +#if MBED_CRC_TABLE_SIZE > 0 + /** CRC computation using ROM tables. + * + * @param buffer data buffer + * @param size size of the data + * @param crc CRC value is filled in, but the value is not the final + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *crc) const + { + uint_fast32_t p_crc = *crc; + // GCC has been observed to not hoist the load of _reflect_data out of the loop + // Note the inversion because table and CRC are reflected - data must be + bool reflect = !_reflect_data; + + for (crc_data_size_t byte = 0; byte < size; byte++) { + uint_fast32_t data_byte = data[byte]; + if (reflect) { + data_byte = reflect_byte(data_byte); + } +#if MBED_CRC_TABLE_SIZE == 16 + p_crc = _crc_table[(data_byte ^ p_crc) & 0xF] ^ (p_crc >> 4); + data_byte >>= 4; + p_crc = _crc_table[(data_byte ^ p_crc) & 0xF] ^ (p_crc >> 4); +#else + p_crc = _crc_table[(data_byte ^ p_crc) & 0xFF] ^ (p_crc >> 8); +#endif + } + *crc = p_crc; + return 0; + } +#endif + +#ifdef DEVICE_CRC + /** Hardware CRC computation. + * + * @param buffer data buffer + * @param size size of the data + * @return 0 on success or a negative error code on failure + */ + template + std::enable_if_t + do_compute_partial(const uint8_t *data, crc_data_size_t size, uint32_t *) const + { + hal_crc_compute_partial(data, size); + return 0; + } +#endif + +}; + +#if MBED_CRC_TABLE_SIZE > 0 +/* Declarations of the tables we provide. (Not strictly needed, but compilers + * can warn if they see us using the template without a generic definition, so + * let it know we have provided these specialisations.) + */ +template<> +const uint8_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint8_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint16_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint16_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +template<> +const uint32_t MbedCRC::_crc_table[MBED_CRC_TABLE_SIZE]; + +#endif // MBED_CRC_TABLE_SIZE > 0 + +} // namespace impl + +#endif // !defined(DOXYGEN_ONLY) + +/** @}*/ +/** @}*/ + +// } // namespace mbed + +#endif // __cplusplus + +/* Internal helper for mbed_error.c crash recovery */ +#ifdef __cplusplus +extern "C" +#endif +uint32_t mbed_tiny_compute_crc32(const void *data, int datalen); + +#endif diff --git a/libraries/KVStore/SecureStore.cpp b/libraries/KVStore/SecureStore.cpp new file mode 100644 index 00000000..d53005b8 --- /dev/null +++ b/libraries/KVStore/SecureStore.cpp @@ -0,0 +1,891 @@ +/* + * 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 "SecureStore.h" + +#if SECURESTORE_ENABLED + +#include "aes.h" +#include "cmac.h" +// #include "mbedtls/platform.h" +#include "entropy.h" +#include "DeviceKey.h" +#include +#include +#include + +// using namespace mbed; + +// --------------------------------------------------------- Definitions ---------------------------------------------------------- + +static const uint32_t securestore_revision = 1; + +static const uint32_t enc_block_size = 16; +static const uint32_t cmac_size = 16; +static const uint32_t iv_size = 8; +static const uint32_t scratch_buf_size = 256; +static const uint32_t derived_key_size = 16; + +static const char *const enc_prefix = "ENC"; +static const char *const auth_prefix = "AUTH"; + +static const uint32_t security_flags = KVStore::REQUIRE_CONFIDENTIALITY_FLAG | KVStore::REQUIRE_REPLAY_PROTECTION_FLAG; + +namespace { +typedef struct { + uint16_t metadata_size = 0u; + uint16_t revision = 0u; + uint32_t data_size = 0u; + uint32_t create_flags = 0u; + uint8_t iv[iv_size] = { 0u }; +} record_metadata_t; + +// iterator handle +typedef struct { + KVStore::iterator_t underlying_it; +} key_iterator_handle_t; + +} + +// incremental set handle +struct SecureStore::inc_set_handle_t { + record_metadata_t metadata; + char *key = nullptr; + uint32_t offset_in_data = 0u; + uint8_t ctr_buf[enc_block_size] = { 0u }; + mbedtls_aes_context enc_ctx; + mbedtls_cipher_context_t auth_ctx; + KVStore::set_handle_t underlying_handle; +}; + +// -------------------------------------------------- Local Functions Declaration ---------------------------------------------------- + +// -------------------------------------------------- Functions Implementation ---------------------------------------------------- + +int encrypt_decrypt_start(mbedtls_aes_context &enc_aes_ctx, uint8_t *iv, const char *key, + uint8_t *ctr_buf, uint8_t *salt_buf, int salt_buf_size) +{ + DeviceKey &devkey = DeviceKey::get_instance(); + char *salt = reinterpret_cast(salt_buf); + uint8_t encrypt_key[derived_key_size]; + strcpy(salt, enc_prefix); + int pos = strlen(enc_prefix); + strncpy(salt + pos, key, salt_buf_size - pos - 1); + salt_buf[salt_buf_size - 1] = 0; + int os_ret = devkey.generate_derived_key(salt_buf, strlen(salt), encrypt_key, DEVICE_KEY_16BYTE); + if (os_ret) { + return os_ret; + } + + mbedtls_aes_init(&enc_aes_ctx); + mbedtls_aes_setkey_enc(&enc_aes_ctx, encrypt_key, enc_block_size * 8); + + memcpy(ctr_buf, iv, iv_size); + memset(ctr_buf + iv_size, 0, iv_size); + + return 0; +} + +int encrypt_decrypt_data(mbedtls_aes_context &enc_aes_ctx, const uint8_t *in_buf, + uint8_t *out_buf, uint32_t chunk_size, uint8_t *ctr_buf, size_t &aes_offs) +{ + uint8_t stream_block[enc_block_size] = { 0 }; + + return mbedtls_aes_crypt_ctr(&enc_aes_ctx, chunk_size, &aes_offs, ctr_buf, + stream_block, in_buf, out_buf); +} + +int cmac_calc_start(mbedtls_cipher_context_t &auth_ctx, const char *key, uint8_t *salt_buf, int salt_buf_size) +{ + DeviceKey &devkey = DeviceKey::get_instance(); + char *salt = reinterpret_cast(salt_buf); + uint8_t auth_key[derived_key_size]; + strcpy(salt, auth_prefix); + int pos = strlen(auth_prefix); + strncpy(salt + pos, key, salt_buf_size - pos - 1); + salt_buf[salt_buf_size - 1] = 0; + int os_ret = devkey.generate_derived_key(salt_buf, strlen(salt), auth_key, DEVICE_KEY_16BYTE); + if (os_ret) { + return os_ret; + } + + const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); + + mbedtls_cipher_init(&auth_ctx); + + if ((os_ret = mbedtls_cipher_setup(&auth_ctx, cipher_info)) != 0) { + return os_ret; + } + + os_ret = mbedtls_cipher_cmac_starts(&auth_ctx, auth_key, cmac_size * 8); + if (os_ret != 0) { + return os_ret; + } + + return 0; +} + +int cmac_calc_data(mbedtls_cipher_context_t &auth_ctx, const void *input, size_t ilen) +{ + int os_ret; + + os_ret = mbedtls_cipher_cmac_update(&auth_ctx, static_cast(input), ilen); + + return os_ret; +} + +int cmac_calc_finish(mbedtls_cipher_context_t &auth_ctx, uint8_t *output) +{ + int os_ret; + + os_ret = mbedtls_cipher_cmac_finish(&auth_ctx, output); + + return os_ret; +} + + + +// Class member functions + +SecureStore::SecureStore(KVStore *underlying_kv, KVStore *rbp_kv) : + _is_initialized(false), _underlying_kv(underlying_kv), _rbp_kv(rbp_kv), _entropy(0), + _ih(0), _scratch_buf(0) +{ +} + +SecureStore::~SecureStore() +{ + deinit(); +} + + +int SecureStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, + uint32_t create_flags) +{ + int ret, os_ret; + info_t info; + bool enc_started = false, auth_started = false; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + *handle = reinterpret_cast(_ih); + + // Validate internal RBP data + if (_rbp_kv) { + ret = _rbp_kv->get_info(key, &info); + if (ret == KVSTORE_SUCCESS) { + if (info.flags & WRITE_ONCE_FLAG) { + // Trying to re-write a key that is write protected + ret = KVSTORE_ERROR_WRITE_PROTECTED; + goto fail; + } + if (!(create_flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { + // Trying to re-write a key that that has REPLAY_PROTECTION + // with a new key that has this flag not set. + ret = KVSTORE_ERROR_INVALID_ARGUMENT; + goto fail; + } + } else if (ret != KVSTORE_ERROR_ITEM_NOT_FOUND) { + ret = KVSTORE_ERROR_READ_FAILED; + goto fail; + } + } else { + // Only trust external flags, if internal RBP is not in use + ret = _underlying_kv->get(key, &_ih->metadata, sizeof(record_metadata_t)); + if (ret == KVSTORE_SUCCESS) { + // Must not remove RP flag, even though internal RBP KV is not in use. + if (!(create_flags & REQUIRE_REPLAY_PROTECTION_FLAG) && (_ih->metadata.create_flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { + ret = KVSTORE_ERROR_INVALID_ARGUMENT; + goto fail; + } + // Existing key is write protected + if (_ih->metadata.create_flags & WRITE_ONCE_FLAG) { + ret = KVSTORE_ERROR_WRITE_PROTECTED; + goto fail; + } + } + } + + // Fill metadata + _ih->metadata.create_flags = create_flags; + _ih->metadata.data_size = final_data_size; + _ih->metadata.metadata_size = sizeof(record_metadata_t); + _ih->metadata.revision = securestore_revision; + + if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + // generate a new random iv + os_ret = mbedtls_entropy_func(_entropy, _ih->metadata.iv, iv_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + os_ret = encrypt_decrypt_start(_ih->enc_ctx, _ih->metadata.iv, key, _ih->ctr_buf, _scratch_buf, + scratch_buf_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + enc_started = true; + } else { + memset(_ih->metadata.iv, 0, iv_size); + } + + os_ret = cmac_calc_start(_ih->auth_ctx, key, _scratch_buf, scratch_buf_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + auth_started = true; + // Although name is not part of the data, we calculate CMAC on it as well + os_ret = cmac_calc_data(_ih->auth_ctx, key, strlen(key)); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + os_ret = cmac_calc_data(_ih->auth_ctx, &_ih->metadata, sizeof(record_metadata_t)); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + + _ih->offset_in_data = 0; + _ih->key = 0; + + // Should strip security flags from underlying storage + ret = _underlying_kv->set_start(&_ih->underlying_handle, key, + sizeof(record_metadata_t) + final_data_size + cmac_size, + create_flags & ~security_flags); + if (ret) { + goto fail; + } + + ret = _underlying_kv->set_add_data(_ih->underlying_handle, &_ih->metadata, + sizeof(record_metadata_t)); + if (ret) { + goto fail; + } + + if (_rbp_kv && (create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { + _ih->key = new char[strlen(key) + 1]; + strcpy(_ih->key, key); + } + + goto end; + +fail: + if (enc_started) { + mbedtls_aes_free(&_ih->enc_ctx); + } + + if (auth_started) { + mbedtls_cipher_free(&_ih->auth_ctx); + } + + // mark handle as invalid by clearing metadata size field in header + _ih->metadata.metadata_size = 0; + _mutex.unlock(); + +end: + return ret; +} + +int SecureStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size) +{ + size_t aes_offs = 0; + int os_ret, ret = KVSTORE_SUCCESS; + const uint8_t *src_ptr; + + if (reinterpret_cast(handle) != _ih) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (!value_data && data_size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (!_ih->metadata.metadata_size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (_ih->offset_in_data + data_size > _ih->metadata.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + + src_ptr = static_cast(value_data); + while (data_size) { + uint32_t chunk_size; + const uint8_t *dst_ptr; + if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + // In encrypt mode we don't want to allocate a buffer in the size given by the user - + // Encrypt the data chunk by chunk + chunk_size = std::min((uint32_t) data_size, scratch_buf_size); + dst_ptr = _scratch_buf; + os_ret = encrypt_decrypt_data(_ih->enc_ctx, src_ptr, _scratch_buf, + chunk_size, _ih->ctr_buf, aes_offs); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + } else { + chunk_size = data_size; + dst_ptr = static_cast (value_data); + } + + os_ret = cmac_calc_data(_ih->auth_ctx, dst_ptr, chunk_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto fail; + } + + ret = _underlying_kv->set_add_data(_ih->underlying_handle, dst_ptr, chunk_size); + if (ret) { + goto fail; + } + data_size -= chunk_size; + src_ptr += chunk_size; + _ih->offset_in_data += chunk_size; + } + + goto end; + +fail: + if (_ih->key) { + delete[] _ih->key; + } + if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + mbedtls_aes_free(&_ih->enc_ctx); + } + + mbedtls_cipher_free(&_ih->auth_ctx); + + // mark handle as invalid by clearing metadata size field in header + _ih->metadata.metadata_size = 0; + _mutex.unlock(); + +end: + return ret; +} + +int SecureStore::set_finalize(set_handle_t handle) +{ + int os_ret, ret = KVSTORE_SUCCESS; + uint8_t cmac[cmac_size] = {0}; + + if (reinterpret_cast(handle) != _ih) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (!_ih->metadata.metadata_size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (_ih->offset_in_data != _ih->metadata.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + + os_ret = cmac_calc_finish(_ih->auth_ctx, cmac); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + + ret = _underlying_kv->set_add_data(_ih->underlying_handle, cmac, cmac_size); + if (ret) { + goto end; + } + + ret = _underlying_kv->set_finalize(_ih->underlying_handle); + if (ret) { + goto end; + } + + if (_rbp_kv && (_ih->metadata.create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { + // In rollback protect case, we need to store CMAC in RBP store. + // If it's also write once case, set write once flag in the RBP key as well. + // Use RBP storage also in write once case only - in order to prevent attacks removing + // a written once value from underlying KV. + ret = _rbp_kv->set(_ih->key, cmac, cmac_size, _ih->metadata.create_flags & WRITE_ONCE_FLAG); + delete[] _ih->key; + if (ret) { + goto end; + } + } + +end: + // mark handle as invalid by clearing metadata size field in header + _ih->metadata.metadata_size = 0; + if (_ih->metadata.create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + mbedtls_aes_free(&_ih->enc_ctx); + } + + mbedtls_cipher_free(&_ih->auth_ctx); + + _mutex.unlock(); + return ret; +} + +int SecureStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags) +{ + int ret; + set_handle_t handle; + + // Don't wait till we get to set_add_data to catch this + if (!buffer && size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + ret = set_start(&handle, key, size, create_flags); + if (ret) { + return ret; + } + + ret = set_add_data(handle, buffer, size); + if (ret) { + return ret; + } + + ret = set_finalize(handle); + return ret; +} + +int SecureStore::remove(const char *key) +{ + info_t info; + _mutex.lock(); + + int ret = do_get(key, 0, 0, 0, 0, &info); + // Allow deleting key if read error is of our own errors + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_AUTHENTICATION_FAILED) && + (ret != KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED)) { + goto end; + } + + if (ret == 0 && info.flags & WRITE_ONCE_FLAG) { + ret = KVSTORE_ERROR_WRITE_PROTECTED; + goto end; + } + + ret = _underlying_kv->remove(key); + if (ret) { + goto end; + } + + if (_rbp_kv && (info.flags & REQUIRE_REPLAY_PROTECTION_FLAG)) { + ret = _rbp_kv->remove(key); + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_ITEM_NOT_FOUND)) { + goto end; + } + } + + ret = KVSTORE_SUCCESS; + +end: + _mutex.unlock(); + return ret; +} + +int SecureStore::do_get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, + size_t offset, info_t *info) +{ + int os_ret, ret; + bool rbp_key_exists = false; + uint8_t rbp_cmac[cmac_size]; + size_t aes_offs = 0; + uint32_t data_size; + uint32_t actual_data_size; + uint32_t current_offset; + uint32_t chunk_size; + uint32_t enc_lead_size; + uint8_t *dest_buf; + bool enc_started = false, auth_started = false; + uint32_t create_flags; + size_t read_len; + info_t rbp_info; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (_rbp_kv) { + ret = _rbp_kv->get_info(key, &rbp_info); + if (ret == KVSTORE_SUCCESS) { + rbp_key_exists = true; + ret = _rbp_kv->get(key, rbp_cmac, cmac_size, &read_len); + if (ret) { + goto end; + } + if ((read_len != cmac_size) || (rbp_info.size != cmac_size)) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + } + } else if (ret != KVSTORE_ERROR_ITEM_NOT_FOUND) { + goto end; + } + } + + ret = _underlying_kv->get(key, &_ih->metadata, sizeof(record_metadata_t), &read_len); + if (ret) { + // In case we have the key in the RBP KV, then even if the key wasn't found in + // the underlying KV, we may have been exposed to an attack. Return an RBP authentication error. + if (rbp_key_exists) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + } + goto end; + } + + // Validate header size + if ((read_len != sizeof(record_metadata_t)) || (_ih->metadata.metadata_size != sizeof(record_metadata_t))) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + goto end; + } + + create_flags = _ih->metadata.create_flags; + if (!_rbp_kv) { + create_flags &= ~REQUIRE_REPLAY_PROTECTION_FLAG; + } + + // Another potential attack case - key hasn't got the RP flag set, but exists in the RBP KV + if (rbp_key_exists && !(create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + goto end; + } + + os_ret = cmac_calc_start(_ih->auth_ctx, key, _scratch_buf, scratch_buf_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + auth_started = true; + + // Although name is not part of the data, we calculate CMAC on it as well + os_ret = cmac_calc_data(_ih->auth_ctx, key, strlen(key)); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + os_ret = cmac_calc_data(_ih->auth_ctx, &_ih->metadata, sizeof(record_metadata_t)); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + + if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + os_ret = encrypt_decrypt_start(_ih->enc_ctx, _ih->metadata.iv, key, _ih->ctr_buf, _scratch_buf, + scratch_buf_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + enc_started = true; + } + + data_size = _ih->metadata.data_size; + actual_data_size = std::min((uint32_t) buffer_size, data_size - offset); + current_offset = 0; + enc_lead_size = 0; + + while (data_size) { + // Make sure we read to the user buffer only between offset and offset + actual_data_size + if ((current_offset >= offset) && (current_offset < offset + actual_data_size)) { + dest_buf = (static_cast (buffer)) + enc_lead_size; + chunk_size = actual_data_size - enc_lead_size; + enc_lead_size = 0; + } else { + dest_buf = _scratch_buf; + if (current_offset < offset) { + chunk_size = std::min(scratch_buf_size, offset - current_offset); + // A special case: encrypted user data starts at a middle of an encryption block. + // In this case, we need to read entire block into our scratch buffer, and copy + // the encrypted lead size to the user buffer start + if ((create_flags & REQUIRE_CONFIDENTIALITY_FLAG) && + (chunk_size % enc_block_size)) { + enc_lead_size = std::min(enc_block_size - chunk_size % enc_block_size, actual_data_size); + chunk_size += enc_lead_size; + } + } else { + chunk_size = std::min(scratch_buf_size, data_size); + enc_lead_size = 0; + } + } + + ret = _underlying_kv->get(key, dest_buf, chunk_size, 0, + _ih->metadata.metadata_size + current_offset); + if (ret != KVSTORE_SUCCESS) { + goto end; + } + + os_ret = cmac_calc_data(_ih->auth_ctx, dest_buf, chunk_size); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + + if (create_flags & REQUIRE_CONFIDENTIALITY_FLAG) { + // Decrypt data in place + os_ret = encrypt_decrypt_data(_ih->enc_ctx, dest_buf, dest_buf, chunk_size, _ih->ctr_buf, + aes_offs); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + + if (enc_lead_size) { + // Now copy decrypted lead size to user buffer start + memcpy(buffer, dest_buf + chunk_size - enc_lead_size, enc_lead_size); + } + } + + current_offset += chunk_size; + data_size -= chunk_size; + } + + if (actual_size) { + *actual_size = actual_data_size; + } + + uint8_t calc_cmac[cmac_size], read_cmac[cmac_size]; + os_ret = cmac_calc_finish(_ih->auth_ctx, calc_cmac); + if (os_ret) { + ret = KVSTORE_ERROR_FAILED_OPERATION; + goto end; + } + + // Check with record CMAC + ret = _underlying_kv->get(key, read_cmac, cmac_size, 0, + _ih->metadata.metadata_size + _ih->metadata.data_size); + if (ret) { + goto end; + } + if (memcmp(calc_cmac, read_cmac, cmac_size) != 0) { + ret = KVSTORE_ERROR_AUTHENTICATION_FAILED; + goto end; + } + + // If rollback protect, check also CMAC stored in RBP store + if (_rbp_kv && (create_flags & (REQUIRE_REPLAY_PROTECTION_FLAG | WRITE_ONCE_FLAG))) { + if (!rbp_key_exists) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + goto end; + } + if (memcmp(calc_cmac, rbp_cmac, cmac_size) != 0) { + ret = KVSTORE_ERROR_RBP_AUTHENTICATION_FAILED; + goto end; + } + } + + if (info) { + info->flags = _ih->metadata.create_flags; + info->size = _ih->metadata.data_size; + } + +end: + _ih->metadata.metadata_size = 0; + + if (enc_started) { + mbedtls_aes_free(&_ih->enc_ctx); + } + + if (auth_started) { + mbedtls_cipher_free(&_ih->auth_ctx); + } + + return ret; +} + +int SecureStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, + size_t offset) +{ + _mutex.lock(); + int ret = do_get(key, buffer, buffer_size, actual_size, offset); + _mutex.unlock(); + + return ret; +} + +int SecureStore::get_info(const char *key, info_t *info) +{ + _mutex.lock(); + int ret = do_get(key, 0, 0, 0, 0, info); + _mutex.unlock(); + + return ret; +} + + +int SecureStore::init() +{ + int ret = KVSTORE_SUCCESS; + + MBED_ASSERT(!(scratch_buf_size % enc_block_size)); + if (scratch_buf_size % enc_block_size) { + return MBED_SYSTEM_ERROR_BASE; + } + + _mutex.lock(); +#if defined(MBEDTLS_PLATFORM_C) + ret = mbedtls_platform_setup(NULL); + if (ret) { + goto fail; + } +#endif /* MBEDTLS_PLATFORM_C */ + + _entropy = new mbedtls_entropy_context; + mbedtls_entropy_init(_entropy); + + _scratch_buf = new uint8_t[scratch_buf_size]; + _ih = new inc_set_handle_t; + + ret = _underlying_kv->init(); + if (ret) { + goto fail; + } + + if (_rbp_kv) { + ret = _rbp_kv->init(); + if (ret) { + goto fail; + } + } + + _is_initialized = true; + +fail: + _mutex.unlock(); + return ret; +} + +int SecureStore::deinit() +{ + _mutex.lock(); + int ret; + if (_is_initialized) { + if (_entropy) { + mbedtls_entropy_free(_entropy); + delete _entropy; + delete _ih; + delete[] _scratch_buf; + _entropy = nullptr; + } + ret = _underlying_kv->deinit(); + if (ret) { + goto END; + } + if (_rbp_kv) { + ret = _rbp_kv->deinit(); + if (ret) { + goto END; + } + } + } + + _is_initialized = false; +#if defined(MBEDTLS_PLATFORM_C) + mbedtls_platform_teardown(NULL); +#endif /* MBEDTLS_PLATFORM_C */ + ret = KVSTORE_SUCCESS; +END: + _mutex.unlock(); + + return ret; +} + + +int SecureStore::reset() +{ + int ret; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + ret = _underlying_kv->reset(); + if (ret) { + goto end; + } + + if (_rbp_kv) { + ret = _rbp_kv->reset(); + if (ret) { + goto end; + } + } + +end: + _mutex.unlock(); + return ret; +} + +int SecureStore::iterator_open(iterator_t *it, const char *prefix) +{ + key_iterator_handle_t *handle; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + if (!it) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + handle = new key_iterator_handle_t; + *it = reinterpret_cast(handle); + + return _underlying_kv->iterator_open(&handle->underlying_it, prefix); +} + +int SecureStore::iterator_next(iterator_t it, char *key, size_t key_size) +{ + key_iterator_handle_t *handle; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + handle = reinterpret_cast(it); + + return _underlying_kv->iterator_next(handle->underlying_it, key, key_size); +} + +int SecureStore::iterator_close(iterator_t it) +{ + key_iterator_handle_t *handle; + int ret; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + handle = reinterpret_cast(it); + + ret = _underlying_kv->iterator_close(handle->underlying_it); + + delete handle; + + return ret; +} + +#endif diff --git a/libraries/KVStore/SecureStore.h b/libraries/KVStore/SecureStore.h new file mode 100644 index 00000000..509b9f4c --- /dev/null +++ b/libraries/KVStore/SecureStore.h @@ -0,0 +1,304 @@ +/* + * 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_SECURESTORE_H +#define MBED_SECURESTORE_H + +#if !defined(MBEDTLS_CONFIG_FILE) +// #include "mbedtls/config.h" +#else +#include MBEDTLS_CONFIG_FILE +#endif + +// #include "device_key/DeviceKey.h" + +#define SECURESTORE_ENABLED 1 + +// Whole class is not supported if entropy, device key or required mbed TLS features are not enabled +#if !defined(MBEDTLS_ENTROPY_C) || !defined(MBEDTLS_CIPHER_MODE_CTR) || !defined(MBEDTLS_CMAC_C) || !DEVICEKEY_ENABLED +#undef SECURESTORE_ENABLED +#define SECURESTORE_ENABLED 0 +#endif + +#if SECURESTORE_ENABLED || defined(DOXYGEN_ONLY) + +#include +#include "KVStore.h" + +// Forward declarations +struct mbedtls_entropy_context; + +// namespace mbed { + +/** TDBStore class + * + * Lightweight Key Value storage over a block device + */ + +class SecureStore : public KVStore { +public: + + /** + * @brief Class constructor + * + * @param[in] underlying_kv KVStore that will hold the data. + * @param[in] rbp_kv Additional KVStore used for rollback protection. + * + * @returns none + */ + SecureStore(KVStore *underlying_kv, KVStore *rbp_kv = 0); + + /** + * @brief Class destructor + * + * @returns none + */ + virtual ~SecureStore(); + + /** + * @brief Initialize SecureStore class. It will also initialize + * the underlying KVStore and the rollback protection KVStore. + * + * @returns KVSTORE_SUCCESS Success. + * or any other error from underlying KVStore instances. + */ + virtual int init(); + + /** + * @brief Deinitialize SecureStore class, free handles and memory allocations. + * + * @returns KVSTORE_SUCCESS Success. + * or any other error from underlying KVStore instances. + */ + virtual int deinit(); + + + /** + * @brief Reset KVStore contents (clear all keys) + * Warning: This function is not thread safe. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * or any other error from underlying KVStore instances. + */ + virtual int reset(); + + /** + * @brief Set one KVStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask - WRITE_ONCE_FLAG|REQUIRE_CONFIDENTIALITY_FLAG| + * REQUIRE_INTEGRITY_FLAG|REQUIRE_REPLAY_PROTECTION_FLAG + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * or any other error from underlying KVStore instances. + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); + + /** + * @brief Get one KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size. + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + * KVSTORE_ERROR_AUTHENTICATION_FAILED Data authentication failed. + * KVSTORE_ERROR_AUTHENTICATION_RBP_FAILED + * Rollback protection data authentication failed. + * or any other error from underlying KVStore instances. + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, + size_t offset = 0); + + /** + * @brief Get information of a given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure containing size and flags. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + * KVSTORE_ERROR_AUTHENTICATION_FAILED Data authentication failed. + * KVSTORE_ERROR_AUTHENTICATION_RBP_FAILED + * Rollback protection data authentication failed. + * or any other error from underlying KVStore instances. + */ + virtual int get_info(const char *key, info_t *info); + + /** + * @brief Remove a KVStore item, given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * or any other error from underlying KVStore instances. + */ + virtual int remove(const char *key); + + + /** + * @brief Start an incremental KVStore set sequence. This operation is blocking other operations. + * Any get/set/remove/iterator operation will be blocked until set_finalize is called. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask - WRITE_ONCE_FLAG|REQUIRE_CONFIDENTIALITY_FLAG| + * REQUIRE_INTEGRITY_FLAG|REQUIRE_REPLAY_PROTECTION_FLAG + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * or any other error from underlying KVStore instances. + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); + + /** + * @brief Add data to incremental KVStore set sequence. This operation is blocking other operations. + * Any get/set/remove operation will be blocked until set_finalize is called. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data value data to add. + * @param[in] data_size value data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * or any other error from underlying KVStore instances. + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); + + /** + * @brief Finalize an incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_FAILED_OPERATION Internal error. + * or any other error from underlying KVStore instances. + */ + virtual int set_finalize(set_handle_t handle); + + /** + * @brief Start an iteration over KVStore keys. + * There are no issue with any other operation while iterator is open. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * or any other error from underlying KVStore instances. + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL); + + /** + * @brief Get next key in iteration. + * There are no issue with any other operation while iterator is open. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * or any other error from underlying KVStore instances. + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size); + + /** + * @brief Close iteration. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * or any other error from underlying KVStore instances. + * + * @returns 0 on success or a negative error code on failure + */ + virtual int iterator_close(iterator_t it); + +#if !defined(DOXYGEN_ONLY) +private: + // Forward declaration + struct inc_set_handle_t; + + PlatformMutex _mutex; + bool _is_initialized; + KVStore *_underlying_kv, *_rbp_kv; + mbedtls_entropy_context *_entropy; + inc_set_handle_t *_ih; + uint8_t *_scratch_buf; + + /** + * @brief Actual get function, serving get and get_info APIs. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size. + * @param[in] offset Offset to read from in data. + * @param[out] info Returned information structure. + * + * @returns 0 on success or a negative error code on failure + */ + int do_get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, + size_t offset = 0, info_t *info = 0); +#endif +}; +/** @}*/ + +// } // namespace mbed + +#endif +#endif diff --git a/libraries/KVStore/TDBStore.cpp b/libraries/KVStore/TDBStore.cpp new file mode 100644 index 00000000..ab28ec12 --- /dev/null +++ b/libraries/KVStore/TDBStore.cpp @@ -0,0 +1,1512 @@ +/* + * 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 "TDBStore.h" + +#include +#include +#include +#include "MbedCRC.h" + +using namespace mbed; + +// --------------------------------------------------------- Definitions ---------------------------------------------------------- + +static const uint32_t delete_flag = (1UL << 31); +static const uint32_t internal_flags = delete_flag; +// Only write once flag is supported, other two are kept in storage but ignored +static const uint32_t supported_flags = KVStore::WRITE_ONCE_FLAG | KVStore::REQUIRE_CONFIDENTIALITY_FLAG | KVStore::REQUIRE_REPLAY_PROTECTION_FLAG; + +namespace { + +typedef struct { + uint32_t magic; + uint16_t header_size; + uint16_t revision; + uint32_t flags; + uint16_t key_size; + uint16_t reserved; + uint32_t data_size; + uint32_t crc; +} record_header_t; + +typedef struct { + uint32_t hash; + bd_size_t bd_offset; +} ram_table_entry_t; + +static const char *master_rec_key = "TDBS"; +static const uint32_t tdbstore_magic = 0x54686683; +static const uint32_t tdbstore_revision = 1; + +typedef struct { + uint16_t version; + uint16_t tdbstore_revision; + uint32_t reserved; +} master_record_data_t; + +typedef enum { + TDBSTORE_AREA_STATE_NONE = 0, + TDBSTORE_AREA_STATE_ERASED, + TDBSTORE_AREA_STATE_INVALID, + TDBSTORE_AREA_STATE_VALID, +} area_state_e; + +typedef struct { + uint16_t trailer_size; + uint16_t data_size; + uint32_t crc; +} reserved_trailer_t; + +static const size_t min_work_buf_size = 64; +static const uint32_t initial_crc = 0xFFFFFFFF; +static const uint32_t initial_max_keys = 16; + +// incremental set handle +typedef struct { + record_header_t header; + bd_size_t bd_base_offset; + bd_size_t bd_curr_offset; + uint32_t offset_in_data; + uint32_t ram_table_ind; + uint32_t hash; + bool new_key; +} inc_set_handle_t; + +// iterator handle +typedef struct { + int iterator_num; + uint32_t ram_table_ind; + char *prefix; +} key_iterator_handle_t; + +} // anonymous namespace + + +// -------------------------------------------------- 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 align_down(uint64_t val, uint64_t size) +{ + return (((val) / size)) * size; +} + + +static uint32_t calc_crc(uint32_t init_crc, uint32_t data_size, const void *data_buf) +{ + uint32_t crc; + MbedCRC ct(init_crc, 0x0, true, false); + ct.compute(data_buf, data_size, &crc); + return crc; +} + +// Class member functions + +TDBStore::TDBStore(BlockDevice *bd) : _ram_table(0), _max_keys(0), + _num_keys(0), _bd(bd), _buff_bd(0), _free_space_offset(0), _master_record_offset(0), + _master_record_size(0), _is_initialized(false), _active_area(0), _active_area_version(0), _size(0), + _area_params{}, _prog_size(0), _work_buf(0), _work_buf_size(0), _key_buf(0), _inc_set_handle(0) +{ + for (int i = 0; i < _num_areas; i++) { + _area_params[i] = { 0 }; + } + for (int i = 0; i < _max_open_iterators; i++) { + _iterator_table[i] = { 0 }; + } +} + +TDBStore::~TDBStore() +{ + deinit(); +} + +int TDBStore::read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf) +{ + //Check that we are not crossing area boundary + if (offset + size > _size) { + return KVSTORE_ERROR_READ_FAILED; + } + int os_ret = _buff_bd->read(buf, _area_params[area].address + offset, size); + + if (os_ret) { + return KVSTORE_ERROR_READ_FAILED; + } + + return KVSTORE_SUCCESS; +} + +int TDBStore::write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf) +{ + int os_ret = _buff_bd->program(buf, _area_params[area].address + offset, size); + if (os_ret) { + return KVSTORE_ERROR_WRITE_FAILED; + } + + return KVSTORE_SUCCESS; +} + +int TDBStore::erase_area(uint8_t area, uint32_t offset, uint32_t size) +{ + uint32_t bd_offset = _area_params[area].address + offset; + + int ret = _buff_bd->erase(bd_offset, size); + if (ret) { + return ret; + } + + if (_buff_bd->get_erase_value() == -1) { + // We need to simulate erase to wipe records, as our block device + // may not do it. Program in chunks of _work_buf_size if the minimum + // program size is too small (e.g. one-byte) to avoid performance + // issues. + MBED_ASSERT(_work_buf != nullptr); + MBED_ASSERT(_work_buf_size != 0); + memset(_work_buf, 0xFF, _work_buf_size); + while (size) { + uint32_t chunk = std::min(_work_buf_size, size); + ret = _buff_bd->program(_work_buf, bd_offset, chunk); + if (ret) { + return ret; + } + size -= chunk; + bd_offset += chunk; + } + } + return KVSTORE_SUCCESS; +} + +void TDBStore::calc_area_params() +{ + // TDBStore can't exceed 32 bits + bd_size_t bd_size = std::min(_bd->size(), 0x80000000L); + + memset(_area_params, 0, sizeof(_area_params)); + size_t area_0_size = 0; + size_t area_1_size = 0; + + // The size calculations are a bit complex because we need to make sure we're + // always aligned to an erase block boundary (whose size may not be uniform + // across the address space), and we also need to make sure that the first + // area never goes over half of the total size even if bd_size is an odd + // number of sectors. + while (true) { + bd_size_t erase_unit_size = _bd->get_erase_size(area_0_size); + if (area_0_size + erase_unit_size <= (bd_size / 2)) { + area_0_size += erase_unit_size; + } else { + break; + } + } + + while (true) { + bd_size_t erase_unit_size = _bd->get_erase_size(area_0_size + area_1_size); + if (area_1_size + erase_unit_size <= (bd_size / 2)) { + area_1_size += erase_unit_size; + } else { + break; + } + } + + _area_params[0].address = 0; + _area_params[0].size = area_0_size; + _area_params[1].address = area_0_size; + _area_params[1].size = area_1_size; + + // The areas must be of same size + MBED_ASSERT(_area_params[0].size == _area_params[1].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) +int TDBStore::read_record(uint8_t area, uint32_t offset, char *key, + void *data_buf, uint32_t data_buf_size, + uint32_t &actual_data_size, size_t data_offset, bool copy_key, + bool copy_data, bool check_expected_key, bool calc_hash, + uint32_t &hash, uint32_t &flags, uint32_t &next_offset) +{ + int ret; + record_header_t header; + uint32_t total_size, key_size, data_size; + uint32_t curr_data_offset; + char *user_key_ptr; + uint32_t crc = initial_crc; + // Upper layers typically use non zero offsets for reading the records chunk by chunk, + // so only validate entire record at first chunk (otherwise we'll have a serious performance penalty). + bool validate = (data_offset == 0); + + ret = KVSTORE_SUCCESS; + // next offset should only be updated to the end of record if successful + next_offset = offset; + + ret = read_area(area, offset, sizeof(header), &header); + if (ret) { + return ret; + } + + if (header.magic != tdbstore_magic) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + offset += align_up(sizeof(header), _prog_size); + + key_size = header.key_size; + data_size = header.data_size; + flags = header.flags; + + if ((!key_size) || (key_size >= MAX_KEY_SIZE)) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + total_size = key_size + data_size; + + // Make sure our read sizes didn't cause any wraparounds + if ((total_size < key_size) || (total_size < data_size)) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + if (offset + total_size >= _size) { + return KVSTORE_ERROR_INVALID_DATA_DETECTED; + } + + if (data_offset > data_size) { + return KVSTORE_ERROR_INVALID_SIZE; + } + + actual_data_size = std::min(data_buf_size, data_size - data_offset); + + if (copy_data && actual_data_size && !data_buf) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (validate) { + // Calculate CRC on header (excluding CRC itself) + crc = calc_crc(crc, sizeof(record_header_t) - sizeof(crc), &header); + curr_data_offset = 0; + } else { + // Non validation case: No need to read the key, nor the parts before data_offset + // or after the actual part requested by the user. + total_size = actual_data_size; + curr_data_offset = data_offset; + offset += data_offset + key_size; + // Mark code that key handling is finished + key_size = 0; + } + + user_key_ptr = key; + hash = initial_crc; + + while (total_size) { + uint8_t *dest_buf; + uint32_t chunk_size; + if (key_size) { + // This means that we're on the key part + if (copy_key) { + dest_buf = reinterpret_cast(user_key_ptr); + chunk_size = key_size; + user_key_ptr[key_size] = '\0'; + } else { + dest_buf = _work_buf; + chunk_size = std::min(key_size, _work_buf_size); + } + } else { + // This means that we're on the data part + // We have four cases that need different handling: + // 1. Before data_offset - read to work buffer + // 2. After data_offset, but before actual part is finished - read to user buffer + // 3. After actual part is finished - read to work buffer + // 4. Copy data flag not set - read to work buffer + if (curr_data_offset < data_offset) { + chunk_size = std::min(_work_buf_size, (data_offset - curr_data_offset)); + dest_buf = _work_buf; + } else if (copy_data && (curr_data_offset < data_offset + actual_data_size)) { + chunk_size = actual_data_size; + dest_buf = static_cast(data_buf); + } else { + chunk_size = std::min(_work_buf_size, total_size); + dest_buf = _work_buf; + } + } + ret = read_area(area, offset, chunk_size, dest_buf); + if (ret) { + goto end; + } + + if (validate) { + // calculate CRC on current read chunk + crc = calc_crc(crc, chunk_size, dest_buf); + } + + if (key_size) { + // We're on key part. May need to calculate hash or check whether key is the expected one + if (check_expected_key) { + if (memcmp(user_key_ptr, dest_buf, chunk_size)) { + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + } + } + + if (calc_hash) { + hash = calc_crc(hash, chunk_size, dest_buf); +#ifdef KVSTORE_RAM_TABLE_NO_CRC_CHECK + next_offset = align_up(offset + total_size, _prog_size); + return ret; +#endif /* KVSTORE_RAM_TABLE_NO_CRC_CHECK */ + } + + user_key_ptr += chunk_size; + key_size -= chunk_size; + if (!key_size) { + offset += data_offset; + } + } else { + curr_data_offset += chunk_size; + } + + total_size -= chunk_size; + offset += chunk_size; + } + + if (validate && (crc != header.crc)) { + ret = KVSTORE_ERROR_INVALID_DATA_DETECTED; + goto end; + } + + next_offset = align_up(offset, _prog_size); + +end: + return ret; +} + +int TDBStore::find_record(uint8_t area, const char *key, uint32_t &offset, + uint32_t &ram_table_ind, uint32_t &hash) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + int ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + uint32_t actual_data_size; + uint32_t flags, dummy_hash, next_offset; + + + hash = calc_crc(initial_crc, strlen(key), key); + + for (ram_table_ind = 0; ram_table_ind < _num_keys; ram_table_ind++) { + entry = &ram_table[ram_table_ind]; + offset = entry->bd_offset; + if (hash < entry->hash) { + continue; + } + if (hash > entry->hash) { + return KVSTORE_ERROR_ITEM_NOT_FOUND; + } + ret = read_record(_active_area, offset, const_cast(key), 0, 0, actual_data_size, 0, + false, false, true, false, dummy_hash, flags, next_offset); + // not found return code here means that hash doesn't belong to name. Continue searching. + if (ret != KVSTORE_ERROR_ITEM_NOT_FOUND) { + break; + } + } + + return ret; +} + +uint32_t TDBStore::record_size(const char *key, uint32_t data_size) +{ + return align_up(sizeof(record_header_t), _prog_size) + + align_up(strlen(key) + data_size, _prog_size); +} + + +int TDBStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, + uint32_t create_flags) +{ + int ret; + uint32_t offset = 0; + uint32_t hash = 0, ram_table_ind = 0; + inc_set_handle_t *ih; + bool need_gc = false; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (create_flags & ~(supported_flags | internal_flags)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + *handle = reinterpret_cast(_inc_set_handle); + ih = reinterpret_cast(*handle); + + if (!strcmp(key, master_rec_key)) { + // Master record - special case (no need to protect by the mutex, as it is already covered + // in the upper layers). + ih->bd_base_offset = _master_record_offset; + ih->new_key = false; + ram_table_ind = 0; + hash = 0; + } else { + + _mutex.lock(); + + // A valid magic in the header means that this function has been called after an aborted + // incremental set process. This means that our media may be in a bad state - call GC. + if (ih->header.magic == tdbstore_magic) { + ret = garbage_collection(); + if (ret) { + goto fail; + } + } + + // If we have no room for the record, perform garbage collection + uint32_t rec_size = record_size(key, final_data_size); + if (_free_space_offset + rec_size > _size) { + ret = garbage_collection(); + if (ret) { + goto fail; + } + } + + // If even after GC we have no room for the record, return error + if (_free_space_offset + rec_size > _size) { + ret = KVSTORE_ERROR_MEDIA_FULL; + goto fail; + } + + ret = find_record(_active_area, key, offset, ram_table_ind, hash); + + if (ret == KVSTORE_SUCCESS) { + ret = read_area(_active_area, offset, sizeof(ih->header), &ih->header); + if (ret) { + goto fail; + } + if (ih->header.flags & WRITE_ONCE_FLAG) { + ret = KVSTORE_ERROR_WRITE_PROTECTED; + goto fail; + } + ih->new_key = false; + } else if (ret == KVSTORE_ERROR_ITEM_NOT_FOUND) { + if (create_flags & delete_flag) { + goto fail; + } + if (_num_keys >= _max_keys) { + increment_max_keys(); + } + ih->new_key = true; + } else { + goto fail; + } + ih->bd_base_offset = _free_space_offset; + + check_erase_before_write(_active_area, ih->bd_base_offset, rec_size); + } + + ret = KVSTORE_SUCCESS; + + // Fill handle and header fields + // Jump to offset after header (header will be written at finalize phase) + ih->bd_curr_offset = ih->bd_base_offset + align_up(sizeof(record_header_t), _prog_size); + ih->offset_in_data = 0; + ih->hash = hash; + ih->ram_table_ind = ram_table_ind; + ih->header.magic = tdbstore_magic; + ih->header.header_size = sizeof(record_header_t); + ih->header.revision = tdbstore_revision; + ih->header.flags = create_flags; + ih->header.key_size = strlen(key); + ih->header.reserved = 0; + ih->header.data_size = final_data_size; + // Calculate CRC on header and key + ih->header.crc = calc_crc(initial_crc, sizeof(record_header_t) - sizeof(ih->header.crc), &ih->header); + ih->header.crc = calc_crc(ih->header.crc, ih->header.key_size, key); + + // Write key now + ret = write_area(_active_area, ih->bd_curr_offset, ih->header.key_size, key); + if (ret) { + need_gc = true; + goto fail; + } + ih->bd_curr_offset += ih->header.key_size; + goto end; + +fail: + if ((need_gc) && (ih->bd_base_offset != _master_record_offset)) { + garbage_collection(); + } + // mark handle as invalid by clearing magic field in header + ih->header.magic = 0; + + _mutex.unlock(); + +end: + return ret; +} + +int TDBStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size) +{ + int ret = KVSTORE_SUCCESS; + inc_set_handle_t *ih; + bool need_gc = false; + + if (handle != _inc_set_handle) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + if (!value_data && data_size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _inc_set_mutex.lock(); + + ih = reinterpret_cast(handle); + + if (!ih->header.magic) { + ret = KVSTORE_ERROR_INVALID_ARGUMENT; + goto end; + } + + if (ih->offset_in_data + data_size > ih->header.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + + // Update CRC with data chunk + ih->header.crc = calc_crc(ih->header.crc, data_size, value_data); + + // Write the data chunk + ret = write_area(_active_area, ih->bd_curr_offset, data_size, value_data); + if (ret) { + need_gc = true; + goto end; + } + ih->bd_curr_offset += data_size; + ih->offset_in_data += data_size; + +end: + if ((need_gc) && (ih->bd_base_offset != _master_record_offset)) { + garbage_collection(); + } + + _inc_set_mutex.unlock(); + return ret; +} + +int TDBStore::set_finalize(set_handle_t handle) +{ + int os_ret, ret = KVSTORE_SUCCESS; + inc_set_handle_t *ih; + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *entry; + bool need_gc = false; + uint32_t actual_data_size, hash, flags, next_offset; + + if (handle != _inc_set_handle) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + ih = reinterpret_cast(handle); + + if (!ih->header.magic) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _inc_set_mutex.lock(); + + if (ih->offset_in_data != ih->header.data_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + need_gc = true; + goto end; + } + + // Write header + ret = write_area(_active_area, ih->bd_base_offset, sizeof(record_header_t), &ih->header); + if (ret) { + need_gc = true; + goto end; + } + + // Need to flush buffered BD as our record is totally written now + os_ret = _buff_bd->sync(); + if (os_ret) { + ret = KVSTORE_ERROR_WRITE_FAILED; + need_gc = true; + goto end; + } + + // In master record case we don't update RAM table + if (ih->bd_base_offset == _master_record_offset) { + goto end; + } + + // Writes may fail without returning a failure (especially in flash components). Reread the record + // to ensure write success (this won't read the data anywhere - just use the CRC calculation). + ret = read_record(_active_area, ih->bd_base_offset, 0, 0, (uint32_t) -1, + actual_data_size, 0, false, false, false, false, + hash, flags, next_offset); + if (ret) { + need_gc = true; + goto end; + } + + // Update RAM table + if (ih->header.flags & delete_flag) { + _num_keys--; + if (ih->ram_table_ind < _num_keys) { + memmove(&ram_table[ih->ram_table_ind], &ram_table[ih->ram_table_ind + 1], + sizeof(ram_table_entry_t) * (_num_keys - ih->ram_table_ind)); + } + update_all_iterators(false, ih->ram_table_ind); + } else { + if (ih->new_key) { + if (ih->ram_table_ind < _num_keys) { + memmove(&ram_table[ih->ram_table_ind + 1], &ram_table[ih->ram_table_ind], + sizeof(ram_table_entry_t) * (_num_keys - ih->ram_table_ind)); + } + _num_keys++; + update_all_iterators(true, ih->ram_table_ind); + } + entry = &ram_table[ih->ram_table_ind]; + entry->hash = ih->hash; + entry->bd_offset = ih->bd_base_offset; + } + + _free_space_offset = align_up(ih->bd_curr_offset, _prog_size); + + // Safety check: If there seems to be valid keys on the free space + // we should erase one sector more, just to ensure that in case of power failure + // next init() would not extend the scan phase to that section as well. + os_ret = read_record(_active_area, _free_space_offset, 0, 0, 0, actual_data_size, 0, + false, false, false, false, hash, flags, next_offset); + if (os_ret == KVSTORE_SUCCESS) { + check_erase_before_write(_active_area, _free_space_offset, sizeof(record_header_t)); + } + +end: + // mark handle as invalid by clearing magic field in header + ih->header.magic = 0; + + _inc_set_mutex.unlock(); + + if (ih->bd_base_offset != _master_record_offset) { + if (need_gc) { + garbage_collection(); + } + _mutex.unlock(); + } + return ret; +} + +int TDBStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags) +{ + int ret; + set_handle_t handle; + + // Don't wait till we get to set_add_data to catch this + if (!buffer && size) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + ret = set_start(&handle, key, size, create_flags); + if (ret) { + return ret; + } + + ret = set_add_data(handle, buffer, size); + if (ret) { + return ret; + } + + ret = set_finalize(handle); + return ret; +} + +int TDBStore::remove(const char *key) +{ + return set(key, 0, 0, delete_flag); +} + +int TDBStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset) +{ + int ret; + uint32_t actual_data_size; + uint32_t bd_offset, next_bd_offset; + uint32_t flags, hash, ram_table_ind; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + ret = find_record(_active_area, key, bd_offset, ram_table_ind, hash); + + if (ret != KVSTORE_SUCCESS) { + goto end; + } + + ret = read_record(_active_area, bd_offset, const_cast(key), buffer, buffer_size, + actual_data_size, offset, false, true, false, false, hash, flags, next_bd_offset); + + if (actual_size) { + *actual_size = actual_data_size; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::get_info(const char *key, info_t *info) +{ + int ret; + uint32_t bd_offset, next_bd_offset; + uint32_t flags, hash, ram_table_ind; + uint32_t actual_data_size; + + if (!is_valid_key(key)) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + ret = find_record(_active_area, key, bd_offset, ram_table_ind, hash); + + if (ret) { + goto end; + } + + // Give a large dummy buffer size in order to achieve actual data size + // (as copy_data flag is not set, data won't be copied anywhere) + ret = read_record(_active_area, bd_offset, const_cast(key), 0, (uint32_t) -1, + actual_data_size, 0, false, false, false, false, hash, flags, + next_bd_offset); + + if (ret) { + goto end; + } + + if (info) { + info->flags = flags; + info->size = actual_data_size; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::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.tdbstore_revision = tdbstore_revision; + master_rec.reserved = 0; + next_offset = _master_record_offset + _master_record_size; + return set(master_rec_key, &master_rec, sizeof(master_rec), 0); +} + +int TDBStore::copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset, + uint32_t &to_next_offset) +{ + int ret; + record_header_t header; + uint32_t total_size; + uint16_t chunk_size; + + ret = read_area(from_area, from_offset, sizeof(header), &header); + if (ret) { + return ret; + } + + total_size = align_up(sizeof(record_header_t), _prog_size) + + align_up(header.key_size + header.data_size, _prog_size);; + + + if (to_offset + total_size > _size) { + // We are trying to copy more that the are can hold + return KVSTORE_ERROR_MEDIA_FULL; + } + ret = check_erase_before_write(1 - from_area, to_offset, total_size); + if (ret) { + return ret; + } + + chunk_size = align_up(sizeof(record_header_t), _prog_size); + // The record header takes up whole program units + memset(_work_buf, 0, chunk_size); + memcpy(_work_buf, &header, sizeof(record_header_t)); + ret = write_area(1 - from_area, to_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + from_offset += chunk_size; + to_offset += chunk_size; + total_size -= chunk_size; + + while (total_size) { + chunk_size = std::min(total_size, _work_buf_size); + ret = read_area(from_area, from_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + ret = write_area(1 - from_area, to_offset, chunk_size, _work_buf); + if (ret) { + return ret; + } + + from_offset += chunk_size; + to_offset += chunk_size; + total_size -= chunk_size; + } + + to_next_offset = align_up(to_offset, _prog_size); + return KVSTORE_SUCCESS; +} + +int TDBStore::garbage_collection() +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t to_offset, to_next_offset; + int ret; + size_t ind; + + // Reset the standby area + ret = reset_area(1 - _active_area); + if (ret) { + return ret; + } + + to_offset = _master_record_offset + _master_record_size; + + // 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_keys; ind++) { + uint32_t from_offset = ram_table[ind].bd_offset; + ret = copy_record(_active_area, from_offset, to_offset, to_next_offset); + if (ret) { + return ret; + } + // Update RAM table + ram_table[ind].bd_offset = to_offset; + to_offset = to_next_offset; + } + + 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; + } + + return KVSTORE_SUCCESS; +} + + +int TDBStore::build_ram_table() +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + uint32_t offset, next_offset = 0, dummy; + int ret = KVSTORE_SUCCESS; + uint32_t hash; + uint32_t flags; + uint32_t actual_data_size; + uint32_t ram_table_ind; + + _num_keys = 0; + offset = _master_record_offset; + + while (offset + sizeof(record_header_t) < _free_space_offset) { + ret = read_record(_active_area, offset, _key_buf, 0, 0, actual_data_size, 0, + true, false, false, true, hash, flags, next_offset); + + if (ret) { + goto end; + } + + ret = find_record(_active_area, _key_buf, dummy, ram_table_ind, hash); + + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_ITEM_NOT_FOUND)) { + goto end; + } + + uint32_t save_offset = offset; + offset = next_offset; + + if (ret == KVSTORE_ERROR_ITEM_NOT_FOUND) { + // Key doesn't exist, need to add it to RAM table + ret = KVSTORE_SUCCESS; + + if (flags & delete_flag) { + continue; + } + if (_num_keys >= _max_keys) { + // In order to avoid numerous reallocations of ram table, + // Add a chunk of entries now + increment_max_keys(reinterpret_cast(&ram_table)); + } + memmove(&ram_table[ram_table_ind + 1], &ram_table[ram_table_ind], + sizeof(ram_table_entry_t) * (_num_keys - ram_table_ind)); + + _num_keys++; + } else if (flags & delete_flag) { + _num_keys--; + memmove(&ram_table[ram_table_ind], &ram_table[ram_table_ind + 1], + sizeof(ram_table_entry_t) * (_num_keys - ram_table_ind)); + + continue; + } + + // update record parameters + ram_table[ram_table_ind].hash = hash; + ram_table[ram_table_ind].bd_offset = save_offset; + } + +end: + _free_space_offset = next_offset; + return ret; +} + +int TDBStore::increment_max_keys(void **ram_table) +{ + // Reallocate ram table with new size + ram_table_entry_t *old_ram_table = (ram_table_entry_t *) _ram_table; + ram_table_entry_t *new_ram_table = new ram_table_entry_t[_max_keys + 1]; + memset(new_ram_table, 0, sizeof(ram_table_entry_t) * (_max_keys + 1)); + + // Copy old content to new table + memcpy(new_ram_table, old_ram_table, sizeof(ram_table_entry_t) * _max_keys); + _max_keys++; + + _ram_table = new_ram_table; + delete[] old_ram_table; + + if (ram_table) { + *ram_table = _ram_table; + } + return KVSTORE_SUCCESS; +} + + +int TDBStore::init() +{ + ram_table_entry_t *ram_table; + area_state_e area_state[_num_areas]; + uint32_t next_offset; + uint32_t flags, hash; + uint32_t actual_data_size; + int ret = KVSTORE_SUCCESS; + uint16_t versions[_num_areas]; + + _mutex.lock(); + + if (_is_initialized) { + goto end; + } + + _max_keys = initial_max_keys; + + ram_table = new ram_table_entry_t[_max_keys]; + memset(ram_table, 0, sizeof(ram_table_entry_t) * _max_keys); + _ram_table = ram_table; + _num_keys = 0; + + _size = (size_t) -1; + + _buff_bd = new BufferedBlockDevice(_bd); + ret = _buff_bd->init(); + if (ret) { + goto fail; + } + + _prog_size = _bd->get_program_size(); + _work_buf_size = std::max(_prog_size, min_work_buf_size); + _work_buf = new uint8_t[_work_buf_size]; + _key_buf = new char[MAX_KEY_SIZE]; + _inc_set_handle = new inc_set_handle_t; + memset(_inc_set_handle, 0, sizeof(inc_set_handle_t)); + memset(_iterator_table, 0, sizeof(_iterator_table)); + + _master_record_offset = align_up(RESERVED_AREA_SIZE + sizeof(reserved_trailer_t), _prog_size); + _master_record_size = record_size(master_rec_key, sizeof(master_record_data_t)); + + calc_area_params(); + + /* Minimum space required by Reserved area and master record */ + MBED_ASSERT(_bd->size() + >= (align_up(RESERVED_AREA_SIZE + sizeof(reserved_trailer_t), _prog_size) + + record_size(master_rec_key, sizeof(master_record_data_t)))); + + for (uint8_t area = 0; area < _num_areas; area++) { + area_state[area] = TDBSTORE_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, _master_record_offset, const_cast(master_rec_key), + &master_rec, sizeof(master_rec), actual_data_size, 0, false, true, true, false, + hash, flags, next_offset); + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + // KVSTORE_ERROR(ret, "TDBSTORE: Unable to read record at init"); // FIXME + } + + // Master record may be either corrupt or erased + if (ret == KVSTORE_ERROR_INVALID_DATA_DETECTED) { + area_state[area] = TDBSTORE_AREA_STATE_INVALID; + continue; + } + + versions[area] = master_rec.version; + + area_state[area] = TDBSTORE_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] == TDBSTORE_AREA_STATE_INVALID) && (area_state[1] == TDBSTORE_AREA_STATE_INVALID)) { + reset_area(0); + _active_area = 0; + _active_area_version = 1; + area_state[0] = TDBSTORE_AREA_STATE_ERASED; + ret = write_master_record(_active_area, _active_area_version, _free_space_offset); + if (ret) { + // KVSTORE_ERROR(ret, "TDBSTORE: Unable to write master record at init"); // FIXME + } + // Nothing more to do here if active area is empty + goto end; + } + + // In case we have two valid areas, choose the one having the higher version (or 0 + // in case of wrap around). + if ((area_state[0] == TDBSTORE_AREA_STATE_VALID) && (area_state[1] == TDBSTORE_AREA_STATE_VALID)) { + if ((versions[0] > versions[1]) || (!versions[0])) { + _active_area = 0; + } else { + _active_area = 1; + } + _active_area_version = versions[_active_area]; + } + + // 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(); + + // build_ram_table() scans all keys, until invalid data found. + // Therefore INVALID_DATA is not considered error. + if ((ret != KVSTORE_SUCCESS) && (ret != KVSTORE_ERROR_INVALID_DATA_DETECTED)) { + goto fail; + } + +end: + _is_initialized = true; + _mutex.unlock(); + return KVSTORE_SUCCESS; +fail: + delete[] ram_table; + delete _buff_bd; + delete[] _work_buf; + delete[] _key_buf; + delete reinterpret_cast(_inc_set_handle); + _ram_table = nullptr; + _buff_bd = nullptr; + _work_buf = nullptr; + _key_buf = nullptr; + _inc_set_handle = nullptr; + _mutex.unlock(); + return ret; +} + +int TDBStore::deinit() +{ + _mutex.lock(); + if (_is_initialized) { + _buff_bd->deinit(); + delete _buff_bd; + + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + delete[] ram_table; + delete[] _work_buf; + delete[] _key_buf; + delete reinterpret_cast(_inc_set_handle); + } + + _is_initialized = false; + _mutex.unlock(); + + return KVSTORE_SUCCESS; +} + +int TDBStore::reset_area(uint8_t area) +{ + uint8_t buf[RESERVED_AREA_SIZE + sizeof(reserved_trailer_t)]; + int ret; + bool copy_reserved_data = do_reserved_data_get(buf, sizeof(buf), 0, buf + RESERVED_AREA_SIZE) == KVSTORE_SUCCESS; + + // Erase reserved area and master record + ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size + _prog_size, true); + if (ret) { + return ret; + } + if (copy_reserved_data) { + ret = write_area(area, 0, sizeof(buf), buf); + } + return ret; +} + +int TDBStore::reset() +{ + uint8_t area; + int ret; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + // Reset both areas + for (area = 0; area < _num_areas; area++) { + ret = check_erase_before_write(area, 0, _master_record_offset + _master_record_size + _prog_size, true); + if (ret) { + goto end; + } + } + + _active_area = 0; + _num_keys = 0; + _free_space_offset = _master_record_offset; + _active_area_version = 1; + memset(_ram_table, 0, sizeof(ram_table_entry_t) * _max_keys); + // Write an initial master record on active area + ret = write_master_record(_active_area, _active_area_version, _free_space_offset); + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_open(iterator_t *it, const char *prefix) +{ + key_iterator_handle_t *handle; + int ret = KVSTORE_SUCCESS; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + if (!it) { + return KVSTORE_ERROR_INVALID_ARGUMENT; + } + + _mutex.lock(); + + int it_num; + for (it_num = 0; it_num < _max_open_iterators; it_num++) { + if (!_iterator_table[it_num]) { + break; + } + } + + if (it_num == _max_open_iterators) { + ret = KVSTORE_ERROR_OUT_OF_RESOURCES; + goto end; + } + + handle = new key_iterator_handle_t; + *it = reinterpret_cast(handle); + + if (prefix && strcmp(prefix, "")) { + handle->prefix = new char[strlen(prefix) + 1]; + strcpy(handle->prefix, prefix); + } else { + handle->prefix = 0; + } + handle->ram_table_ind = 0; + handle->iterator_num = it_num; + _iterator_table[it_num] = handle; + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_next(iterator_t it, char *key, size_t key_size) +{ + ram_table_entry_t *ram_table = (ram_table_entry_t *) _ram_table; + key_iterator_handle_t *handle; + int ret; + uint32_t actual_data_size, hash, flags, next_offset; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + handle = reinterpret_cast(it); + + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + + while (ret && (handle->ram_table_ind < _num_keys)) { + ret = read_record(_active_area, ram_table[handle->ram_table_ind].bd_offset, _key_buf, + 0, 0, actual_data_size, 0, true, false, false, false, hash, flags, next_offset); + if (ret) { + goto end; + } + if (!handle->prefix || (strstr(_key_buf, handle->prefix) == _key_buf)) { + if (strlen(_key_buf) >= key_size) { + ret = KVSTORE_ERROR_INVALID_SIZE; + goto end; + } + strcpy(key, _key_buf); + } else { + ret = KVSTORE_ERROR_ITEM_NOT_FOUND; + } + handle->ram_table_ind++; + } + +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::iterator_close(iterator_t it) +{ + key_iterator_handle_t *handle; + + if (!_is_initialized) { + return KVSTORE_ERROR_NOT_READY; + } + + _mutex.lock(); + + handle = reinterpret_cast(it); + delete[] handle->prefix; + _iterator_table[handle->iterator_num] = 0; + delete handle; + + _mutex.unlock(); + + return KVSTORE_SUCCESS; +} + +void TDBStore::update_all_iterators(bool added, uint32_t ram_table_ind) +{ + for (int it_num = 0; it_num < _max_open_iterators; it_num++) { + key_iterator_handle_t *handle = static_cast (_iterator_table[it_num]); + if (!handle) { + continue; + } + + if (ram_table_ind >= handle->ram_table_ind) { + continue; + } + + if (added) { + handle->ram_table_ind++; + } else { + handle->ram_table_ind--; + } + } +} + +int TDBStore::reserved_data_set(const void *reserved_data, size_t reserved_data_buf_size) +{ + reserved_trailer_t trailer; + int ret; + + if (reserved_data_buf_size > RESERVED_AREA_SIZE) { + return KVSTORE_ERROR_INVALID_SIZE; + } + + _mutex.lock(); + + ret = do_reserved_data_get(0, 0); + if (ret == KVSTORE_SUCCESS) { + ret = KVSTORE_ERROR_WRITE_FAILED; + goto end; + } + + trailer.trailer_size = sizeof(trailer); + trailer.data_size = reserved_data_buf_size; + trailer.crc = calc_crc(initial_crc, reserved_data_buf_size, reserved_data); + + // Erase the header of non-active area, just to make sure that we can write to it + // In case garbage collection has not yet been run, the area can be un-erased + ret = reset_area(1 - _active_area); + if (ret) { + goto end; + } + + /* + * Write to both areas + * Both must success, as they are required to be erased when TDBStore initializes + * its area + */ + for (int i = 0; i < _num_areas; ++i) { + ret = write_area(i, 0, reserved_data_buf_size, reserved_data); + if (ret) { + goto end; + } + ret = write_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer); + if (ret) { + goto end; + } + ret = _buff_bd->sync(); + if (ret) { + goto end; + } + } + ret = KVSTORE_SUCCESS; +end: + _mutex.unlock(); + return ret; +} + +int TDBStore::do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size, void *copy_trailer) +{ + reserved_trailer_t trailer; + uint8_t buf[RESERVED_AREA_SIZE]; + int ret; + uint32_t crc; + + /* + * Try to keep reserved data identical on both areas, therefore + * we can return any of these data, if the checmsum is correct. + */ + for (int i = 0; i < _num_areas; ++i) { + ret = read_area(i, RESERVED_AREA_SIZE, sizeof(trailer), &trailer); + if (ret) { + return ret; + } + + // First validy check: is the trailer header size correct + if (trailer.trailer_size != sizeof(trailer)) { + continue; + } + // Second validy check: Is the data too big (corrupt header) + if (trailer.data_size > RESERVED_AREA_SIZE) { + continue; + } + + // Next, verify the checksum + ret = read_area(i, 0, trailer.data_size, buf); + if (ret) { + return ret; + } + crc = calc_crc(initial_crc, trailer.data_size, buf); + if (crc == trailer.crc) { + // Correct data, copy it and return to caller + if (reserved_data) { + if (reserved_data_buf_size < trailer.data_size) { + return KVSTORE_ERROR_INVALID_SIZE; + } + memcpy(reserved_data, buf, trailer.data_size); + } + if (actual_data_size) { + *actual_data_size = trailer.data_size; + } + if (copy_trailer) { + memcpy(copy_trailer, &trailer, sizeof(trailer)); + } + return KVSTORE_SUCCESS; + } + } + + return KVSTORE_ERROR_ITEM_NOT_FOUND; +} + +int TDBStore::reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, size_t *actual_data_size) +{ + _mutex.lock(); + int ret = do_reserved_data_get(reserved_data, reserved_data_buf_size, actual_data_size); + _mutex.unlock(); + return ret; +} + +void TDBStore::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; + + // The parameter of `BlockDevice::get_erase_size(bd_addr_t addr)` + // does not need to be aligned. + uint32_t erase_unit = _buff_bd->get_erase_size(bd_offset); + + // Even on a flash device with multiple regions, the start address of + // an erase unit is aligned to the current region's unit size. + offset_from_start = bd_offset % erase_unit; + dist_to_end = erase_unit - offset_from_start; +} + +int TDBStore::check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size, bool force_check) +{ + // In order to save init time, we don't check that the entire area is erased. + // Instead, whenever reaching an erase unit start erase it. + bool erase = false; + uint32_t start_offset; + uint32_t end_offset; + 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 == 0 || force_check) { + if (!erase) { + erase = true; + start_offset = offset - offset_from_start; + } + end_offset = offset + dist; + } + offset += chunk; + size -= chunk; + } + + if (erase) { + int ret = erase_area(area, start_offset, end_offset - start_offset); + if (ret != KVSTORE_SUCCESS) { + return KVSTORE_ERROR_WRITE_FAILED; + } + } + + return KVSTORE_SUCCESS; +} diff --git a/libraries/KVStore/TDBStore.h b/libraries/KVStore/TDBStore.h new file mode 100644 index 00000000..ab5fdc44 --- /dev/null +++ b/libraries/KVStore/TDBStore.h @@ -0,0 +1,548 @@ +/* + * 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_TDBSTORE_H +#define MBED_TDBSTORE_H + +#include +#include "KVStore.h" +#include +#include + +// namespace mbed { + +/** TDBStore class + * + * Lightweight Key Value storage over a block device + */ + +class TDBStore : public mbed::KVStore { +public: + + static const uint32_t RESERVED_AREA_SIZE = 64; + + /** + * @brief Class constructor + * + * @param[in] bd Underlying block device. + * + * @returns none + */ + TDBStore(BlockDevice *bd); + + /** + * @brief Class destructor + * + * @returns none + */ + virtual ~TDBStore(); + + /** + * @brief Initialize TDBStore. If data exists, TDBStore will check the data integrity + * on initialize. If the integrity checks fails, the TDBStore will use GC to collect + * the available data and clean corrupted and erroneous records. + * + * @returns KVSTORE_SUCCESS Success. + * @returns Negative error code on failure. + */ + virtual int init(); + + /** + * @brief Deinitialize TDBStore, release and free resources. + * + * @returns KVSTORE_SUCCESS Success. + */ + virtual int deinit(); + + + /** + * @brief Reset TDBStore contents (clear all keys) and reserved data + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + */ + virtual int reset(); + + /** + * @brief Set one TDBStore item, given key and value. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] size Value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); + + /** + * @brief Get one TDBStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] buffer Value data buffer. + * @param[in] buffer_size Value data buffer size. + * @param[out] actual_size Actual read size. + * @param[in] offset Offset to read from in data. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, + size_t offset = 0); + + /** + * @brief Get information of a given key. The returned info contains size and flags + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] info Returned information structure. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + */ + virtual int get_info(const char *key, info_t *info); + + /** + * @brief Remove a TDBStore item by given key. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_ITEM_NOT_FOUND No such key. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int remove(const char *key); + + + /** + * @brief Start an incremental TDBStore set sequence. This operation is blocking other operations. + * Any get/set/remove/iterator operation will be blocked until set_finalize is called. + * + * @param[out] handle Returned incremental set handle. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] final_data_size Final value data size. + * @param[in] create_flags Flag mask. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_MEDIA_FULL Not enough room on media. + * KVSTORE_ERROR_WRITE_PROTECTED Already stored with "write once" flag. + */ + virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); + + /** + * @brief Add data to incremental TDBStore set sequence. This operation is blocking other operations. + * Any get/set/remove operation will be blocked until set_finalize will be called. + * + * @param[in] handle Incremental set handle. + * @param[in] value_data Value data to add. + * @param[in] data_size Value data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + */ + virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); + + /** + * @brief Finalize an incremental KVStore set sequence. + * + * @param[in] handle Incremental set handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int set_finalize(set_handle_t handle); + + /** + * @brief Start an iteration over KVStore keys. + * There are no issues with any other operations while iterator is open. + * + * @param[out] it Returned iterator handle. + * @param[in] prefix Key prefix (null for all keys). + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_open(iterator_t *it, const char *prefix = NULL); + + /** + * @brief Get next key in iteration. + * There are no issues with any other operations while iterator is open. + * + * @param[in] it Iterator handle. + * @param[in] key Buffer for returned key. + * @param[in] key_size Key buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from block device. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No more keys found. + */ + virtual int iterator_next(iterator_t it, char *key, size_t key_size); + + /** + * @brief Close iteration. + * + * @param[in] it Iterator handle. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + */ + virtual int iterator_close(iterator_t it); + + /** + * @brief Set data in reserved area, which is a special location for special data, such as ROT. + * The data written to reserved area can't be overwritten. + * + * @param[in] reserved_data Reserved data buffer. + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_WRITE_FAILED Unable to write to media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_SIZE Invalid size given in function arguments. + */ + virtual int reserved_data_set(const void *reserved_data, size_t reserved_data_buf_size); + + /** + * @brief Get data from reserved area, which is a special location for special data, such as ROT. + * + * @param[in] reserved_data Reserved data buffer. + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * @param[in] actual_data_size Return data size. + * + * @returns KVSTORE_SUCCESS Success. + * KVSTORE_ERROR_NOT_READY Not initialized. + * KVSTORE_ERROR_READ_FAILED Unable to read from media. + * KVSTORE_ERROR_INVALID_ARGUMENT Invalid argument given in function arguments. + * KVSTORE_ERROR_INVALID_DATA_DETECTED Data is corrupt. + * KVSTORE_ERROR_ITEM_NOT_FOUND No reserved data was written. + */ + virtual int reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, + size_t *actual_data_size = 0); + +#if !defined(DOXYGEN_ONLY) +private: + + typedef struct { + uint32_t address; + size_t size; + } tdbstore_area_data_t; + + static const int _num_areas = 2; + static const int _max_open_iterators = 16; + + PlatformMutex _mutex; + PlatformMutex _inc_set_mutex; + void *_ram_table; + size_t _max_keys; + size_t _num_keys; + BlockDevice *_bd; + BufferedBlockDevice *_buff_bd; + uint32_t _free_space_offset; + uint32_t _master_record_offset; + uint32_t _master_record_size; + bool _is_initialized; + int _active_area; + uint16_t _active_area_version; + size_t _size; + tdbstore_area_data_t _area_params[_num_areas]; + uint32_t _prog_size; + uint8_t *_work_buf; + size_t _work_buf_size; + char *_key_buf; + void *_inc_set_handle; + void *_iterator_table[_max_open_iterators]; + + /** + * @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 Reset an area (erase its start). + * This erases master record, but preserves the + * reserved area data. + * + * @param[in] area Area. + * + * @returns 0 for success, nonzero for failure. + */ + int reset_area(uint8_t area); + + /** + * @brief Erase an area. + * + * @param[in] area Area. + * @param[in] offset Offset in area. + * @param[in] size Number of bytes to erase. + * + * @returns 0 for success, nonzero for failure. + */ + int erase_area(uint8_t area, uint32_t offset, uint32_t size); + + /** + * @brief Calculate addresses and sizes of areas. + */ + void calc_area_params(); + + /** + * @brief Read a TDBStore record from a given location. + * + * @param[in] area Area. + * @param[in] offset Offset of record in area. + * @param[out] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] data_buf Data buffer. + * @param[in] data_buf_size Data buffer size. + * @param[out] actual_data_size Actual data size. + * @param[in] data_offset Offset in data. + * @param[in] copy_key Copy key to user buffer. + * @param[in] copy_data Copy data to user buffer. + * @param[in] check_expected_key Check whether key belongs to this record. + * @param[in] calc_hash Calculate hash (on key). + * @param[out] hash Calculated hash. + * @param[out] flags Record flags. + * @param[out] next_offset Offset of next record. + * + * @returns 0 for success, nonzero for failure. + */ + int read_record(uint8_t area, uint32_t offset, char *key, + void *data_buf, uint32_t data_buf_size, + uint32_t &actual_data_size, size_t data_offset, bool copy_key, + bool copy_data, bool check_expected_key, bool calc_hash, + uint32_t &hash, uint32_t &flags, 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 Garbage collection (compact all records from active area to the standby one). + * + * @returns 0 for success, nonzero for failure. + */ + int garbage_collection(); + + /** + * @brief Return record size given key and data size. + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] data_size Data size. + * + * @returns record size. + */ + uint32_t record_size(const char *key, uint32_t data_size); + + /** + * @brief Find a record given key + * + * @param[in] area Area. + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[out] offset Offset of record. + * @param[out] ram_table_ind Index in RAM table (target one if not found). + * @param[out] hash Calculated key hash. + * + * @returns 0 for success, nonzero for failure. + */ + int find_record(uint8_t area, const char *key, uint32_t &offset, + uint32_t &ram_table_ind, uint32_t &hash); + /** + * @brief Actual logics of get API (also covers all other get APIs). + * + * @param[in] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @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 char *key, 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] key Key - must not include '*' '/' '?' ':' ';' '\' '"' '|' ' ' '<' '>' '\'. + * @param[in] data_buf Data buffer. + * @param[in] data_buf_size Data buffer size (bytes). + * @param[in] flags Flags. + * + * @returns 0 for success, nonzero for failure. + */ + int do_set(const char *key, 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). + * + * @returns 0 for success, nonzero for failure. + */ + int build_ram_table(); + + /** + * @brief Increment maximum number of keys and reallocate RAM table accordingly. + * + * @param[out] ram_table Updated RAM table. + * + * @returns 0 for success, nonzero for failure. + */ + int increment_max_keys(void **ram_table = 0); + + /** + * @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 Before writing a record, check whether you are crossing an erase unit. + * If you 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. + * @param[in] force_check Force checking. + * + * @returns 0 for success, nonzero for failure. + */ + int check_erase_before_write(uint8_t area, uint32_t offset, uint32_t size, + bool force_check = false); + + /** + * @brief Get data from reserved area - worker function. + * This verifies that reserved data on both areas have + * correct checksums. If given pointer is not NULL, also + * write the reserved data to buffer. If checksums are not + * valid, return error code, and don't write anything to any + * pointers. + * + * @param[out] reserved_data Reserved data buffer (NULL to return nothing). + * @param[in] reserved_data_buf_size + * Reserved data buffer size. + * @param[out] actual_data_size If not NULL, return actual data size. + * @param[out] copy_trailer If not NULL, copy the trailer content to given buffer. + * + * @returns 0 on success or a negative error code on failure + */ + int do_reserved_data_get(void *reserved_data, size_t reserved_data_buf_size, + size_t *actual_data_size = 0, void *copy_trailer = 0); + + /** + * @brief Update all iterators after adding or deleting of keys. + * + * @param[in] added True if added, false if deleted. + * @param[in] ram_table_ind RAM table index. + * + * @returns none + */ + void update_all_iterators(bool added, uint32_t ram_table_ind); + +#endif + +}; +/** @}*/ + +// } // namespace mbed + +#endif