mirror of
https://github.com/espressif/esp-idf.git
synced 2025-12-26 05:09:19 +00:00
743 lines
34 KiB
C
743 lines
34 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "nvs_bootloader.h"
|
|
#include "nvs_bootloader_private.h"
|
|
#include "esp_assert.h"
|
|
#include "sdkconfig.h"
|
|
|
|
#include "esp_partition.h"
|
|
#include "nvs_constants.h"
|
|
#include <esp_rom_crc.h>
|
|
|
|
#if CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD
|
|
#include "mbedtls_rom_osi.h"
|
|
#endif
|
|
|
|
static const char* TAG = "nvs_bootloader";
|
|
|
|
// Static asserts ensuring that the size of the c structures match NVS physical footprint on the flash
|
|
ESP_STATIC_ASSERT(sizeof(nvs_bootloader_page_header_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_page_header_t size is not 32 bytes");
|
|
ESP_STATIC_ASSERT(sizeof(nvs_bootloader_page_entry_states_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_page_entry_states_t size is not 32 bytes");
|
|
ESP_STATIC_ASSERT(sizeof(nvs_bootloader_single_entry_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_single_entry_t size is not 32 bytes");
|
|
|
|
#define NVS_KEY_SIZE 32 // AES-256
|
|
|
|
/* Currently we support only single-threaded use-cases of reading encrypted NVS partitions */
|
|
static nvs_bootloader_xts_aes_context dec_ctx;
|
|
static bool is_nvs_partition_encrypted = false;
|
|
|
|
static esp_err_t decrpyt_data(uint32_t src_offset, void* dst, size_t size)
|
|
{
|
|
// decrypt data
|
|
// sector num, could have been just uint64/32.
|
|
uint8_t data_unit[16];
|
|
uint8_t *destination = (uint8_t *)dst;
|
|
uint32_t relAddr = src_offset;
|
|
|
|
memset(data_unit, 0, sizeof(data_unit));
|
|
memcpy(data_unit, &relAddr, sizeof(relAddr));
|
|
|
|
return nvs_bootloader_aes_crypt_xts(&dec_ctx, AES_DEC, size, data_unit, destination, destination);
|
|
}
|
|
|
|
static esp_err_t nvs_bootloader_partition_read_string_value(const esp_partition_t *partition, size_t src_offset, void *block, size_t block_len)
|
|
{
|
|
esp_err_t ret = ESP_FAIL;
|
|
|
|
/* For the bootloader build, the esp_partition_read() API internally is calls bootloader_flash_read() that
|
|
* requires the src_address, length and the destination address to be word aligned.
|
|
* src_address: NVS keys and values are always stored at a word aligned offset
|
|
* length: Reading bytes of length divisible by 4 at a time (BOOTLOADER_FLASH_READ_LEN)
|
|
* destination address: Using a word aligned buffer to read the flash contents (bootloader_flash_read_buffer)
|
|
*/
|
|
#define BOOTLOADER_FLASH_READ_LEN 32 // because it matches the below discussed XTS-AES requirements as well
|
|
|
|
/*
|
|
* When reading an encrypted partition, we implement the splitting method, that is, we read and decrypt data size in the multiples of 16.
|
|
* This is necessary because of a bug present in the mbedtls_aes_crypt_xts() function wherein "inplace" encryption/decryption
|
|
* calculation fails if the length of buffers is not a multiple of 16.
|
|
* Reference: https://github.com/Mbed-TLS/mbedtls/issues/4302
|
|
*
|
|
* Thus, in this case we first operate over the aligned down to 16 length of data and then over the remaining data, by copying
|
|
* it to a buffer of size 16.
|
|
*
|
|
* Also, until https://github.com/Mbed-TLS/mbedtls/issues/9827 is resolved, we need to operate over chunks of length 32.
|
|
*/
|
|
#define XTS_AES_PROCESS_BLOCK_LEN 32
|
|
|
|
if (src_offset & 3 || block_len & 3 || (intptr_t) block & 3
|
|
|| is_nvs_partition_encrypted
|
|
) {
|
|
WORD_ALIGNED_ATTR uint8_t bootloader_flash_read_buffer[BOOTLOADER_FLASH_READ_LEN] = { 0 };
|
|
|
|
size_t block_data_len = block_len / BOOTLOADER_FLASH_READ_LEN * BOOTLOADER_FLASH_READ_LEN;
|
|
size_t remaining_data_len = block_len % BOOTLOADER_FLASH_READ_LEN;
|
|
|
|
/* Process block data */
|
|
if (block_data_len > 0) {
|
|
for (size_t data_processed = 0; data_processed < block_data_len; data_processed += BOOTLOADER_FLASH_READ_LEN) {
|
|
ret = esp_partition_read(partition, src_offset + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
if (is_nvs_partition_encrypted) {
|
|
ret = decrpyt_data(src_offset + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
memcpy(block + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN);
|
|
}
|
|
}
|
|
|
|
/* Process remaining data */
|
|
if (remaining_data_len) {
|
|
ret = esp_partition_read(partition, src_offset + block_data_len, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
if (is_nvs_partition_encrypted) {
|
|
ret = decrpyt_data(src_offset + block_data_len, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
memcpy(block + block_data_len, bootloader_flash_read_buffer, remaining_data_len);
|
|
}
|
|
} else {
|
|
ret = esp_partition_read(partition, src_offset, block, block_len);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_check_parameters(const char* partition_name,
|
|
const size_t read_list_count,
|
|
nvs_bootloader_read_list_t read_list[])
|
|
{
|
|
// return `ESP_ERR_NVS_INVALID_NAME` if the partition name specified is too long or is null.
|
|
if (partition_name == NULL || strlen(partition_name) > NVS_PART_NAME_MAX_SIZE) {
|
|
ESP_LOGD(TAG, "Invalid argument: partition_name is NULL or too long");
|
|
return ESP_ERR_NVS_INVALID_NAME;
|
|
}
|
|
// immediately return `ESP_ERR_INVALID_ARG` if the read_list is NULL or read_list_count is 0
|
|
if (read_list == NULL || read_list_count == 0) {
|
|
ESP_LOGD(TAG, "Invalid argument: read_list is NULL or read_list_count is 0");
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
esp_err_t ret = ESP_OK;
|
|
// iterate over all the read_list entries and check if the namespace name is too long or is null,
|
|
// or if the key name is too long or is null,
|
|
// or if the value type is invalid
|
|
// or if the value type is NVS_TYPE_STR and the buffer length is 0 or greater than the maximum allowed size
|
|
for (size_t i = 0; i < read_list_count; i++) {
|
|
// pre-fill the result code with `ESP_ERR_NVS_NOT_FOUND` and namespace index with 0
|
|
read_list[i].result_code = ESP_ERR_NVS_NOT_FOUND; // default result code during the check_parameters phase
|
|
read_list[i].namespace_index = 0; // indicated namespace as not found
|
|
|
|
// checks
|
|
if (read_list[i].namespace_name == NULL || strlen(read_list[i].namespace_name) > NVS_NS_NAME_MAX_SIZE - 1) {
|
|
ESP_LOGD(TAG, "Invalid argument: namespace_name is NULL or too long");
|
|
read_list[i].result_code = ESP_ERR_NVS_INVALID_NAME;
|
|
}
|
|
if (read_list[i].key_name == NULL || strlen(read_list[i].key_name) > NVS_KEY_NAME_MAX_SIZE - 1) {
|
|
ESP_LOGD(TAG, "Invalid argument: key_name is NULL or too long");
|
|
read_list[i].result_code = ESP_ERR_NVS_KEY_TOO_LONG;
|
|
}
|
|
if (!NVS_BOOTLOADER_IS_SUPPORTED_TYPE(read_list[i].value_type)) {
|
|
ESP_LOGD(TAG, "Invalid argument: value_type is invalid");
|
|
read_list[i].result_code = ESP_ERR_INVALID_ARG;
|
|
}
|
|
if ((read_list[i].value_type) == NVS_TYPE_STR && (read_list[i].value.str_val.buff_len == 0 || read_list[i].value.str_val.buff_len > NVS_CONST_STR_LEN_MAX_SIZE)) {
|
|
ESP_LOGD(TAG, "Invalid argument: buffer size provided for NVS_TYPE_STR is invalid");
|
|
read_list[i].result_code = ESP_ERR_INVALID_SIZE;
|
|
}
|
|
if ((read_list[i].value_type) == NVS_TYPE_STR && (read_list[i].value.str_val.buff_ptr == NULL)) {
|
|
ESP_LOGD(TAG, "Invalid argument: buffer pointer provided for NVS_TYPE_STR is null");
|
|
read_list[i].result_code = ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
// if the individual result code has changed from it's default value ESP_ERR_NVS_NOT_FOUND, set the result code of function to `ESP_ERR_INVALID_ARG`
|
|
if (read_list[i].result_code != ESP_ERR_NVS_NOT_FOUND) {
|
|
ret = ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
// clear the value placeholder for single entry data types. We do not touch the buffer type placeholder
|
|
if (NVS_BOOTLOADER_TYPE_FITS_SINGLE_ENTRY(read_list[i].value_type)) {
|
|
size_t val_len = sizeof(read_list[i].value);
|
|
memset((void*) &read_list[i].value, 0, val_len); // clear the value placeholder
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// we are going to use the visitor pattern to process all NVS pages in three subsequent loops:
|
|
// 1. read the page header to figure out the existence of the page in state "FREEING"
|
|
// 2. depending on the state of the page, read the entries from the page to figure out the namespace indexes of the requested namespaces
|
|
// 3. read the requested key - value pairs from the pages
|
|
|
|
// visitor function for reading the page header
|
|
esp_err_t nvs_bootloader_page_visitor_get_page_states(nvs_bootloader_page_visitor_param_t *visitor_param,
|
|
const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_header_t *page_header)
|
|
{
|
|
nvs_bootloader_page_visitor_param_get_page_states_t *param = (nvs_bootloader_page_visitor_param_get_page_states_t*) visitor_param;
|
|
switch (page_header->page_state) {
|
|
case NVS_CONST_PAGE_STATE_ACTIVE:
|
|
param->no_active_pages++;
|
|
break;
|
|
case NVS_CONST_PAGE_STATE_FREEING:
|
|
param->no_freeing_pages++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
// visitor function for reading the namespaces from pages
|
|
esp_err_t nvs_bootloader_page_visitor_get_namespaces(nvs_bootloader_page_visitor_param_t *visitor_param,
|
|
const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_header_t *page_header)
|
|
{
|
|
// log the visitor parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_namespaces called with page_index: %u", (unsigned)page_index);
|
|
|
|
nvs_bootloader_page_visitor_param_read_entries_t *param = (nvs_bootloader_page_visitor_param_read_entries_t*) visitor_param;
|
|
|
|
// if the page is in the state of "ACTIVE" and we are skipping the active page, then return immediately
|
|
if (param->skip_active_page && page_header->page_state == NVS_CONST_PAGE_STATE_ACTIVE) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
// read entry state map from the page
|
|
WORD_ALIGNED_ATTR nvs_bootloader_page_entry_states_t page_entry_states = {0};
|
|
|
|
esp_err_t ret = nvs_bootloader_read_page_entry_states(partition, page_index, &page_entry_states);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "Error reading NVS page entry states");
|
|
return ret;
|
|
}
|
|
|
|
// iterate over all entries with state written
|
|
// if the entry is namespace entry, then iterate the read_list and populate the namespace index by matching the namespace name
|
|
uint8_t start_index = 0;
|
|
WORD_ALIGNED_ATTR nvs_bootloader_single_entry_t item = {0};
|
|
|
|
// repeat finding single entry items on the page until all entries are processed or error occurs
|
|
while (ret == ESP_OK) {
|
|
ret = nvs_bootloader_read_next_single_entry_item(partition, page_index, &page_entry_states, &start_index, &item);
|
|
|
|
if (ret != ESP_OK) {
|
|
break;
|
|
}
|
|
|
|
// if the entry is a namespace entry, then iterate the read_list and populate the namespace index by matching the namespace name
|
|
if (item.namespace_index == 0 && item.data_type == NVS_TYPE_U8) {
|
|
|
|
ESP_LOGV(TAG, "Namespace %s record found. NS index is: %d. Trying to match read list...", item.key, item.data.primitive_type.data[0]);
|
|
for (size_t i = 0; i < param->read_list_count; i++) {
|
|
// if the namespace index is already populated, skip the read list entry
|
|
if (param->read_list[i].namespace_index != 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(param->read_list[i].namespace_name, item.key, NVS_KEY_NAME_MAX_SIZE) == 0) {
|
|
param->read_list[i].namespace_index = item.data.primitive_type.data[0];
|
|
ESP_LOGV(TAG, "Namespace %s found for read item: %u key: %s NS index is: %d", param->read_list[i].namespace_name,(unsigned)i, param->read_list[i].key_name, param->read_list[i].namespace_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
|
ret = ESP_OK;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// visitor function for reading the key - value pairs from pages
|
|
esp_err_t nvs_bootloader_page_visitor_get_key_value_pairs(nvs_bootloader_page_visitor_param_t *visitor_param,
|
|
const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_header_t *page_header)
|
|
{
|
|
nvs_bootloader_page_visitor_param_read_entries_t *param = (nvs_bootloader_page_visitor_param_read_entries_t*) visitor_param;
|
|
|
|
// if the page is in the state of "ACTIVE" and we are skipping the active page, then return immediately
|
|
if (param->skip_active_page && page_header->page_state == NVS_CONST_PAGE_STATE_ACTIVE) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
// read entry state map from the page
|
|
WORD_ALIGNED_ATTR nvs_bootloader_page_entry_states_t page_entry_states = {0};
|
|
esp_err_t ret = nvs_bootloader_read_page_entry_states(partition, page_index, &page_entry_states);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "Error reading NVS page entry states");
|
|
return ret;
|
|
}
|
|
|
|
// iterate over all entries with state written
|
|
// if the entry is not a namespace entry, then iterate the read_list and populate the value by matching the namespace index, key name and value type
|
|
uint8_t next_index = 0; // index of the next entry to read, updated to the next entry by the read_next_single_entry_item
|
|
uint8_t current_index = 0; // index of the actual entry being processed
|
|
WORD_ALIGNED_ATTR nvs_bootloader_single_entry_t item = {0};
|
|
|
|
// repeat finding single entry items on the page until all entries are processed or error occurs
|
|
while (ret == ESP_OK) {
|
|
current_index = next_index;
|
|
ret = nvs_bootloader_read_next_single_entry_item(partition, page_index, &page_entry_states, &next_index, &item);
|
|
|
|
if (ret != ESP_OK) {
|
|
break;
|
|
}
|
|
|
|
ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_key_value_pairs - read item NS index: %d, key: %s, data type: %d, span: %d. Trying to match read list...", item.namespace_index, item.key, item.data_type, item.span);
|
|
|
|
// skip namespace entry
|
|
if (item.namespace_index == 0) {
|
|
continue;
|
|
}
|
|
|
|
// iterate the read_list and populate the value by matching the namespace index, key name and value type
|
|
for (size_t i = 0; i < param->read_list_count; i++) {
|
|
|
|
// skip non matching namespace indexes
|
|
if (param->read_list[i].namespace_index != item.namespace_index) {
|
|
continue;
|
|
}
|
|
|
|
// process only if the result code of the read list is still in the state of "NOT_FOUND"
|
|
if (param->read_list[i].result_code != ESP_ERR_NVS_NOT_FOUND) {
|
|
continue;
|
|
}
|
|
|
|
// try to match the key name
|
|
if (strncmp(param->read_list[i].key_name, item.key, NVS_KEY_NAME_MAX_SIZE) != 0) {
|
|
continue;
|
|
}
|
|
|
|
// check data type mismatch between the entry and the read_list, if data type requested and found differ, indicate it in the result code and skip the entry
|
|
if (param->read_list[i].value_type != item.data_type) {
|
|
param->read_list[i].result_code = ESP_ERR_NVS_TYPE_MISMATCH;
|
|
continue;
|
|
}
|
|
|
|
ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_key_value_pairs - matched item with read_list index: %u", (unsigned)i);
|
|
|
|
// for single entry data types, populate the value directly from the data of item
|
|
if (NVS_BOOTLOADER_TYPE_FITS_SINGLE_ENTRY(item.data_type)) {
|
|
memcpy((void*) ¶m->read_list[i].value, (void*) item.data.primitive_type.data, NVS_BOOTLOADER_GET_TYPE_LEN(item.data_type));
|
|
param->read_list[i].result_code = ESP_OK;
|
|
|
|
// we are done with this entry, continue with the next one
|
|
break;
|
|
}
|
|
// for a string, the data is stored in the entries following the metadata entry in item
|
|
else if (item.data_type == NVS_TYPE_STR) {
|
|
// check if the buffer length is long enough to hold the string data and if not, indicate it with ESP_ERR_INVALID_SIZE
|
|
if (param->read_list[i].value.str_val.buff_len < item.data.var_len_type.size) {
|
|
param->read_list[i].result_code = ESP_ERR_INVALID_SIZE;
|
|
// it does not make sense to continue with the next entry, as the the function doesn't support multiple entries for a single key
|
|
break;
|
|
}
|
|
|
|
// to get the actual data, entries at the index current_index + 1 onwards have to be read. If the result is different from ESP_OK, treat the situation as not found - it is technically a mismatch
|
|
esp_err_t ret_str = nvs_bootloader_read_entries_block(partition, page_index, &page_entry_states, current_index + 1, item.data.var_len_type.size, (uint8_t*)param->read_list[i].value.str_val.buff_ptr);
|
|
if (ret_str != ESP_OK) {
|
|
// it does not make sense to continue with the next entry, as the data is not found
|
|
break;
|
|
}
|
|
|
|
// calculate the crc32 of the data and compare it with the crc32 stored in the item. If they do not match, treat the situation as not found - it is technically a mismatch
|
|
uint32_t calc_crc = esp_rom_crc32_le(0xffffffff, (uint8_t*)(param->read_list[i].value.str_val.buff_ptr), item.data.var_len_type.size);
|
|
|
|
if (calc_crc != item.data.var_len_type.crc32) {
|
|
ESP_LOGV(TAG, "CRC32 of string data failed");
|
|
// it does not make sense to continue with the next entry, as the data is not found
|
|
break;
|
|
}
|
|
|
|
// set the result code to ESP_OK
|
|
param->read_list[i].result_code = ESP_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret == ESP_ERR_NVS_NOT_FOUND) {
|
|
ret = ESP_OK;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// visitor pattern for processing of all NVS pages of type "FULL" or "ACTIVE" xor "FREEING"
|
|
esp_err_t nvs_bootloader_visit_pages(const esp_partition_t *partition,
|
|
nvs_bootloader_page_visitor_t visitor,
|
|
nvs_bootloader_page_visitor_param_t *visitor_param)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
WORD_ALIGNED_ATTR nvs_bootloader_page_header_t page_header = {0}; // has to be 32 bit aligned
|
|
|
|
for (size_t page_index = 0; page_index < (partition->size / NVS_CONST_PAGE_SIZE); page_index++) {
|
|
// reading the NVS page header
|
|
ret = nvs_bootloader_read_page_header(partition, page_index, &page_header);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "Error %04x reading NVS page header of page %u", ret, (unsigned)page_index);
|
|
}
|
|
|
|
// just skip page with invalid header as it can be the case of uninitialized page
|
|
if (ret == ESP_ERR_NVS_INVALID_STATE) {
|
|
ret = ESP_OK;
|
|
continue;
|
|
}
|
|
|
|
// log the page header
|
|
ESP_LOGV(TAG, "nvs_bootloader_visit_pages - page %u, state: %" PRIu32 "", (unsigned)page_index, page_header.page_state);
|
|
|
|
// skip pages not in states "FULL", "ACTIVE" or "FREEING"
|
|
if (page_header.page_state != NVS_CONST_PAGE_STATE_FULL
|
|
&& page_header.page_state != NVS_CONST_PAGE_STATE_ACTIVE
|
|
&& page_header.page_state != NVS_CONST_PAGE_STATE_FREEING) {
|
|
continue;
|
|
}
|
|
|
|
// visit the page
|
|
ret = visitor(visitor_param, partition, page_index, &page_header);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "Error %04x visiting NVS page %u", ret, (unsigned)page_index);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read_page_header(const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_header_t *page_header)
|
|
{
|
|
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
ret = esp_partition_read(partition, page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_HEADER_OFFSET, (void*)page_header, sizeof(nvs_bootloader_page_header_t));
|
|
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
// calculate the CRC32 of the page header
|
|
uint32_t calc_crc = esp_rom_crc32_le(0xffffffff, (uint8_t*)page_header + offsetof(nvs_bootloader_page_header_t, sequence_number), offsetof(nvs_bootloader_page_header_t, crc32) - offsetof(nvs_bootloader_page_header_t, sequence_number));
|
|
if (calc_crc != page_header->crc32) {
|
|
ESP_LOGV(TAG, "CRC32 of page# %u header failed", (unsigned)page_index);
|
|
return ESP_ERR_NVS_INVALID_STATE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read_page_entry_states(const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
nvs_bootloader_page_entry_states_t *page_entry_states)
|
|
{
|
|
// log the read_page_entry_states parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_namespaces - reading page entry states for page %u", (unsigned)page_index);
|
|
|
|
return esp_partition_read(partition, page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_TABLE_OFFSET, (void*)page_entry_states, sizeof(nvs_bootloader_page_entry_states_t));
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read_next_single_entry_item(const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_entry_states_t *page_entry_states,
|
|
uint8_t *entry_index,
|
|
nvs_bootloader_single_entry_t *item)
|
|
{
|
|
// log parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_read_next_single_entry_item called with page_index: %u, entry_index: %d", (unsigned)page_index, *entry_index);
|
|
|
|
// check if the start index is valid. Not found is signal to the caller that there are no more entries to read
|
|
if (*entry_index >= NVS_CONST_ENTRY_COUNT) {
|
|
return ESP_ERR_NVS_NOT_FOUND;
|
|
}
|
|
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
// find the next single entry item
|
|
while ((*entry_index) < NVS_CONST_ENTRY_COUNT) {
|
|
// If the entry is written, try to read and process it
|
|
if (NVS_BOOTLOADER_GET_ENTRY_STATE(page_entry_states, *entry_index) == NVS_CONST_ENTRY_STATE_WRITTEN) {
|
|
|
|
ret = esp_partition_read(partition, page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + (*entry_index) * NVS_CONST_ENTRY_SIZE, (void*)item, sizeof(nvs_bootloader_single_entry_t));
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
if (is_nvs_partition_encrypted) {
|
|
ret = decrpyt_data(page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + (*entry_index) * NVS_CONST_ENTRY_SIZE, (void*)item, sizeof(nvs_bootloader_single_entry_t));
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// only look at item with consistent header
|
|
if (nvs_bootloader_check_item_header_consistency(item, *entry_index)) {
|
|
// advance the start index
|
|
(*entry_index) += item->span;
|
|
|
|
// if the item is single entry value data type, return ESP_OK
|
|
if (NVS_BOOTLOADER_IS_SUPPORTED_TYPE(item->data_type)) {
|
|
return ESP_OK;
|
|
}
|
|
}
|
|
// inconsistent header, try next entry
|
|
else {
|
|
(*entry_index)++;
|
|
}
|
|
|
|
}
|
|
// if the entry is not written just try next entry
|
|
else {
|
|
(*entry_index)++;
|
|
}
|
|
}
|
|
|
|
return ESP_ERR_NVS_NOT_FOUND;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read_entries_block(const esp_partition_t *partition,
|
|
const size_t page_index,
|
|
const nvs_bootloader_page_entry_states_t *entry_states,
|
|
const size_t entry_index,
|
|
const size_t block_len,
|
|
uint8_t *block)
|
|
{
|
|
// log parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_read_entries_block called with page_index: %u, entry_index: %u, block_len: %u", (unsigned)page_index, (unsigned)entry_index, (unsigned)block_len);
|
|
|
|
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
|
|
|
do {
|
|
// check if the start index is valid
|
|
if (entry_index >= NVS_CONST_ENTRY_COUNT) {
|
|
ESP_LOGV(TAG, "entry index is beyond the page boundary");
|
|
break;
|
|
}
|
|
|
|
// calculate the number of entries necessary to read the block
|
|
size_t number_of_entries = block_len / NVS_CONST_ENTRY_SIZE;
|
|
if (block_len % NVS_CONST_ENTRY_SIZE != 0) {
|
|
number_of_entries++;
|
|
}
|
|
|
|
// check if the highest entry to be read still fits the page size
|
|
if (entry_index + number_of_entries > NVS_CONST_ENTRY_COUNT) {
|
|
ESP_LOGV(TAG, "block length exceeds the page boundary");
|
|
break;
|
|
}
|
|
|
|
// check if all entries are in the state written
|
|
bool all_entries_written = true;
|
|
for (size_t i = 0; i < number_of_entries; i++) {
|
|
if (NVS_BOOTLOADER_GET_ENTRY_STATE(entry_states, entry_index + i) != NVS_CONST_ENTRY_STATE_WRITTEN) {
|
|
ESP_LOGV(TAG, "some entry is not in the state written");
|
|
all_entries_written = false;
|
|
break;
|
|
}
|
|
}
|
|
if(!all_entries_written) {
|
|
break;
|
|
}
|
|
|
|
ret = ESP_OK;
|
|
} while (false);
|
|
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
size_t data_offset = page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + entry_index * NVS_CONST_ENTRY_SIZE;
|
|
|
|
return nvs_bootloader_partition_read_string_value(partition, data_offset, block, block_len);
|
|
}
|
|
|
|
// validates item's header
|
|
// CRC is checked
|
|
// zero span is checked
|
|
// span is validated using the data type
|
|
// span is checked not to exceed the remaining number of entries in the page
|
|
bool nvs_bootloader_check_item_header_consistency(const nvs_bootloader_single_entry_t *item,
|
|
const uint8_t entry_index)
|
|
{
|
|
|
|
// check crc of item
|
|
uint32_t calc_crc = esp_rom_crc32_le(0xffffffff, (uint8_t*)item + offsetof(nvs_bootloader_single_entry_t, namespace_index), offsetof(nvs_bootloader_single_entry_t, crc32) - offsetof(nvs_bootloader_single_entry_t, namespace_index));
|
|
calc_crc = esp_rom_crc32_le(calc_crc, (uint8_t*)item + offsetof(nvs_bootloader_single_entry_t, key), sizeof(item->key));
|
|
calc_crc = esp_rom_crc32_le(calc_crc, (uint8_t*)item + offsetof(nvs_bootloader_single_entry_t, data), sizeof(item->data));
|
|
|
|
bool ret = false;
|
|
|
|
do {
|
|
if (calc_crc != item->crc32) {
|
|
ESP_LOGV(TAG, "Header CRC32 failed for item with key %s", item->key);
|
|
break;
|
|
}
|
|
|
|
// check zero span
|
|
if (item->span == 0) {
|
|
ESP_LOGV(TAG, "Span of key %s is zero", item->key);
|
|
break;
|
|
}
|
|
|
|
// check span according to the data type
|
|
if (NVS_BOOTLOADER_TYPE_FITS_SINGLE_ENTRY(item->data_type) && item->span != 1) {
|
|
ESP_LOGV(TAG, "Span of single entry key %s is not 1", item->key);
|
|
break;
|
|
}
|
|
|
|
// check span according to the remaining space in the page
|
|
if ((uint16_t)entry_index + (uint16_t)(item->span) > NVS_CONST_ENTRY_COUNT) {
|
|
ESP_LOGV(TAG, "Span of key %s exceeds remaining page space", item->key);
|
|
break;
|
|
}
|
|
|
|
// check span for string data type. The span has to be able to hold the data length but should not occupy more than necessary entries
|
|
if (item->data_type == NVS_TYPE_STR) {
|
|
if( ((item->span - 1) * NVS_CONST_ENTRY_SIZE < item->data.var_len_type.size)
|
|
|| ((item->span - 1) * NVS_CONST_ENTRY_SIZE > item->data.var_len_type.size + NVS_CONST_ENTRY_SIZE - 1) ) {
|
|
ESP_LOGV(TAG, "Span %u of key %s for string type doesn't match indicated string length %u", (unsigned)item->span, item->key, item->data.var_len_type.size);
|
|
break;
|
|
}
|
|
}
|
|
ret = true;
|
|
} while (false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read(const char* partition_name,
|
|
const size_t read_list_count,
|
|
nvs_bootloader_read_list_t read_list[])
|
|
{
|
|
/*
|
|
The flow:
|
|
|
|
1. validate parameters and if there are any, report errors.
|
|
2. check if the partition exists
|
|
3. read the partition and browse all NVS pages to figure out whether there is any page in the state of "FREEING"
|
|
3.1. if there is a page in the state of "FREEING", then reading from the page marked as "ACTIVE" will be skipped
|
|
4. read entries from pages marked as "FULL" or ("ACTIVE" xor "FREEING") to identify namespace indexes of the requested namespaces
|
|
5. read the requested entries, same skipping of pages in the state of "ACTIVE" as in step 4
|
|
|
|
*/
|
|
// load input parameters
|
|
ESP_LOGD(TAG, "nvs_bootloader_read called with partition_name: %s, read_list_count: %u", partition_name, (unsigned)read_list_count);
|
|
|
|
// Placeholder return value, replace with actual error handling
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
// Check if the parameters are valid
|
|
ret = nvs_bootloader_check_parameters(partition_name, read_list_count, read_list);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGD(TAG, "nvs_bootloader_check_parameters failed");
|
|
return ret;
|
|
}
|
|
|
|
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, partition_name);
|
|
if (partition == NULL) {
|
|
ESP_LOGV(TAG, "esp_partition_find_first failed");
|
|
return ESP_ERR_NVS_PART_NOT_FOUND;
|
|
}
|
|
|
|
// log the partition details
|
|
ESP_LOGV(TAG, "Partition %s found, size is: %" PRIu32 "", partition->label, partition->size);
|
|
|
|
// visit pages to get the number of pages in the state of "ACTIVE" and "FREEING"
|
|
nvs_bootloader_page_visitor_param_get_page_states_t page_states_count = {0};
|
|
|
|
ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_page_states, (nvs_bootloader_page_visitor_param_t*)&page_states_count);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "Failed reading page states");
|
|
return ret;
|
|
}
|
|
if (page_states_count.no_freeing_pages > 1) {
|
|
ESP_LOGV(TAG, "Multiple pages in the state of FREEING");
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
if (page_states_count.no_active_pages > 1) {
|
|
ESP_LOGV(TAG, "Multiple pages in the state of ACTIVE");
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
// Here we have at most 1 active page and at most 1 freeing page. If there exists a freeing page, we will skip reading from the active page
|
|
// Visit pages to get the namespace indexes of the requested namespaces
|
|
nvs_bootloader_page_visitor_param_read_entries_t read_entries = { .skip_active_page = page_states_count.no_freeing_pages > 0, .read_list_count = read_list_count, .read_list = read_list };
|
|
|
|
// log the visitor parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get namespace indexes");
|
|
|
|
ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_namespaces, (nvs_bootloader_page_visitor_param_t*) &read_entries);
|
|
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_namespaces failed");
|
|
return ret;
|
|
}
|
|
|
|
// log the visitor parameters
|
|
ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get key - value pairs");
|
|
|
|
// Visit pages to read the requested key - value pairs
|
|
ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_key_value_pairs, (nvs_bootloader_page_visitor_param_t*) &read_entries);
|
|
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_secure_init(const nvs_sec_cfg_t *sec_cfg)
|
|
{
|
|
#if CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD
|
|
// To enable usage of mbedtls APIs form the ROM, we need to initialize the rom mbedtls functions table pointer.
|
|
// In case of application, a constructor present in the mbedtls component initializes the rom mbedtls functions table pointer during boot up.
|
|
mbedtls_rom_osi_functions_init_bootloader();
|
|
#endif /* CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD */
|
|
|
|
nvs_bootloader_xts_aes_init(&dec_ctx);
|
|
|
|
esp_err_t ret = nvs_bootloader_xts_aes_setkey(&dec_ctx, (const uint8_t*) sec_cfg, 2 * NVS_KEY_SIZE);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
is_nvs_partition_encrypted = true;
|
|
return ret;
|
|
}
|
|
|
|
void nvs_bootloader_secure_deinit(void)
|
|
{
|
|
if (is_nvs_partition_encrypted) {
|
|
nvs_bootloader_xts_aes_free(&dec_ctx);
|
|
is_nvs_partition_encrypted = false;
|
|
}
|
|
}
|
|
|
|
esp_err_t nvs_bootloader_read_security_cfg(nvs_sec_scheme_t *scheme_cfg, nvs_sec_cfg_t* cfg)
|
|
{
|
|
if (scheme_cfg == NULL || cfg == NULL || scheme_cfg->nvs_flash_read_cfg == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
return (scheme_cfg->nvs_flash_read_cfg)(scheme_cfg->scheme_data, cfg);
|
|
}
|