mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
[doc]: NVS documentation updates
* Move nvs flash README to common doc directory * correct markup of functions and types in text from old README * Better comment of nvs_get_used_entry_count() * Mention C++ example in API reference * Used target instead of hard code ESP32 * Note that strings can only span one page * Reflect that item types have been moved * Some clarification about nvs_commit() * Improved reference to the ESP Partition API * fixed little mistake in documenting-code.rst * Change of nvs_open_from_part() to nvs_open_from_partition() reflected in docs * Corrected documentation of NVSHandle::get_string(), NVSHandle::get_blob() and NVSHandle::get_item_size(). * Closes DOC-165 * Closes IDF-1563 * Closes IDF-859 * Closes https://github.com/espressif/esp-idf/issues/6123
This commit is contained in:
parent
98c20ce417
commit
5395f451a2
@ -1,340 +0,0 @@
|
||||
Non-volatile storage library
|
||||
============================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This section introduces some concepts used by NVS.
|
||||
|
||||
|
||||
Underlying storage
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Currently, NVS uses a portion of main flash memory through ``spi_flash_{read|write|erase}`` APIs. The library uses all the partitions with ``data`` type and ``nvs`` subtype. The application can choose to use the partition with the label ``nvs`` through the ``nvs_open`` API function or any other partition by specifying its name using the ``nvs_open_from_part`` API function.
|
||||
|
||||
Future versions of this library may have other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc.
|
||||
|
||||
.. note:: if an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase_flash`` target to erase all contents of the flash chip.
|
||||
|
||||
.. note:: NVS works best for storing many small values, rather than a few large values of the type 'string' and 'blob'. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library.
|
||||
|
||||
|
||||
Keys and values
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length is currently 15 characters. Values can have one of the following types:
|
||||
|
||||
- integer types: ``uint8_t``, ``int8_t``, ``uint16_t``, ``int16_t``, ``uint32_t``, ``int32_t``, ``uint64_t``, ``int64_t``
|
||||
- zero-terminated string
|
||||
- variable length binary data (blob)
|
||||
|
||||
.. note::
|
||||
|
||||
String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower.
|
||||
|
||||
Additional types, such as ``float`` and ``double`` might be added later.
|
||||
|
||||
Keys are required to be unique. Assigning a new value to an existing key works as follows:
|
||||
|
||||
- if the new value is of the same type as the old one, value is updated
|
||||
- if the new value has a different data type, an error is returned
|
||||
|
||||
Data type check is also performed when reading a value. An error is returned if the data type of the read operation does not match the data type of the value.
|
||||
|
||||
|
||||
Namespaces
|
||||
^^^^^^^^^^
|
||||
|
||||
To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Namespace name is specified in the ``nvs_open`` or ``nvs_open_from_part`` call. This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and ``nvs_commit`` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces.
|
||||
Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces.
|
||||
|
||||
|
||||
Security, tampering, and robustness
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS is not directly compatible with the ESP32 flash encryption system. However, data can still be stored in encrypted form if NVS encryption is used together with ESP32 flash encryption. Please refer to :ref:`nvs_encryption` for more details.
|
||||
|
||||
If NVS encryption is not used, it is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. With NVS encryption enabled, it is not possible to alter or add a key-value pair and get recognized as a valid pair without knowing corresponding NVS encryption keys. However, there is no tamper-resistance against the erase operation.
|
||||
|
||||
The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, except for the new key-value pair if it was being written at the moment of powering off. The library should also be able to initialize properly with any random data present in flash memory.
|
||||
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
Log of key-value pairs
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, a new key-value pair is added at the end of the log and the old key-value pair is marked as erased.
|
||||
|
||||
Pages and entries
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states:
|
||||
|
||||
Empty/uninitialized
|
||||
Flash storage for the page is empty (all bytes are ``0xff``). Page is not used to store any data at this point and does not have a sequence number.
|
||||
|
||||
Active
|
||||
Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment.
|
||||
|
||||
Full
|
||||
Flash storage is in a consistent state and is filled with key-value pairs.
|
||||
Writing new key-value pairs into this page is not possible. It is still possible to mark some key-value pairs as erased.
|
||||
|
||||
Erasing
|
||||
Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e., page should never stay in this state at the time when any API call returns. In case of a sudden power off, the move-and-erase process will be completed upon the next power-on.
|
||||
|
||||
Corrupted
|
||||
Page header contains invalid data, and further parsing of page data was canceled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in *uninitialized* state for later use. This may be useful for debugging.
|
||||
|
||||
Mapping from flash sectors to logical pages does not have any particular order. The library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers.
|
||||
|
||||
::
|
||||
|
||||
+--------+ +--------+ +--------+ +--------+
|
||||
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
|
||||
| Full +---> | Full +---> | Active | | Empty | <- states
|
||||
| #11 | | #12 | | #14 | | | <- sequence numbers
|
||||
+---+----+ +----+---+ +----+---+ +---+----+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+---v------+ +-----v----+ +------v---+ +------v---+
|
||||
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- physical sectors
|
||||
+----------+ +----------+ +----------+ +----------+
|
||||
|
||||
Structure of a page
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For now, we assume that flash sector size is 4096 bytes and that ESP32 flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g., via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g., SPI flash driver and SPI flash cache can support these other sizes).
|
||||
|
||||
Page consists of three parts: header, entry state bitmap, and entries themselves. To be compatible with ESP32 flash encryption, entry size is 32 bytes. For integer types, entry holds one key-value pair. For strings and blobs, an entry holds part of key-value pair (more on that in the entry structure description).
|
||||
|
||||
The following diagram illustrates the page structure. Numbers in parentheses indicate the size of each part in bytes. ::
|
||||
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | Header (32)
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| Entry state bitmap (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 0 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 1 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
/ /
|
||||
/ /
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 125 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
|
||||
Page header and entry state bitmap are always written to flash unencrypted. Entries are encrypted if flash encryption feature of ESP32 is used.
|
||||
|
||||
Page state values are defined in such a way that changing state is possible by writing 0 into some of the bits. Therefore it is not necessary to erase the page to change its state unless that is a change to the *erased* state.
|
||||
|
||||
The version field in the header reflects the NVS format version used. For backward compatibility reasons, it is decremented for every version upgrade starting at 0xff (i.e., 0xff for version-1, 0xfe for version-2 and so on).
|
||||
|
||||
CRC32 value in the header is calculated over the part which does not include a state value (bytes 4 to 28). The unused part is currently filled with ``0xff`` bytes.
|
||||
|
||||
The following sections describe the structure of entry state bitmap and entry itself.
|
||||
|
||||
Entry and entry state bitmap
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Each entry can be in one of the following three states represented with two bits in the entry state bitmap. The final four bits in the bitmap (256 - 2 * 126) are not used.
|
||||
|
||||
Empty (2'b11)
|
||||
Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes are ``0xff``).
|
||||
|
||||
Written (2'b10)
|
||||
A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry.
|
||||
|
||||
Erased (2'b00)
|
||||
A key-value pair in this entry has been discarded. Contents of this entry will not be parsed anymore.
|
||||
|
||||
|
||||
.. _structure_of_entry:
|
||||
|
||||
Structure of entry
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. For strings, in case when a key-value pair spans multiple entries, all entries are stored in the same page. Blobs are allowed to span over multiple pages by dividing them into smaller chunks. For tracking these chunks, an additional fixed length metadata entry is stored called "blob index". Earlier formats of blobs are still supported (can be read and modified). However, once the blobs are modified, they are stored using the new format.
|
||||
|
||||
::
|
||||
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
| NS (1) | Type (1) | Span (1) | ChunkIndex (1) | CRC32 (4) | Key (16) | Data (8) |
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
|
||||
Primitive +--------------------------------+
|
||||
+--------> | Data (8) |
|
||||
| Types +--------------------------------+
|
||||
+-> Fixed length --
|
||||
| | +---------+--------------+---------------+-------+
|
||||
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
|
||||
Data format ---+ Blob Index +---------+--------------+---------------+-------+
|
||||
|
|
||||
| +----------+---------+-----------+
|
||||
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
|
||||
(Strings, Blob Data) +----------+---------+-----------+
|
||||
|
||||
|
||||
Individual fields in entry structure have the following meanings:
|
||||
|
||||
NS
|
||||
Namespace index for this entry. For more information on this value, see the section on namespaces implementation.
|
||||
|
||||
Type
|
||||
One byte indicating the value data type. See the ``ItemType`` enumeration in ``nvs_types.h`` for possible values.
|
||||
|
||||
Span
|
||||
Number of entries used by this key-value pair. For integer types, this is equal to 1. For strings and blobs, this depends on value length.
|
||||
|
||||
ChunkIndex
|
||||
Used to store the index of a blob-data chunk for blob types. For other types, this should be ``0xff``.
|
||||
|
||||
CRC32
|
||||
Checksum calculated over all the bytes in this entry, except for the CRC32 field itself.
|
||||
|
||||
Key
|
||||
Zero-terminated ASCII string containing a key name. Maximum string length is 15 bytes, excluding a zero terminator.
|
||||
|
||||
Data
|
||||
For integer types, this field contains the value itself. If the value itself is shorter than 8 bytes, it is padded to the right, with unused bytes filled with ``0xff``.
|
||||
|
||||
For "blob index" entry, these 8 bytes hold the following information about data-chunks:
|
||||
|
||||
- Size
|
||||
(Only for blob index.) Size, in bytes, of complete blob data.
|
||||
|
||||
- ChunkCount
|
||||
(Only for blob index.) Total number of blob-data chunks into which the blob was divided during storage.
|
||||
|
||||
- ChunkStart
|
||||
(Only for blob index.) ChunkIndex of the first blob-data chunk of this blob. Subsequent chunks have chunkIndex incrementally allocated (step of 1).
|
||||
|
||||
For string and blob data chunks, these 8 bytes hold additional data about the value, which are described below:
|
||||
|
||||
- Size
|
||||
(Only for strings and blobs.) Size, in bytes, of actual data. For strings, this includes zero terminators.
|
||||
|
||||
- CRC32
|
||||
(Only for strings and blobs.) Checksum calculated over all bytes of data.
|
||||
|
||||
Variable length values (strings and blobs) are written into subsequent entries, 32 bytes per entry. The `Span` field of the first entry indicates how many entries are used.
|
||||
|
||||
|
||||
Namespaces
|
||||
^^^^^^^^^^
|
||||
|
||||
As mentioned above, each key-value pair belongs to one of the namespaces. Namespace identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces.
|
||||
|
||||
::
|
||||
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
| NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
|
||||
|
||||
Item hash list
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
To reduce the number of reads from flash memory, each member of the Page class maintains a list of pairs: item index; item hash. This list makes searches much quicker. Instead of iterating over all entries, reading them from flash one at a time, ``Page::findItem`` first performs a search for the item hash in the hash list. This gives the item index within the page if such an item exists. Due to a hash collision, it is possible that a different item will be found. This is handled by falling back to iteration over items in flash.
|
||||
|
||||
Each node in the hash list contains a 24-bit hash and 8-bit item index. Hash is calculated based on item namespace, key name, and ChunkIndex. CRC32 is used for calculation; the result is truncated to 24 bits. To reduce the overhead for storing 32-bit entries in a linked list, the list is implemented as a double-linked list of arrays. Each array holds 29 entries, for the total size of 128 bytes, together with linked list pointers and a 32-bit count field. The minimum amount of extra RAM usage per page is therefore 128 bytes; maximum is 640 bytes.
|
||||
|
||||
.. _nvs_encryption:
|
||||
|
||||
NVS Encryption
|
||||
--------------
|
||||
|
||||
Data stored in NVS partitions can be encrypted using AES-XTS in the manner similar to the one mentioned in disk encryption standard IEEE P1619. For the purpose of encryption, each entry is treated as one `sector` and relative address of the entry (w.r.t. partition-start) is fed to the encryption algorithm as `sector-number`. The NVS Encryption can be enabled by enabling :ref:`CONFIG_NVS_ENCRYPTION`. The keys required for NVS encryption are stored in yet another partition, which is protected using :doc:`Flash Encryption <../../security/flash-encryption>`. Therefore, enabling :doc:`Flash Encryption <../../security/flash-encryption>` is a prerequisite for NVS encryption.
|
||||
|
||||
The NVS Encryption is enabled by default when :doc:`Flash Encryption <../../security/flash-encryption>` is enabled. This is done because WiFi driver stores credentials (like SSID and passphrase) in the default NVS partition. It is important to encrypt them as default choice if platform level encryption is already enabled.
|
||||
|
||||
For using NVS encryption, the partition table must contain the :ref:`nvs_key_partition`. Two partition tables containing the :ref:`nvs_key_partition` are provided for NVS encryption under the partition table option (menuconfig->Partition Table). They can be selected with the project configuration menu (``idf.py menuconfig``). Please refer to the example :example:`security/flash_encryption` for how to configure and use NVS encryption feature.
|
||||
|
||||
.. _nvs_key_partition:
|
||||
|
||||
NVS key partition
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
An application requiring NVS encryption support needs to be compiled with a key-partition of the type `data` and subtype `key`. This partition should be marked as `encrypted`. Refer to :doc:`Partition Tables <../../api-guides/partition-tables>` for more details. Two additional partition tables which contain the :ref:`nvs_key_partition` are provided under the partition table option (menuconfig->Partition Table). They can be directly used for :ref:`nvs_encryption`. The structure of these partitions is depicted below.
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key(32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32(4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
The XTS encryption keys in the :ref:`nvs_key_partition` can be generated with one of the following two ways.
|
||||
|
||||
1. Generate the keys on the ESP chip:
|
||||
|
||||
When NVS encryption is enabled the :cpp:func:`nvs_flash_init` API function can be used to initialize the encrypted default NVS partition. The API function internally generates the XTS encryption keys on the ESP chip. The API function finds the first :ref:`nvs_key_partition`.
|
||||
Then the API function automatically generates and stores the nvs keys in that partition by making use of the :cpp:func:`nvs_flash_generate_keys` API function provided by ``nvs_flash.h``. New keys are generated and stored only when the respective key partiton is empty. The same key partition can then be used to read the security configurations for initializing a custom encrypted NVS partition with help of :cpp:func:`nvs_flash_secure_init_partition`.
|
||||
|
||||
The API functions :cpp:func:`nvs_flash_secure_init` and :cpp:func:`nvs_flash_secure_init_partition` do not generate the keys internally. When these API functions are used for initializing encrypted NVS partitions, the keys can be generated after startup using the :cpp:func:`nvs_flash_generate_keys` API function provided by ``nvs_flash.h``. The API function will then write those keys onto the key-partition in encrypted form.
|
||||
|
||||
2. Use pre-generated key partition:
|
||||
|
||||
This option will be required by the user when keys in the :ref:`nvs_key_partition` are not generated by the application. The :ref:`nvs_key_partition` containing the XTS encryption keys can be generated with the help of :doc:`NVS Partition Generator Utility</api-reference/storage/nvs_partition_gen>`. Then the user can store the pre generated key partition on the flash with help of the following two commands:
|
||||
|
||||
i) Build and flash the partition table
|
||||
::
|
||||
|
||||
idf.py partition_table partition_table-flash
|
||||
|
||||
ii) Store the keys in the :ref:`nvs_key_partition` (on the flash) with the help of :component_file:`parttool.py<partition_table/parttool.py>` (see Partition Tool section in :doc:`partition-tables </api-guides/partition-tables>` for more details)
|
||||
::
|
||||
|
||||
parttool.py --port /dev/ttyUSB0 --partition-table-offset "nvs_key partition offset" write_partition --partition-name="name of nvs_key partition" --input "nvs_key partition"
|
||||
|
||||
Since the key partition is marked as `encrypted` and :doc:`Flash Encryption <../../security/flash-encryption>` is enabled, the bootloader will encrypt this partition using flash encryption key on the first boot.
|
||||
|
||||
It is possible for an application to use different keys for different NVS partitions and thereby have multiple key-partitions. However, it is a responsibility of the application to provide correct key-partition/keys for the purpose of encryption/decryption.
|
||||
|
||||
Encrypted Read/Write
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The same NVS API functions ``nvs_get_*`` or ``nvs_set_*`` can be used for reading of, and writing to an encrypted nvs partition as well.
|
||||
|
||||
**Encrypt the default NVS partition:**
|
||||
To enable encryption for the default NVS partition no additional steps are necessary. When :ref:`CONFIG_NVS_ENCRYPTION` is enabled, the :cpp:func:`nvs_flash_init` API function internally performs some additional steps using the first :ref:`nvs_key_partition` found to enable encryption for the default NVS partition (refer to the API documentation for more details). Alternatively, :cpp:func:`nvs_flash_secure_init` API function can also be used to enable encryption for the default NVS partition.
|
||||
|
||||
**Encrypt a custom NVS partition:**
|
||||
To enable encryption for a custom NVS partition, :cpp:func:`nvs_flash_secure_init_partition` API function is used instead of :cpp:func:`nvs_flash_init_partition`.
|
||||
|
||||
When :cpp:func:`nvs_flash_secure_init` and :cpp:func:`nvs_flash_secure_init_partition` API functions are used, the applications are expected to follow the steps below in order to perform NVS read/write operations with encryption enabled.
|
||||
|
||||
1. Find key partition and NVS data partition using ``esp_partition_find*`` API functions.
|
||||
2. Populate the ``nvs_sec_cfg_t`` struct using the ``nvs_flash_read_security_cfg`` or ``nvs_flash_generate_keys`` API functions.
|
||||
3. Initialise NVS flash partition using the ``nvs_flash_secure_init`` or ``nvs_flash_secure_init_partition`` API functions.
|
||||
4. Open a namespace using the ``nvs_open`` or ``nvs_open_from_part`` API functions.
|
||||
5. Perform NVS read/write operations using ``nvs_get_*`` or ``nvs_set_*``.
|
||||
6. Deinitialise an NVS partition using ``nvs_flash_deinit``.
|
||||
|
||||
NVS iterators
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Iterators allow to list key-value pairs stored in NVS, based on specified partition name, namespace, and data type.
|
||||
|
||||
There are the following functions available:
|
||||
|
||||
- ``nvs_entry_find`` returns an opaque handle, which is used in subsequent calls to the ``nvs_entry_next`` and ``nvs_entry_info`` functions.
|
||||
- ``nvs_entry_next`` returns iterator to the next key-value pair.
|
||||
- ``nvs_entry_info`` returns information about each key-value pair
|
||||
|
||||
If none or no other key-value pair was found for given criteria, ``nvs_entry_find`` and ``nvs_entry_next`` return NULL. In that case, the iterator does not have to be released. If the iterator is no longer needed, you can release it by using the function ``nvs_release_iterator``.
|
@ -1,303 +0,0 @@
|
||||
非易失性存储库
|
||||
============================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
简介
|
||||
------------
|
||||
|
||||
非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。本文档将详细介绍 NVS 常用的一些概念。
|
||||
|
||||
底层存储
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 通过调用 ``spi_flash_{read|write|erase}`` API 对主 flash 的部分空间进行读、写、擦除操作,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 ``nvs_open`` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 ``nvs_open_from_part`` API 选择使用指定名称的任意分区。
|
||||
|
||||
NVS 库后续版本可能会增加其他存储器后端,实现将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。
|
||||
|
||||
.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase_flash`` 命令擦除 flash 上的所有内容。
|
||||
|
||||
.. note:: NVS 最适合存储一些较小的数据,而非字符串或二进制大对象 (BLOB) 等较大的数据。如需存储较大的 BLOB 或者字符串,请考虑使用基于磨损均衡库的 FAT 文件系统。
|
||||
|
||||
|
||||
键值对
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型:
|
||||
|
||||
- 整数型:``uint8_t``、``int8_t``、``uint16_t``、``int16_t``、``uint32_t``、``int32_t``、``uint64_t`` 和 ``int64_t``;
|
||||
- 以 ``\0`` 结尾的字符串;
|
||||
- 可变长度的二进制数据 (BLOB)
|
||||
|
||||
.. note::
|
||||
|
||||
字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小减去 4000 字节的 97.6%,以较低值为准。
|
||||
|
||||
后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。
|
||||
|
||||
键必须唯一。为现有的键写入新的值可能产生如下结果:
|
||||
|
||||
- 如果新旧值数据类型相同,则更新值;
|
||||
- 如果新旧值数据类型不同,则返回错误。
|
||||
|
||||
读取值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则返回错误。
|
||||
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
|
||||
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,即最多可占 15 个字符。命名空间的名称在调用 ``nvs_open`` 或 ``nvs_open_from_part`` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
|
||||
|
||||
|
||||
安全性、篡改性及鲁棒性
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 与 ESP32 flash 加密系统不直接兼容。但如果 NVS 加密与 ESP32 flash 加密一起使用时,数据仍可以加密形式存储。更多详情请参阅 :ref:`nvs_encryption`。
|
||||
|
||||
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的人都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
|
||||
|
||||
当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应当能对 flash 中的任意数据进行正确初始化。
|
||||
|
||||
|
||||
内部实现
|
||||
---------
|
||||
|
||||
键值对日志
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需更新某一键值对,实际是在日志最后增加一对新的键值对,同时将旧的键值对标记为已擦除。
|
||||
|
||||
页面和条目
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的序列号。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
|
||||
|
||||
空或未初始化
|
||||
页面对应的 flash 扇区为空白状态(所有字节均为 ``0xff``)。此时,页面未存储任何数据且没有关联的序列号。
|
||||
|
||||
活跃状态
|
||||
此时 flash 已完成初始化,页头部写入 flash,页面已具备有效序列号。页面中存在一些空条目,可写入数据。任意时刻,至多有一个页面处于活跃状态。
|
||||
|
||||
写满状态
|
||||
Flash 已写满键值对,状态不再改变。用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
|
||||
|
||||
擦除状态
|
||||
未擦除的键值对将移至其他页面,以便擦除当前页面。这一状态仅为暂时性状态,即 API 调用返回时,页面应脱离这一状态。如果设备突然断电,下次开机时,设备将继续把未擦除的键值对移至其他页面,并继续擦除当前页面。
|
||||
|
||||
损坏状态
|
||||
页头部包含无效数据,无法进一步解析该页面中的数据,因此之前写入该页面的所有条目均无法访问。相应的 flash 扇区并不会被立即擦除,而是与其他处于未初始化状态的扇区一起等待后续使用。这一状态可能对调试有用。
|
||||
|
||||
Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存储在 flash 扇区的页面序列号,并根据序列号组织页面。
|
||||
|
||||
::
|
||||
|
||||
+--------+ +--------+ +--------+ +--------+
|
||||
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
|
||||
| Full +---> | Full +---> | Active | | Empty | <- 状态
|
||||
| #11 | | #12 | | #14 | | | <- 序列号
|
||||
+---+----+ +----+---+ +----+---+ +---+----+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+---v------+ +-----v----+ +------v---+ +------v---+
|
||||
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- 物理扇区
|
||||
+----------+ +----------+ +----------+ +----------+
|
||||
|
||||
页面结构
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
当前,我们假设 flash 扇区大小为 4096 字节,并且 ESP32 flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
|
||||
|
||||
页面由头部、条目状态位图和条目三部分组成。为了实现与 ESP32 flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
|
||||
|
||||
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)::
|
||||
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | 页头部 (32)
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| Entry state bitmap (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 0 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 1 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
/ /
|
||||
/ /
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 125 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
|
||||
头部和条目状态位图写入 flash 时不加密。如果启用了 ESP32 flash 加密功能,则条目写入 flash 时将会加密。
|
||||
|
||||
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为擦除状态。
|
||||
|
||||
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减(例如,version-1 为 0xff,version-2 为 0xfe 等)。
|
||||
|
||||
头部中 CRC32 值是由不包含状态值的条目计算所得(4 到 28 字节)。当前未使用的条目用 ``0xff`` 字节填充。
|
||||
|
||||
条目结构和条目状态位图详细信息见下文描述。
|
||||
|
||||
条目和条目状态位图
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
每个条目可处于以下三种状态之一,每个状态在条目状态位图中用两位表示。位图中的最后四位 (256 - 2 * 126) 未使用。
|
||||
|
||||
空 (2'b11)
|
||||
条目还未写入任何内容,处于未初始化状态(全部字节为 ``0xff``)。
|
||||
|
||||
写入(2'b10)
|
||||
一个键值对(或跨多个条目的键值对的部分内容)已写入条目中。
|
||||
|
||||
擦除(2'b00)
|
||||
条目中的键值对已丢弃,条目内容不再解析。
|
||||
|
||||
.. _structure_of_entry:
|
||||
|
||||
条目结构
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
如果键值类型为基础类型,即 1 - 8 个字节长度的整数型,条目将保存一个键值对;如果键值类型为字符串或 BLOB 类型,条目将保存整个键值对的部分内容。另外,如果键值为字符串类型且跨多个条目,则键值所跨的所有条目均保存在同一页面。BLOB 则可以切分为多个块,实现跨多个页面。BLOB 索引是一个附加的固定长度元数据条目,用于追踪 BLOB 块。目前条目仍支持早期 BLOB 格式(可读取可修改),但这些 BLOB 一经修改,即以新格式储存至条目。
|
||||
|
||||
::
|
||||
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
| NS (1) | Type (1) | Span (1) | ChunkIndex (1) | CRC32 (4) | Key (16) | Data (8) |
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
|
||||
Primitive +--------------------------------+
|
||||
+--------> | Data (8) |
|
||||
| Types +--------------------------------+
|
||||
+-> Fixed length --
|
||||
| | +---------+--------------+---------------+-------+
|
||||
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
|
||||
Data format ---+ BLOB Index +---------+--------------+---------------+-------+
|
||||
|
|
||||
| +----------+---------+-----------+
|
||||
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
|
||||
(Strings, BLOB Data) +----------+---------+-----------+
|
||||
|
||||
|
||||
条目结构中各个字段含义如下:
|
||||
|
||||
命名空间 (NS, NameSpace)
|
||||
该条目的命名空间索引,详细信息见命名空间实现章节。
|
||||
|
||||
类型 (Type)
|
||||
一个字节表示的值的数据类型,可能的类型见 ``nvs_types.h`` 中 ``ItemType`` 枚举。
|
||||
|
||||
跨度 (Span)
|
||||
该键值对所用的条目数量。如果键值为整数型,条目数量即为 1。如果键值为字符串或 BLOB,则条目数量取决于值的长度。
|
||||
|
||||
块索引 (ChunkIndex)
|
||||
用于存储 BLOB 类型数据块的索引。如果键值为其他数据类型,则此处索引应写入 ``0xff``。
|
||||
|
||||
CRC32
|
||||
对条目下所有字节进行校验,所得的校验和(CRC32 字段不计算在内)。
|
||||
|
||||
键 (Key)
|
||||
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的 NULL (``\0``) 终止符。
|
||||
|
||||
数据 (Data)
|
||||
如果键值类型为整数型,则数据字段仅包含键值。如果键值小于八个字节,使用 ``0xff`` 填充未使用的部分(右侧)。
|
||||
|
||||
如果键值类型为 BLOB 索引条目,则该字段的八个字节将保存以下数据块信息:
|
||||
|
||||
- 块大小
|
||||
整个 BLOB 数据的大小(以字节为单位)。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkCount
|
||||
存储过程中 BLOB 分成的数据块数量。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkStart
|
||||
BLOB 第一个数据块的块索引,后续数据块索引依次递增,步长为 1。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
如果键值类型为字符串或 BLOB 数据块,数据字段的这八个字节将保存该键值的一些附加信息,如下所示:
|
||||
|
||||
- 数据大小
|
||||
实际数据的大小(以字节为单位)。如果键值类型为字符串,此字段也应将零终止符包含在内。此字段仅用于字符串和 BLOB 类型条目。
|
||||
|
||||
- CRC32
|
||||
数据所有字节的校验和,该字段仅用于字符串和 BLOB 类型条目。
|
||||
|
||||
可变长度值(字符串和 BLOB)写入后续条目,每个条目 32 字节。第一个条目的 span 字段将指明使用了多少条目。
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
|
||||
如上所述,每个键值对属于一个命名空间。命名空间标识符(字符串)也作为键值对的键,存储在索引为 0 的命名空间中。与这些键对应的值就是这些命名空间的索引。
|
||||
|
||||
::
|
||||
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
| NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
|
||||
|
||||
条目哈希列表
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
为了减少对 flash 执行的读操作次数,Page 类对象均设有一个列表,包含一对数据:条目索引和条目哈希值。该列表可大大提高检索速度,而无需迭代所有条目并逐个从 flash 中读取。``Page::findItem`` 首先从哈希列表中检索条目哈希值,如果条目存在,则在页面内给出条目索引。由于哈希冲突,在哈希列表中检索条目哈希值可能会得到不同的条目,对 flash 中条目再次迭代可解决这一冲突。
|
||||
|
||||
哈希列表中每个节点均包含一个 24 位哈希值和 8 位条目索引。哈希值根据条目命名空间、键名和块索引由 CRC32 计算所得,计算结果保留 24 位。为减少将 32 位条目存储在链表中的开销,链表采用了数组的双向链表。每个数组占用 128 个字节,包含 29 个条目、两个链表指针和一个 32 位计数字段。因此,每页额外需要的 RAM 最少为 128 字节,最多为 640 字节。
|
||||
|
||||
.. _nvs_encryption:
|
||||
|
||||
NVS 加密
|
||||
--------------
|
||||
|
||||
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密,每个条目被均视为一个扇区,并将条目相对地址(相对于分区开头)传递给加密算法,用作扇区号。NVS 加密所需的密钥存储于其他分区,并进行了 :doc:`flash 加密 <../../security/flash-encryption>`。因此,在使用 NVS 加密前应先启用 :doc:`flash 加密 <../../security/flash-encryption>`。
|
||||
|
||||
.. _nvs_key_partition:
|
||||
|
||||
NVS 密钥分区
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 ``data``,子类型为 ``key`` 的密钥分区。该分区应标记为已加密,且最小为 4096 字节,具体结构见下表。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`。
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key(32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32(4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
使用 NVS 分区生成程序生成上述分区表,并烧录至设备。由于分区已标记为已加密,而且启用了 :doc:`flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。您也可以在设备启动后调用 ``nvs_flash.h`` 提供的 ``nvs_flash_generate_keys`` API 生成加密密钥,然后再将密钥以加密形式写入密钥分区。
|
||||
|
||||
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
|
||||
|
||||
加密读取/写入
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``nvs_get_*`` 和 ``nvs_set_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。但用于初始化 NVS 非加密分区和加密分区的 API 则有所不同:初始化 NVS 非加密分区可以使用 ``nvs_flash_init`` 和 ``nvs_flash_init_partition``,但初始化 NVS 加密分区则需调用 ``nvs_flash_secure_init`` 和 ``nvs_flash_secure_init_partition``。上述 API 函数所需的 ``nvs_sec_cfg_t`` 结构可使用 ``nvs_flash_generate_keys`` 或者 ``nvs_flash_read_security_cfg`` 进行填充。
|
||||
|
||||
应用程序如需在加密状态下执行 NVS 读写操作,应遵循以下步骤:
|
||||
|
||||
1. 使用 ``esp_partition_find*`` API 查找密钥分区和 NVS 数据分区;
|
||||
2. 使用 ``nvs_flash_read_security_cfg`` 或 ``nvs_flash_generate_keys`` API 填充 ``nvs_sec_cfg_t`` 结构;
|
||||
3. 使用 ``nvs_flash_secure_init`` 或 ``nvs_flash_secure_init_partition`` API 初始化 NVS flash 分区;
|
||||
4. 使用 ``nvs_open`` 或 ``nvs_open_from_part`` API 打开命名空间;
|
||||
5. 使用 ``nvs_get_*`` 或 ``nvs_set_*`` API 执行 NVS 读取/写入操作;
|
||||
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
|
||||
|
||||
NVS 迭代器
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。
|
||||
|
||||
您可以使用以下函数,执行相关操作:
|
||||
|
||||
- ``nvs_entry_find``:返回一个不透明句柄,用于后续调用 ``nvs_entry_next`` 和 ``nvs_entry_info`` 函数;
|
||||
- ``nvs_entry_next``:返回指向下一个键值对的迭代器;
|
||||
- ``nvs_entry_info``:返回每个键值对的信息。
|
||||
|
||||
如果未找到符合标准的键值对,``nvs_entry_find`` 和 ``nvs_entry_next`` 将返回 NULL,此时不必释放迭代器。若不再需要迭代器,可使用 ``nvs_release_iterator`` 释放迭代器。
|
||||
|
@ -171,17 +171,92 @@ esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_o
|
||||
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief set value for given key
|
||||
* @brief set int8_t value for given key
|
||||
*
|
||||
* This family of functions set value for the key, given its name. Note that
|
||||
* actual storage will not be updated until nvs_commit function is called.
|
||||
* Set value for the key, given its name. Note that the actual storage will not be updated
|
||||
* until \c nvs_commit is called.
|
||||
*
|
||||
* @param[in] handle Handle obtained from nvs_open function.
|
||||
* Handles that were opened read only cannot be used.
|
||||
* @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param[in] value The value to set.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if value was set successfully
|
||||
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
|
||||
* - ESP_ERR_NVS_READ_ONLY if storage handle was opened as read only
|
||||
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
|
||||
* - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the
|
||||
* underlying storage to save the value
|
||||
* - ESP_ERR_NVS_REMOVE_FAILED if the value wasn't updated because flash
|
||||
* write operation has failed. The value was written however, and
|
||||
* update will be finished after re-initialization of nvs, provided that
|
||||
* flash operation doesn't fail again.
|
||||
*/
|
||||
esp_err_t nvs_set_i8 (nvs_handle_t handle, const char* key, int8_t value);
|
||||
|
||||
/**
|
||||
* @brief set uint8_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_u8 (nvs_handle_t handle, const char* key, uint8_t value);
|
||||
|
||||
/**
|
||||
* @brief set int16_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_i16 (nvs_handle_t handle, const char* key, int16_t value);
|
||||
|
||||
/**
|
||||
* @brief set uint16_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_u16 (nvs_handle_t handle, const char* key, uint16_t value);
|
||||
|
||||
/**
|
||||
* @brief set int32_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_i32 (nvs_handle_t handle, const char* key, int32_t value);
|
||||
|
||||
/**
|
||||
* @brief set uint32_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_u32 (nvs_handle_t handle, const char* key, uint32_t value);
|
||||
|
||||
/**
|
||||
* @brief set int64_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_i64 (nvs_handle_t handle, const char* key, int64_t value);
|
||||
|
||||
/**
|
||||
* @brief set uint64_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_set_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_set_u64 (nvs_handle_t handle, const char* key, uint64_t value);
|
||||
|
||||
/**
|
||||
* @brief set string for given key
|
||||
*
|
||||
* Set value for the key, given its name. Note that the actual storage will not be updated
|
||||
* until \c nvs_commit is called.
|
||||
*
|
||||
* @param[in] handle Handle obtained from nvs_open function.
|
||||
* Handles that were opened read only cannot be used.
|
||||
* @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param[in] value The value to set.
|
||||
* For strings, the maximum length (including null character) is
|
||||
* 4000 bytes.
|
||||
* 4000 bytes, if there is one complete page free for writing.
|
||||
* This decreases, however, if the free space is fragmented.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if value was set successfully
|
||||
@ -196,14 +271,6 @@ esp_err_t nvs_open_from_partition(const char *part_name, const char* name, nvs_o
|
||||
* flash operation doesn't fail again.
|
||||
* - ESP_ERR_NVS_VALUE_TOO_LONG if the string value is too long
|
||||
*/
|
||||
esp_err_t nvs_set_i8 (nvs_handle_t handle, const char* key, int8_t value);
|
||||
esp_err_t nvs_set_u8 (nvs_handle_t handle, const char* key, uint8_t value);
|
||||
esp_err_t nvs_set_i16 (nvs_handle_t handle, const char* key, int16_t value);
|
||||
esp_err_t nvs_set_u16 (nvs_handle_t handle, const char* key, uint16_t value);
|
||||
esp_err_t nvs_set_i32 (nvs_handle_t handle, const char* key, int32_t value);
|
||||
esp_err_t nvs_set_u32 (nvs_handle_t handle, const char* key, uint32_t value);
|
||||
esp_err_t nvs_set_i64 (nvs_handle_t handle, const char* key, int64_t value);
|
||||
esp_err_t nvs_set_u64 (nvs_handle_t handle, const char* key, uint64_t value);
|
||||
esp_err_t nvs_set_str (nvs_handle_t handle, const char* key, const char* value);
|
||||
/**@}*/
|
||||
|
||||
@ -238,16 +305,15 @@ esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value,
|
||||
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief get value for given key
|
||||
* @brief get int8_t value for given key
|
||||
*
|
||||
* These functions retrieve value for the key, given its name. If key does not
|
||||
* These functions retrieve value for the key, given its name. If \c key does not
|
||||
* exist, or the requested variable type doesn't match the type which was used
|
||||
* when setting a value, an error is returned.
|
||||
*
|
||||
* In case of any error, out_value is not modified.
|
||||
*
|
||||
* All functions expect out_value to be a pointer to an already allocated variable
|
||||
* of the given type.
|
||||
* \c out_value has to be a pointer to an already allocated variable of the given type.
|
||||
*
|
||||
* \code{c}
|
||||
* // Example of using nvs_get_i32:
|
||||
@ -272,18 +338,61 @@ esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value,
|
||||
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
|
||||
* - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data
|
||||
*/
|
||||
esp_err_t nvs_get_i8 (nvs_handle_t handle, const char* key, int8_t* out_value);
|
||||
esp_err_t nvs_get_u8 (nvs_handle_t handle, const char* key, uint8_t* out_value);
|
||||
esp_err_t nvs_get_i8 (nvs_handle_t handle, const char* key, int8_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get uint8_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_u8 (nvs_handle_t handle, const char* key, uint8_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get int16_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_i16 (nvs_handle_t handle, const char* key, int16_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get uint16_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_u16 (nvs_handle_t handle, const char* key, uint16_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get int32_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_i32 (nvs_handle_t handle, const char* key, int32_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get uint32_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_u32 (nvs_handle_t handle, const char* key, uint32_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get int64_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_i64 (nvs_handle_t handle, const char* key, int64_t* out_value);
|
||||
|
||||
/**
|
||||
* @brief get uint64_t value for given key
|
||||
*
|
||||
* This function is the same as \c nvs_get_i8 except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_u64 (nvs_handle_t handle, const char* key, uint64_t* out_value);
|
||||
/**@}*/
|
||||
|
||||
/**@{*/
|
||||
/**
|
||||
* @brief get value for given key
|
||||
* @brief get string value for given key
|
||||
*
|
||||
* These functions retrieve the data of an entry, given its key. If key does not
|
||||
* exist, or the requested variable type doesn't match the type which was used
|
||||
@ -320,7 +429,7 @@ esp_err_t nvs_get_u64 (nvs_handle_t handle, const char* key, uint64_t* out_value
|
||||
*
|
||||
* @param[in] handle Handle obtained from nvs_open function.
|
||||
* @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param out_value Pointer to the output value.
|
||||
* @param[out] out_value Pointer to the output value.
|
||||
* May be NULL for nvs_get_str and nvs_get_blob, in this
|
||||
* case required length will be returned in length argument.
|
||||
* @param[inout] length A non-zero pointer to the variable holding the length of out_value.
|
||||
@ -334,10 +443,15 @@ esp_err_t nvs_get_u64 (nvs_handle_t handle, const char* key, uint64_t* out_value
|
||||
* - ESP_ERR_NVS_NOT_FOUND if the requested key doesn't exist
|
||||
* - ESP_ERR_NVS_INVALID_HANDLE if handle has been closed or is NULL
|
||||
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
|
||||
* - ESP_ERR_NVS_INVALID_LENGTH if length is not sufficient to store data
|
||||
* - ESP_ERR_NVS_INVALID_LENGTH if \c length is not sufficient to store data
|
||||
*/
|
||||
/**@{*/
|
||||
esp_err_t nvs_get_str (nvs_handle_t handle, const char* key, char* out_value, size_t* length);
|
||||
|
||||
/**
|
||||
* @brief get blob value for given key
|
||||
*
|
||||
* This function behaves the same as \c nvs_get_str, except for the data type.
|
||||
*/
|
||||
esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length);
|
||||
/**@}*/
|
||||
|
||||
@ -454,7 +568,9 @@ esp_err_t nvs_get_stats(const char *part_name, nvs_stats_t *nvs_stats);
|
||||
/**
|
||||
* @brief Calculate all entries in a namespace.
|
||||
*
|
||||
* Note that to find out the total number of records occupied by the namespace,
|
||||
* An entry represents the smallest storage unit in NVS.
|
||||
* Strings and blobs may occupy more than one entry.
|
||||
* Note that to find out the total number of entries occupied by the namespace,
|
||||
* add one to the returned value used_entries (if err is equal to ESP_OK).
|
||||
* Because the name space entry takes one entry.
|
||||
*
|
||||
@ -466,7 +582,7 @@ esp_err_t nvs_get_stats(const char *part_name, nvs_stats_t *nvs_stats);
|
||||
* size_t used_entries;
|
||||
* size_t total_entries_namespace;
|
||||
* if(nvs_get_used_entry_count(handle, &used_entries) == ESP_OK){
|
||||
* // the total number of records occupied by the namespace
|
||||
* // the total number of entries occupied by the namespace
|
||||
* total_entries_namespace = used_entries + 1;
|
||||
* }
|
||||
* \endcode
|
||||
|
@ -47,7 +47,8 @@ public:
|
||||
* @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param[in] value The value to set. Allowed types are the ones declared in ItemType as well as enums.
|
||||
* For strings, the maximum length (including null character) is
|
||||
* 4000 bytes.
|
||||
* 4000 bytes, if there is one complete page free for writing.
|
||||
* This decreases, however, if the free space is fragmented.
|
||||
* Note that enums loose their type information when stored in NVS. Ensure that the correct
|
||||
* enum type is used during retrieval with \ref get_item!
|
||||
*
|
||||
@ -131,15 +132,14 @@ public:
|
||||
* Both functions expect out_value to be a pointer to an already allocated variable
|
||||
* of the given type.
|
||||
*
|
||||
* It is suggested that nvs_get/set_str is used for zero-terminated C strings, and
|
||||
* nvs_get/set_blob used for arbitrary data structures.
|
||||
* It is suggested that nvs_get/set_str is used for zero-terminated short C strings, and
|
||||
* nvs_get/set_blob is used for arbitrary data structures and long C strings.
|
||||
*
|
||||
* @param[in] key Key name. Maximal length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param[in] key Key name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param out_str/ Pointer to the output value.
|
||||
* out_blob
|
||||
* @param[inout] length A non-zero pointer to the variable holding the length of out_value.
|
||||
* It will be set to the actual length of the value
|
||||
* written. For nvs_get_str this includes the zero terminator.
|
||||
* @param[inout] len The length of the output buffer pointed to by out_str/out_blob.
|
||||
* Use \c get_item_size to query the size of the item beforehand.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK if the value was retrieved successfully
|
||||
@ -151,9 +151,16 @@ public:
|
||||
virtual esp_err_t get_blob(const char *key, void* out_blob, size_t len) = 0;
|
||||
|
||||
/**
|
||||
* @brief Looks up the size of an entry's data.
|
||||
* @brief Look up the size of an entry's data.
|
||||
*
|
||||
* For strings, this size includes the zero terminator.
|
||||
* @param[in] datatype Data type to search for.
|
||||
* @param[in] key Key name. Maximum length is (NVS_KEY_NAME_MAX_SIZE-1) characters. Shouldn't be empty.
|
||||
* @param[out] size Size of the item, if it exists.
|
||||
* For strings, this size includes the zero terminator.
|
||||
*
|
||||
* @return - ESP_OK if the item with specified type and key exists. Its size will be returned via \c size.
|
||||
* - ESP_ERR_NVS_NOT_FOUND if an item with the requested key and type doesn't exist or any other
|
||||
* error occurs.
|
||||
*/
|
||||
virtual esp_err_t get_item_size(ItemType datatype, const char *key, size_t &size) = 0;
|
||||
|
||||
@ -171,6 +178,8 @@ public:
|
||||
|
||||
/**
|
||||
* Commits all changes done through this handle so far.
|
||||
* Currently, NVS writes to storage right after the set and get functions,
|
||||
* but this is not guaranteed.
|
||||
*/
|
||||
virtual esp_err_t commit() = 0;
|
||||
|
||||
|
@ -1,4 +1,153 @@
|
||||
.. include:: ../../../../components/nvs_flash/README.rst
|
||||
Non-volatile storage library
|
||||
============================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This section introduces some concepts used by NVS.
|
||||
|
||||
|
||||
Underlying storage
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Currently, NVS uses a portion of main flash memory through the :ref:`esp_partition <flash-partition-apis>` API. The library uses all the partitions with ``data`` type and ``nvs`` subtype. The application can choose to use the partition with the label ``nvs`` through the :cpp:func:`nvs_open` API function or any other partition by specifying its name using the :cpp:func:`nvs_open_from_partition` API function.
|
||||
|
||||
Future versions of this library may have other storage backends to keep data in another flash chip (SPI or I2C), RTC, FRAM, etc.
|
||||
|
||||
.. note:: if an NVS partition is truncated (for example, when the partition table layout is changed), its contents should be erased. ESP-IDF build system provides a ``idf.py erase_flash`` target to erase all contents of the flash chip.
|
||||
|
||||
.. note:: NVS works best for storing many small values, rather than a few large values of the type 'string' and 'blob'. If you need to store large blobs or strings, consider using the facilities provided by the FAT filesystem on top of the wear levelling library.
|
||||
|
||||
|
||||
Keys and values
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
NVS operates on key-value pairs. Keys are ASCII strings; the maximum key length is currently 15 characters. Values can have one of the following types:
|
||||
|
||||
- integer types: ``uint8_t``, ``int8_t``, ``uint16_t``, ``int16_t``, ``uint32_t``, ``int32_t``, ``uint64_t``, ``int64_t``
|
||||
- zero-terminated string
|
||||
- variable length binary data (blob)
|
||||
|
||||
.. note::
|
||||
|
||||
String values are currently limited to 4000 bytes. This includes the null terminator. Blob values are limited to 508000 bytes or 97.6% of the partition size - 4000 bytes, whichever is lower.
|
||||
|
||||
Additional types, such as ``float`` and ``double`` might be added later.
|
||||
|
||||
Keys are required to be unique. Assigning a new value to an existing key works as follows:
|
||||
|
||||
- if the new value is of the same type as the old one, value is updated
|
||||
- if the new value has a different data type, an error is returned
|
||||
|
||||
Data type check is also performed when reading a value. An error is returned if the data type of the read operation does not match the data type of the value.
|
||||
|
||||
|
||||
Namespaces
|
||||
^^^^^^^^^^
|
||||
|
||||
To mitigate potential conflicts in key names between different components, NVS assigns each key-value pair to one of namespaces. Namespace names follow the same rules as key names, i.e., the maximum length is 15 characters. Namespace name is specified in the :cpp:func:`nvs_open` or :cpp:type:`nvs_open_from_partition` call. This call returns an opaque handle, which is used in subsequent calls to the ``nvs_get_*``, ``nvs_set_*``, and :cpp:func:`nvs_commit` functions. This way, a handle is associated with a namespace, and key names will not collide with same names in other namespaces. Please note that the namespaces with the same name in different NVS partitions are considered as separate namespaces.
|
||||
|
||||
NVS iterators
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Iterators allow to list key-value pairs stored in NVS, based on specified partition name, namespace, and data type.
|
||||
|
||||
There are the following functions available:
|
||||
|
||||
- :cpp:func:`nvs_entry_find` returns an opaque handle, which is used in subsequent calls to the :cpp:func:`nvs_entry_next` and :cpp:func:`nvs_entry_info` functions.
|
||||
- :cpp:func:`nvs_entry_next` returns iterator to the next key-value pair.
|
||||
- :cpp:func:`nvs_entry_info` returns information about each key-value pair
|
||||
|
||||
If none or no other key-value pair was found for given criteria, :cpp:func:`nvs_entry_find` and :cpp:func:`nvs_entry_next` return NULL. In that case, the iterator does not have to be released. If the iterator is no longer needed, you can release it by using the function :cpp:func:`nvs_release_iterator`.
|
||||
|
||||
|
||||
Security, tampering, and robustness
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS is not directly compatible with the {IDF_TARGET_NAME} flash encryption system. However, data can still be stored in encrypted form if NVS encryption is used together with {IDF_TARGET_NAME} flash encryption. Please refer to :ref:`nvs_encryption` for more details.
|
||||
|
||||
If NVS encryption is not used, it is possible for anyone with physical access to the flash chip to alter, erase, or add key-value pairs. With NVS encryption enabled, it is not possible to alter or add a key-value pair and get recognized as a valid pair without knowing corresponding NVS encryption keys. However, there is no tamper-resistance against the erase operation.
|
||||
|
||||
The library does try to recover from conditions when flash memory is in an inconsistent state. In particular, one should be able to power off the device at any point and time and then power it back on. This should not result in loss of data, except for the new key-value pair if it was being written at the moment of powering off. The library should also be able to initialize properly with any random data present in flash memory.
|
||||
|
||||
|
||||
.. _nvs_encryption:
|
||||
|
||||
NVS Encryption
|
||||
--------------
|
||||
|
||||
Data stored in NVS partitions can be encrypted using AES-XTS in the manner similar to the one mentioned in disk encryption standard IEEE P1619. For the purpose of encryption, each entry is treated as one `sector` and relative address of the entry (w.r.t. partition-start) is fed to the encryption algorithm as `sector-number`. The NVS Encryption can be enabled by enabling :ref:`CONFIG_NVS_ENCRYPTION`. The keys required for NVS encryption are stored in yet another partition, which is protected using :doc:`Flash Encryption <../../security/flash-encryption>`. Therefore, enabling :doc:`Flash Encryption <../../security/flash-encryption>` is a prerequisite for NVS encryption.
|
||||
|
||||
The NVS Encryption is enabled by default when :doc:`Flash Encryption <../../security/flash-encryption>` is enabled. This is done because WiFi driver stores credentials (like SSID and passphrase) in the default NVS partition. It is important to encrypt them as default choice if platform level encryption is already enabled.
|
||||
|
||||
For using NVS encryption, the partition table must contain the :ref:`nvs_key_partition`. Two partition tables containing the :ref:`nvs_key_partition` are provided for NVS encryption under the partition table option (menuconfig->Partition Table). They can be selected with the project configuration menu (``idf.py menuconfig``). Please refer to the example :example:`security/flash_encryption` for how to configure and use NVS encryption feature.
|
||||
|
||||
.. _nvs_key_partition:
|
||||
|
||||
NVS key partition
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
An application requiring NVS encryption support needs to be compiled with a key-partition of the type `data` and subtype `key`. This partition should be marked as `encrypted` and its size should be the minimum partition size (4KB). Refer to :doc:`Partition Tables <../../api-guides/partition-tables>` for more details. Two additional partition tables which contain the :ref:`nvs_key_partition` are provided under the partition table option (menuconfig->Partition Table). They can be directly used for :ref:`nvs_encryption`. The structure of these partitions is depicted below.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key(32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32(4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
The XTS encryption keys in the :ref:`nvs_key_partition` can be generated in one of the following two ways.
|
||||
|
||||
1. Generate the keys on the ESP chip:
|
||||
|
||||
When NVS encryption is enabled the :cpp:func:`nvs_flash_init` API function can be used to initialize the encrypted default NVS partition. The API function internally generates the XTS encryption keys on the ESP chip. The API function finds the first :ref:`nvs_key_partition`. Then the API function automatically generates and stores the nvs keys in that partition by making use of the :cpp:func:`nvs_flash_generate_keys` API function provided by :component_file:`nvs_flash/include/nvs_flash.h`. New keys are generated and stored only when the respective key partiton is empty. The same key partition can then be used to read the security configurations for initializing a custom encrypted NVS partition with help of :cpp:func:`nvs_flash_secure_init_partition`.
|
||||
|
||||
The API functions :cpp:func:`nvs_flash_secure_init` and :cpp:func:`nvs_flash_secure_init_partition` do not generate the keys internally. When these API functions are used for initializing encrypted NVS partitions, the keys can be generated after startup using the :cpp:func:`nvs_flash_generate_keys` API function provided by ``nvs_flash.h``. The API function will then write those keys onto the key-partition in encrypted form.
|
||||
|
||||
2. Use pre-generated key partition:
|
||||
|
||||
This option will be required by the user when keys in the :ref:`nvs_key_partition` are not generated by the application. The :ref:`nvs_key_partition` containing the XTS encryption keys can be generated with the help of :doc:`NVS Partition Generator Utility</api-reference/storage/nvs_partition_gen>`. Then the user can store the pre generated key partition on the flash with help of the following two commands:
|
||||
|
||||
i) Build and flash the partition table
|
||||
::
|
||||
|
||||
idf.py partition_table partition_table-flash
|
||||
|
||||
ii) Store the keys in the :ref:`nvs_key_partition` (on the flash) with the help of :component_file:`parttool.py<partition_table/parttool.py>` (see Partition Tool section in :doc:`partition-tables </api-guides/partition-tables>` for more details)
|
||||
::
|
||||
|
||||
parttool.py --port /dev/ttyUSB0 --partition-table-offset "nvs_key partition offset" write_partition --partition-name="name of nvs_key partition" --input "nvs_key partition"
|
||||
|
||||
Since the key partition is marked as `encrypted` and :doc:`Flash Encryption <../../security/flash-encryption>` is enabled, the bootloader will encrypt this partition using flash encryption key on the first boot.
|
||||
|
||||
It is possible for an application to use different keys for different NVS partitions and thereby have multiple key-partitions. However, it is a responsibility of the application to provide correct key-partition/keys for the purpose of encryption/decryption.
|
||||
|
||||
Encrypted Read/Write
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The same NVS API functions ``nvs_get_*`` or ``nvs_set_*`` can be used for reading of, and writing to an encrypted nvs partition as well.
|
||||
|
||||
**Encrypt the default NVS partition:**
|
||||
To enable encryption for the default NVS partition no additional steps are necessary. When :ref:`CONFIG_NVS_ENCRYPTION` is enabled, the :cpp:func:`nvs_flash_init` API function internally performs some additional steps using the first :ref:`nvs_key_partition` found to enable encryption for the default NVS partition (refer to the API documentation for more details). Alternatively, :cpp:func:`nvs_flash_secure_init` API function can also be used to enable encryption for the default NVS partition.
|
||||
|
||||
**Encrypt a custom NVS partition:**
|
||||
To enable encryption for a custom NVS partition, :cpp:func:`nvs_flash_secure_init_partition` API function is used instead of :cpp:func:`nvs_flash_init_partition`.
|
||||
|
||||
When :cpp:func:`nvs_flash_secure_init` and :cpp:func:`nvs_flash_secure_init_partition` API functions are used, the applications are expected to follow the steps below in order to perform NVS read/write operations with encryption enabled.
|
||||
|
||||
1. Find key partition and NVS data partition using ``esp_partition_find*`` API functions.
|
||||
2. Populate the :cpp:type:`nvs_sec_cfg_t` struct using the :cpp:func:`nvs_flash_read_security_cfg` or :cpp:func:`nvs_flash_generate_keys` API functions.
|
||||
3. Initialise NVS flash partition using the :cpp:func:`nvs_flash_secure_init` or :cpp:func:`nvs_flash_secure_init_partition` API functions.
|
||||
4. Open a namespace using the :cpp:func:`nvs_open` or :cpp:func:`nvs_open_from_partition` API functions.
|
||||
5. Perform NVS read/write operations using ``nvs_get_*`` or ``nvs_set_*``.
|
||||
6. Deinitialise an NVS partition using :cpp:func:`nvs_flash_deinit`.
|
||||
|
||||
NVS Partition Generator Utility
|
||||
-------------------------------
|
||||
@ -8,7 +157,7 @@ This utility helps generate NVS partition binary files which can be flashed sepa
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
You can find two code examples in the :example:`storage` directory of ESP-IDF examples:
|
||||
You can find code examples in the :example:`storage` directory of ESP-IDF examples:
|
||||
|
||||
:example:`storage/nvs_rw_value`
|
||||
|
||||
@ -27,6 +176,201 @@ You can find two code examples in the :example:`storage` directory of ESP-IDF ex
|
||||
|
||||
The example also shows how to implement the diagnostic procedure to check if the read / write operation was successful.
|
||||
|
||||
:example:`storage/nvs_rw_value_cxx`
|
||||
|
||||
This example does exactly the same as :example:`storage/nvs_rw_value`, except that it uses the C++ nvs handle class.
|
||||
|
||||
Internals
|
||||
---------
|
||||
|
||||
Log of key-value pairs
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, a new key-value pair is added at the end of the log and the old key-value pair is marked as erased.
|
||||
|
||||
Pages and entries
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states:
|
||||
|
||||
Empty/uninitialized
|
||||
Flash storage for the page is empty (all bytes are ``0xff``). Page is not used to store any data at this point and does not have a sequence number.
|
||||
|
||||
Active
|
||||
Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. No more than one page can be in this state at any given moment.
|
||||
|
||||
Full
|
||||
Flash storage is in a consistent state and is filled with key-value pairs.
|
||||
Writing new key-value pairs into this page is not possible. It is still possible to mark some key-value pairs as erased.
|
||||
|
||||
Erasing
|
||||
Non-erased key-value pairs are being moved into another page so that the current page can be erased. This is a transient state, i.e., page should never stay in this state at the time when any API call returns. In case of a sudden power off, the move-and-erase process will be completed upon the next power-on.
|
||||
|
||||
Corrupted
|
||||
Page header contains invalid data, and further parsing of page data was canceled. Any items previously written into this page will not be accessible. The corresponding flash sector will not be erased immediately and will be kept along with sectors in *uninitialized* state for later use. This may be useful for debugging.
|
||||
|
||||
Mapping from flash sectors to logical pages does not have any particular order. The library will inspect sequence numbers of pages found in each flash sector and organize pages in a list based on these numbers.
|
||||
|
||||
::
|
||||
|
||||
+--------+ +--------+ +--------+ +--------+
|
||||
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
|
||||
| Full +---> | Full +---> | Active | | Empty | <- states
|
||||
| #11 | | #12 | | #14 | | | <- sequence numbers
|
||||
+---+----+ +----+---+ +----+---+ +---+----+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+---v------+ +-----v----+ +------v---+ +------v---+
|
||||
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- physical sectors
|
||||
+----------+ +----------+ +----------+ +----------+
|
||||
|
||||
Structure of a page
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For now, we assume that flash sector size is 4096 bytes and that {IDF_TARGET_NAME} flash encryption hardware operates on 32-byte blocks. It is possible to introduce some settings configurable at compile-time (e.g., via menuconfig) to accommodate flash chips with different sector sizes (although it is not clear if other components in the system, e.g., SPI flash driver and SPI flash cache can support these other sizes).
|
||||
|
||||
Page consists of three parts: header, entry state bitmap, and entries themselves. To be compatible with {IDF_TARGET_NAME} flash encryption, the entry size is 32 bytes. For integer types, an entry holds one key-value pair. For strings and blobs, an entry holds part of key-value pair (more on that in the entry structure description).
|
||||
|
||||
The following diagram illustrates the page structure. Numbers in parentheses indicate the size of each part in bytes.
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | Header (32)
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| Entry state bitmap (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 0 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 1 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
/ /
|
||||
/ /
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 125 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
|
||||
Page header and entry state bitmap are always written to flash unencrypted. Entries are encrypted if flash encryption feature of {IDF_TARGET_NAME} is used.
|
||||
|
||||
Page state values are defined in such a way that changing state is possible by writing 0 into some of the bits. Therefore it is not necessary to erase the page to change its state unless that is a change to the *erased* state.
|
||||
|
||||
The version field in the header reflects the NVS format version used. For backward compatibility reasons, it is decremented for every version upgrade starting at 0xff (i.e., 0xff for version-1, 0xfe for version-2 and so on).
|
||||
|
||||
CRC32 value in the header is calculated over the part which does not include a state value (bytes 4 to 28). The unused part is currently filled with ``0xff`` bytes.
|
||||
|
||||
The following sections describe the structure of entry state bitmap and entry itself.
|
||||
|
||||
Entry and entry state bitmap
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Each entry can be in one of the following three states represented with two bits in the entry state bitmap. The final four bits in the bitmap (256 - 2 * 126) are not used.
|
||||
|
||||
Empty (2'b11)
|
||||
Nothing is written into the specific entry yet. It is in an uninitialized state (all bytes are ``0xff``).
|
||||
|
||||
Written (2'b10)
|
||||
A key-value pair (or part of key-value pair which spans multiple entries) has been written into the entry.
|
||||
|
||||
Erased (2'b00)
|
||||
A key-value pair in this entry has been discarded. Contents of this entry will not be parsed anymore.
|
||||
|
||||
|
||||
.. _structure_of_entry:
|
||||
|
||||
Structure of entry
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For values of primitive types (currently integers from 1 to 8 bytes long), entry holds one key-value pair. For string and blob types, entry holds part of the whole key-value pair. For strings, in case when a key-value pair spans multiple entries, all entries are stored in the same page. Blobs are allowed to span over multiple pages by dividing them into smaller chunks. For tracking these chunks, an additional fixed length metadata entry is stored called "blob index". Earlier formats of blobs are still supported (can be read and modified). However, once the blobs are modified, they are stored using the new format.
|
||||
|
||||
::
|
||||
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
| NS (1) | Type (1) | Span (1) | ChunkIndex (1) | CRC32 (4) | Key (16) | Data (8) |
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
|
||||
Primitive +--------------------------------+
|
||||
+--------> | Data (8) |
|
||||
| Types +--------------------------------+
|
||||
+-> Fixed length --
|
||||
| | +---------+--------------+---------------+-------+
|
||||
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
|
||||
Data format ---+ Blob Index +---------+--------------+---------------+-------+
|
||||
|
|
||||
| +----------+---------+-----------+
|
||||
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
|
||||
(Strings, Blob Data) +----------+---------+-----------+
|
||||
|
||||
|
||||
Individual fields in entry structure have the following meanings:
|
||||
|
||||
NS
|
||||
Namespace index for this entry. For more information on this value, see the section on namespaces implementation.
|
||||
|
||||
Type
|
||||
One byte indicating the value data type. See the :cpp:type:`ItemType` enumeration in :component_file:`nvs_flash/include/nvs_handle.hpp` for possible values.
|
||||
|
||||
Span
|
||||
Number of entries used by this key-value pair. For integer types, this is equal to 1. For strings and blobs, this depends on value length.
|
||||
|
||||
ChunkIndex
|
||||
Used to store the index of a blob-data chunk for blob types. For other types, this should be ``0xff``.
|
||||
|
||||
CRC32
|
||||
Checksum calculated over all the bytes in this entry, except for the CRC32 field itself.
|
||||
|
||||
Key
|
||||
Zero-terminated ASCII string containing a key name. Maximum string length is 15 bytes, excluding a zero terminator.
|
||||
|
||||
Data
|
||||
For integer types, this field contains the value itself. If the value itself is shorter than 8 bytes, it is padded to the right, with unused bytes filled with ``0xff``.
|
||||
|
||||
For "blob index" entry, these 8 bytes hold the following information about data-chunks:
|
||||
|
||||
- Size
|
||||
(Only for blob index.) Size, in bytes, of complete blob data.
|
||||
|
||||
- ChunkCount
|
||||
(Only for blob index.) Total number of blob-data chunks into which the blob was divided during storage.
|
||||
|
||||
- ChunkStart
|
||||
(Only for blob index.) ChunkIndex of the first blob-data chunk of this blob. Subsequent chunks have chunkIndex incrementally allocated (step of 1).
|
||||
|
||||
For string and blob data chunks, these 8 bytes hold additional data about the value, which are described below:
|
||||
|
||||
- Size
|
||||
(Only for strings and blobs.) Size, in bytes, of actual data. For strings, this includes zero terminators.
|
||||
|
||||
- CRC32
|
||||
(Only for strings and blobs.) Checksum calculated over all bytes of data.
|
||||
|
||||
Variable length values (strings and blobs) are written into subsequent entries, 32 bytes per entry. The `Span` field of the first entry indicates how many entries are used.
|
||||
|
||||
|
||||
Namespaces
|
||||
^^^^^^^^^^
|
||||
|
||||
As mentioned above, each key-value pair belongs to one of the namespaces. Namespace identifiers (strings) are stored as keys of key-value pairs in namespace with index 0. Values corresponding to these keys are indexes of these namespaces.
|
||||
|
||||
::
|
||||
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
| NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
|
||||
|
||||
Item hash list
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
To reduce the number of reads from flash memory, each member of the Page class maintains a list of pairs: item index; item hash. This list makes searches much quicker. Instead of iterating over all entries, reading them from flash one at a time, `Page::findItem` first performs a search for the item hash in the hash list. This gives the item index within the page if such an item exists. Due to a hash collision, it is possible that a different item will be found. This is handled by falling back to iteration over items in flash.
|
||||
|
||||
Each node in the hash list contains a 24-bit hash and 8-bit item index. Hash is calculated based on item namespace, key name, and ChunkIndex. CRC32 is used for calculation; the result is truncated to 24 bits. To reduce the overhead for storing 32-bit entries in a linked list, the list is implemented as a double-linked list of arrays. Each array holds 29 entries, for the total size of 128 bytes, together with linked list pointers and a 32-bit count field. The minimum amount of extra RAM usage per page is therefore 128 bytes; maximum is 640 bytes.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
@ -376,7 +376,7 @@ Once documentation is ready, follow instruction in :doc:`../api-reference/templa
|
||||
OK, but I am new to Sphinx!
|
||||
---------------------------
|
||||
|
||||
1. No worries. All the software you need is well documented. It is also open source and free. Start by checking `Sphinx`_ documentation. If you are not clear how to write using rst markup language, see `reStructuredText Primer <https://www.sphinx-doc.org/en/stable/rest.html>`_. You can also use markdown (.md) files, and find out about more about the specific markdown syntax that we use on `Recommonmark parser's documentation page <https://recommonmark.readthedocs.io/en/latest/>`_.
|
||||
1. No worries. All the software you need is well documented. It is also open source and free. Start by checking `Sphinx`_ documentation. If you are not clear how to write using rst markup language, see `reStructuredText Primer <https://www.sphinx-doc.org/en/stable/rest.html>`_. You can also use markdown (.md) files, and find out more about the specific markdown syntax that we use on `Recommonmark parser's documentation page <https://recommonmark.readthedocs.io/en/latest/>`_.
|
||||
|
||||
2. Check the source files of this documentation to understand what is behind of what you see now on the screen. Sources are maintained on GitHub in `espressif/esp-idf`_ repository in :idf:`docs` folder. You can go directly to the source file of this page by scrolling up and clicking the link in the top right corner. When on GitHub, see what's really inside, open source files by clicking ``Raw`` button.
|
||||
|
||||
|
@ -1,4 +1,305 @@
|
||||
.. include:: ../../../../components/nvs_flash/README_CN.rst
|
||||
非易失性存储库
|
||||
============================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
简介
|
||||
------------
|
||||
|
||||
非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。本文档将详细介绍 NVS 常用的一些概念。
|
||||
|
||||
底层存储
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 通过调用 ``spi_flash_{read|write|erase}`` API 对主 flash 的部分空间进行读、写、擦除操作,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 ``nvs_open`` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 ``nvs_open_from_part`` API 选择使用指定名称的任意分区。
|
||||
|
||||
NVS 库后续版本可能会增加其他存储器后端,实现将数据保存至其他 flash 芯片(SPI 或 I2C 接口)、RTC 或 FRAM 中。
|
||||
|
||||
.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase_flash`` 命令擦除 flash 上的所有内容。
|
||||
|
||||
.. note:: NVS 最适合存储一些较小的数据,而非字符串或二进制大对象 (BLOB) 等较大的数据。如需存储较大的 BLOB 或者字符串,请考虑使用基于磨损均衡库的 FAT 文件系统。
|
||||
|
||||
|
||||
键值对
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型:
|
||||
|
||||
- 整数型:``uint8_t``、``int8_t``、``uint16_t``、``int16_t``、``uint32_t``、``int32_t``、``uint64_t`` 和 ``int64_t``;
|
||||
- 以 ``\0`` 结尾的字符串;
|
||||
- 可变长度的二进制数据 (BLOB)
|
||||
|
||||
.. note::
|
||||
|
||||
字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小减去 4000 字节的 97.6%,以较低值为准。
|
||||
|
||||
后续可能会增加对 ``float`` 和 ``double`` 等其他类型数据的支持。
|
||||
|
||||
键必须唯一。为现有的键写入新的值可能产生如下结果:
|
||||
|
||||
- 如果新旧值数据类型相同,则更新值;
|
||||
- 如果新旧值数据类型不同,则返回错误。
|
||||
|
||||
读取值时也会执行数据类型检查。如果读取操作的数据类型与该值的数据类型不匹配,则返回错误。
|
||||
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
|
||||
为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,即最多可占 15 个字符。命名空间的名称在调用 ``nvs_open`` 或 ``nvs_open_from_part`` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*``、``nvs_set_*`` 和 ``nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
|
||||
|
||||
|
||||
安全性、篡改性及鲁棒性
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 与 ESP32 flash 加密系统不直接兼容。但如果 NVS 加密与 ESP32 flash 加密一起使用时,数据仍可以加密形式存储。更多详情请参阅 :ref:`nvs_encryption`。
|
||||
|
||||
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的人都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
|
||||
|
||||
当 flash 处于不一致状态时,NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应当能对 flash 中的任意数据进行正确初始化。
|
||||
|
||||
|
||||
内部实现
|
||||
---------
|
||||
|
||||
键值对日志
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需更新某一键值对,实际是在日志最后增加一对新的键值对,同时将旧的键值对标记为已擦除。
|
||||
|
||||
页面和条目
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的序列号。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
|
||||
|
||||
空或未初始化
|
||||
页面对应的 flash 扇区为空白状态(所有字节均为 ``0xff``)。此时,页面未存储任何数据且没有关联的序列号。
|
||||
|
||||
活跃状态
|
||||
此时 flash 已完成初始化,页头部写入 flash,页面已具备有效序列号。页面中存在一些空条目,可写入数据。任意时刻,至多有一个页面处于活跃状态。
|
||||
|
||||
写满状态
|
||||
Flash 已写满键值对,状态不再改变。用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
|
||||
|
||||
擦除状态
|
||||
未擦除的键值对将移至其他页面,以便擦除当前页面。这一状态仅为暂时性状态,即 API 调用返回时,页面应脱离这一状态。如果设备突然断电,下次开机时,设备将继续把未擦除的键值对移至其他页面,并继续擦除当前页面。
|
||||
|
||||
损坏状态
|
||||
页头部包含无效数据,无法进一步解析该页面中的数据,因此之前写入该页面的所有条目均无法访问。相应的 flash 扇区并不会被立即擦除,而是与其他处于未初始化状态的扇区一起等待后续使用。这一状态可能对调试有用。
|
||||
|
||||
Flash 扇区映射至逻辑页面并没有特定的顺序,NVS 库会检查存储在 flash 扇区的页面序列号,并根据序列号组织页面。
|
||||
|
||||
::
|
||||
|
||||
+--------+ +--------+ +--------+ +--------+
|
||||
| Page 1 | | Page 2 | | Page 3 | | Page 4 |
|
||||
| Full +---> | Full +---> | Active | | Empty | <- 状态
|
||||
| #11 | | #12 | | #14 | | | <- 序列号
|
||||
+---+----+ +----+---+ +----+---+ +---+----+
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
+---v------+ +-----v----+ +------v---+ +------v---+
|
||||
| Sector 3 | | Sector 0 | | Sector 2 | | Sector 1 | <- 物理扇区
|
||||
+----------+ +----------+ +----------+ +----------+
|
||||
|
||||
页面结构
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
当前,我们假设 flash 扇区大小为 4096 字节,并且 ESP32 flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
|
||||
|
||||
页面由头部、条目状态位图和条目三部分组成。为了实现与 ESP32 flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
|
||||
|
||||
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)::
|
||||
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | 页头部 (32)
|
||||
+-----------+--------------+-------------+-------------------------+
|
||||
| Entry state bitmap (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 0 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 1 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
/ /
|
||||
/ /
|
||||
+------------------------------------------------------------------+
|
||||
| Entry 125 (32) |
|
||||
+------------------------------------------------------------------+
|
||||
|
||||
头部和条目状态位图写入 flash 时不加密。如果启用了 ESP32 flash 加密功能,则条目写入 flash 时将会加密。
|
||||
|
||||
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为擦除状态。
|
||||
|
||||
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减(例如,version-1 为 0xff,version-2 为 0xfe 等)。
|
||||
|
||||
头部中 CRC32 值是由不包含状态值的条目计算所得(4 到 28 字节)。当前未使用的条目用 ``0xff`` 字节填充。
|
||||
|
||||
条目结构和条目状态位图详细信息见下文描述。
|
||||
|
||||
条目和条目状态位图
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
每个条目可处于以下三种状态之一,每个状态在条目状态位图中用两位表示。位图中的最后四位 (256 - 2 * 126) 未使用。
|
||||
|
||||
空 (2'b11)
|
||||
条目还未写入任何内容,处于未初始化状态(全部字节为 ``0xff``)。
|
||||
|
||||
写入(2'b10)
|
||||
一个键值对(或跨多个条目的键值对的部分内容)已写入条目中。
|
||||
|
||||
擦除(2'b00)
|
||||
条目中的键值对已丢弃,条目内容不再解析。
|
||||
|
||||
.. _structure_of_entry:
|
||||
|
||||
条目结构
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
如果键值类型为基础类型,即 1 - 8 个字节长度的整数型,条目将保存一个键值对;如果键值类型为字符串或 BLOB 类型,条目将保存整个键值对的部分内容。另外,如果键值为字符串类型且跨多个条目,则键值所跨的所有条目均保存在同一页面。BLOB 则可以切分为多个块,实现跨多个页面。BLOB 索引是一个附加的固定长度元数据条目,用于追踪 BLOB 块。目前条目仍支持早期 BLOB 格式(可读取可修改),但这些 BLOB 一经修改,即以新格式储存至条目。
|
||||
|
||||
::
|
||||
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
| NS (1) | Type (1) | Span (1) | ChunkIndex (1) | CRC32 (4) | Key (16) | Data (8) |
|
||||
+--------+----------+----------+----------------+-----------+---------------+----------+
|
||||
|
||||
Primitive +--------------------------------+
|
||||
+--------> | Data (8) |
|
||||
| Types +--------------------------------+
|
||||
+-> Fixed length --
|
||||
| | +---------+--------------+---------------+-------+
|
||||
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
|
||||
Data format ---+ BLOB Index +---------+--------------+---------------+-------+
|
||||
|
|
||||
| +----------+---------+-----------+
|
||||
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
|
||||
(Strings, BLOB Data) +----------+---------+-----------+
|
||||
|
||||
|
||||
条目结构中各个字段含义如下:
|
||||
|
||||
命名空间 (NS, NameSpace)
|
||||
该条目的命名空间索引,详细信息见命名空间实现章节。
|
||||
|
||||
类型 (Type)
|
||||
一个字节表示的值的数据类型,可能的类型见 ``nvs_types.h`` 中 ``ItemType`` 枚举。
|
||||
|
||||
跨度 (Span)
|
||||
该键值对所用的条目数量。如果键值为整数型,条目数量即为 1。如果键值为字符串或 BLOB,则条目数量取决于值的长度。
|
||||
|
||||
块索引 (ChunkIndex)
|
||||
用于存储 BLOB 类型数据块的索引。如果键值为其他数据类型,则此处索引应写入 ``0xff``。
|
||||
|
||||
CRC32
|
||||
对条目下所有字节进行校验,所得的校验和(CRC32 字段不计算在内)。
|
||||
|
||||
键 (Key)
|
||||
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的 NULL (``\0``) 终止符。
|
||||
|
||||
数据 (Data)
|
||||
如果键值类型为整数型,则数据字段仅包含键值。如果键值小于八个字节,使用 ``0xff`` 填充未使用的部分(右侧)。
|
||||
|
||||
如果键值类型为 BLOB 索引条目,则该字段的八个字节将保存以下数据块信息:
|
||||
|
||||
- 块大小
|
||||
整个 BLOB 数据的大小(以字节为单位)。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkCount
|
||||
存储过程中 BLOB 分成的数据块数量。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
- ChunkStart
|
||||
BLOB 第一个数据块的块索引,后续数据块索引依次递增,步长为 1。该字段仅用于 BLOB 索引类型条目。
|
||||
|
||||
如果键值类型为字符串或 BLOB 数据块,数据字段的这八个字节将保存该键值的一些附加信息,如下所示:
|
||||
|
||||
- 数据大小
|
||||
实际数据的大小(以字节为单位)。如果键值类型为字符串,此字段也应将零终止符包含在内。此字段仅用于字符串和 BLOB 类型条目。
|
||||
|
||||
- CRC32
|
||||
数据所有字节的校验和,该字段仅用于字符串和 BLOB 类型条目。
|
||||
|
||||
可变长度值(字符串和 BLOB)写入后续条目,每个条目 32 字节。第一个条目的 span 字段将指明使用了多少条目。
|
||||
|
||||
命名空间
|
||||
^^^^^^^^^^
|
||||
|
||||
如上所述,每个键值对属于一个命名空间。命名空间标识符(字符串)也作为键值对的键,存储在索引为 0 的命名空间中。与这些键对应的值就是这些命名空间的索引。
|
||||
|
||||
::
|
||||
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="wifi" Value=1 | Entry describing namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=1 Type=uint32_t Key="channel" Value=6 | Key "channel" in namespace "wifi"
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
| NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
|
||||
|
||||
条目哈希列表
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
为了减少对 flash 执行的读操作次数,Page 类对象均设有一个列表,包含一对数据:条目索引和条目哈希值。该列表可大大提高检索速度,而无需迭代所有条目并逐个从 flash 中读取。``Page::findItem`` 首先从哈希列表中检索条目哈希值,如果条目存在,则在页面内给出条目索引。由于哈希冲突,在哈希列表中检索条目哈希值可能会得到不同的条目,对 flash 中条目再次迭代可解决这一冲突。
|
||||
|
||||
哈希列表中每个节点均包含一个 24 位哈希值和 8 位条目索引。哈希值根据条目命名空间、键名和块索引由 CRC32 计算所得,计算结果保留 24 位。为减少将 32 位条目存储在链表中的开销,链表采用了数组的双向链表。每个数组占用 128 个字节,包含 29 个条目、两个链表指针和一个 32 位计数字段。因此,每页额外需要的 RAM 最少为 128 字节,最多为 640 字节。
|
||||
|
||||
.. _nvs_encryption:
|
||||
|
||||
NVS 加密
|
||||
--------------
|
||||
|
||||
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密,每个条目被均视为一个扇区,并将条目相对地址(相对于分区开头)传递给加密算法,用作扇区号。NVS 加密所需的密钥存储于其他分区,并进行了 :doc:`flash 加密 <../../security/flash-encryption>`。因此,在使用 NVS 加密前应先启用 :doc:`flash 加密 <../../security/flash-encryption>`。
|
||||
|
||||
.. _nvs_key_partition:
|
||||
|
||||
NVS 密钥分区
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 ``data``,子类型为 ``key`` 的密钥分区。该分区应标记为已加密,且最小为 4096 字节,具体结构见下表。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`。
|
||||
|
||||
::
|
||||
|
||||
+-----------+--------------+-------------+----+
|
||||
| XTS encryption key(32) |
|
||||
+---------------------------------------------+
|
||||
| XTS tweak key (32) |
|
||||
+---------------------------------------------+
|
||||
| CRC32(4) |
|
||||
+---------------------------------------------+
|
||||
|
||||
使用 NVS 分区生成程序生成上述分区表,并烧录至设备。由于分区已标记为已加密,而且启用了 :doc:`flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。您也可以在设备启动后调用 ``nvs_flash.h`` 提供的 ``nvs_flash_generate_keys`` API 生成加密密钥,然后再将密钥以加密形式写入密钥分区。
|
||||
|
||||
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
|
||||
|
||||
加密读取/写入
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``nvs_get_*`` 和 ``nvs_set_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。但用于初始化 NVS 非加密分区和加密分区的 API 则有所不同:初始化 NVS 非加密分区可以使用 ``nvs_flash_init`` 和 ``nvs_flash_init_partition``,但初始化 NVS 加密分区则需调用 ``nvs_flash_secure_init`` 和 ``nvs_flash_secure_init_partition``。上述 API 函数所需的 ``nvs_sec_cfg_t`` 结构可使用 ``nvs_flash_generate_keys`` 或者 ``nvs_flash_read_security_cfg`` 进行填充。
|
||||
|
||||
应用程序如需在加密状态下执行 NVS 读写操作,应遵循以下步骤:
|
||||
|
||||
1. 使用 ``esp_partition_find*`` API 查找密钥分区和 NVS 数据分区;
|
||||
2. 使用 ``nvs_flash_read_security_cfg`` 或 ``nvs_flash_generate_keys`` API 填充 ``nvs_sec_cfg_t`` 结构;
|
||||
3. 使用 ``nvs_flash_secure_init`` 或 ``nvs_flash_secure_init_partition`` API 初始化 NVS flash 分区;
|
||||
4. 使用 ``nvs_open`` 或 ``nvs_open_from_part`` API 打开命名空间;
|
||||
5. 使用 ``nvs_get_*`` 或 ``nvs_set_*`` API 执行 NVS 读取/写入操作;
|
||||
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
|
||||
|
||||
NVS 迭代器
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。
|
||||
|
||||
您可以使用以下函数,执行相关操作:
|
||||
|
||||
- ``nvs_entry_find``:返回一个不透明句柄,用于后续调用 ``nvs_entry_next`` 和 ``nvs_entry_info`` 函数;
|
||||
- ``nvs_entry_next``:返回指向下一个键值对的迭代器;
|
||||
- ``nvs_entry_info``:返回每个键值对的信息。
|
||||
|
||||
如果未找到符合标准的键值对,``nvs_entry_find`` 和 ``nvs_entry_next`` 将返回 NULL,此时不必释放迭代器。若不再需要迭代器,可使用 ``nvs_release_iterator`` 释放迭代器。
|
||||
|
||||
NVS 分区生成程序
|
||||
------------------
|
||||
|
Loading…
Reference in New Issue
Block a user