Using Key-Value Storage in Userspace
When we use the HOTP application to store new keys, we want those keys to be persistent across reboots. That is, if we unplug the USB key, we would like our saved keys to still be accessible when we plug the key back in.
To enable this, we are using Tock's Key-Value (KV) interface. This allows userspace applications to store data in the form of key-value pairs. Applications can retrieve data by querying for the given key.
Checking if Key-Value Support Already Exists
Having key-value support is useful for more cases than just implementing an HOTP key, and so it is possible that your board already has key-value support enabled.
To check this, load the kv_check
test app onto your board:
cd libtock-c/examples/tests/kv_check
make
tockloader install
Run tockloader listen
and reset the board. You should see the following output
if KV support exists:
[KV] Check for Key-Value Support
Key-Value support is enabled.
If KV support already exists, you can skip this module!
Configuring the Kernel
Again we will use components to add key-value support to the kernel.
1. Include the Key-Value Stack Types
The KV stack includes many layers which leads to rather complex types. For more information about the KV stack in Tock, see the TicKV reference. To simplify somewhat, we define a series of types used at each layer of the stack. Include these towards the top of main.rs:
#![allow(unused)] fn main() { // TicKV type Mx25r6435f = components::mx25r6435f::Mx25r6435fComponentType< nrf52840::spi::SPIM<'static>, nrf52840::gpio::GPIOPin<'static>, nrf52840::rtc::Rtc<'static>, >; const TICKV_PAGE_SIZE: usize = core::mem::size_of::<<Mx25r6435f as kernel::hil::flash::Flash>::Page>(); type Siphasher24 = components::siphash::Siphasher24ComponentType; type TicKVDedicatedFlash = components::tickv::TicKVDedicatedFlashComponentType<Mx25r6435f, Siphasher24, TICKV_PAGE_SIZE>; type TicKVKVStore = components::kv::TicKVKVStoreComponentType< TicKVDedicatedFlash, capsules_extra::tickv::TicKVKeyType, >; type KVStorePermissions = components::kv::KVStorePermissionsComponentType<TicKVKVStore>; type VirtualKVPermissions = components::kv::VirtualKVPermissionsComponentType<KVStorePermissions>; type KVDriver = components::kv::KVDriverComponentType<VirtualKVPermissions>; }
Note the first type is the underlying flash driver where the KV database is actually stored. This will need to be customized for your specific board and flash device.
2. Include the KV Components
Now we can use those types to instantiate the components for each layer of the KV stack:
#![allow(unused)] fn main() { //-------------------------------------------------------------------------- // TICKV //-------------------------------------------------------------------------- // Static buffer to use when reading/writing flash for TicKV. let page_buffer = static_init!( <Mx25r6435f as kernel::hil::flash::Flash>::Page, <Mx25r6435f as kernel::hil::flash::Flash>::Page::default() ); // SipHash for creating TicKV hashed keys. let sip_hash = components::siphash::Siphasher24Component::new() .finalize(components::siphasher24_component_static!()); // TicKV with Tock wrapper/interface. let tickv = components::tickv::TicKVDedicatedFlashComponent::new( sip_hash, mx25r6435f, 0, // start at the beginning of the flash chip (capsules_extra::mx25r6435f::SECTOR_SIZE as usize) * 32, // arbitrary size of 32 pages page_buffer, ) .finalize(components::tickv_dedicated_flash_component_static!( Mx25r6435f, Siphasher24, TICKV_PAGE_SIZE, )); // KVSystem interface to KV (built on TicKV). let tickv_kv_store = components::kv::TicKVKVStoreComponent::new(tickv).finalize( components::tickv_kv_store_component_static!( TicKVDedicatedFlash, capsules_extra::tickv::TicKVKeyType, ), ); let kv_store_permissions = components::kv::KVStorePermissionsComponent::new(tickv_kv_store) .finalize(components::kv_store_permissions_component_static!( TicKVKVStore )); // Share the KV stack with a mux. let mux_kv = components::kv::KVPermissionsMuxComponent::new(kv_store_permissions).finalize( components::kv_permissions_mux_component_static!(KVStorePermissions), ); // Create a virtual component for the userspace driver. let virtual_kv_driver = components::kv::VirtualKVPermissionsComponent::new(mux_kv).finalize( components::virtual_kv_permissions_component_static!(KVStorePermissions), ); // Userspace driver for KV. let kv_driver = components::kv::KVDriverComponent::new( virtual_kv_driver, board_kernel, capsules_extra::kv_driver::DRIVER_NUM, ) .finalize(components::kv_driver_component_static!( VirtualKVPermissions )); }
This example is for the nRF52840dk board. You will likely need to change the
mx25r6435f
flash driver to the flash driver appropriate for your board.
3. Update the Platform
Struct and Expose KV to Userspace
We need to include the kv_driver
in the board's platform struct:
Then add these capsules to the Platform
struct:
#![allow(unused)] fn main() { pub struct Platform { ... kv_driver: &'static KVDriver, ... } let platform = Platform { ... kv_driver, ... }; }
And make the syscall interface available to userspace:
#![allow(unused)] fn main() { impl SyscallDriverLookup for Platform { fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R where F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { ... capsules_extra::kv_driver::DRIVER_NUM => f(Some(self.kv_driver)), ... } } } }
Checkpoint: Key-Value is now accessible to userspace!
Testing and Trying Out KV Storage
With KV support in your Tock kernel, you can use the applications in
libtock-c/examples/tests/kv*
to experiment with KV storage. In particular, the
kv_interactive
app allows you to get and set key-value pairs.