Stow API
Overview
The Stow API allows firmware modules to set, get, and subscribe to items in the Stow. The API is generic meaning the same interface can be used regardless of an item’s data type. Tagged unions are used to ensure type safety alongside runtime type checking. Some data types exist on the stack while others live in reference counted memory blocks. The generic interface abstracts this complexity from the caller.
All Stow operations are validated against the given item’s access controls and value constraints. It can be assumed that values in the Stow are always valid for a given item.
Stow operations are thread-safe. The Stow API is also safe to use from interrupt contexts.
Initialization
Call stow_init() once at startup before accessing any items. This loads persistent values from flash.
Storage
Changes to persistent values are stored using Zephyr’s Settings module. The Zephyr Settings module supports various storage backends to choose from, custom backends can also be defined.
Validation
When saved values are loaded from storage, they are validated against the item’s current constraints. If the saved value is no longer valid, the item is reverted to its default and the saved value is discarded. For structs, all fields must be valid for the saved value to be valid. Notably, if the structure of a struct changes (e.g. fields are added or removed) old saved values are invalidated.
The Stow does not provide any complex migration mechanisms, but the automatic validation should cover most use cases. Ideally, usage of the Stow should lend itself to atomic data items that do not require complex migration. If migration is necessary, consider a custom implementation of stow_storage.c to intercept and migrate stored values.
Get Items
Use STOW_GET to read an item’s current value. Always call STOW_RELEASE when finished with the value retrieved by STOW_GET. This allows types that are reference-counted to be released, it is a noop for other types. The Resource Checker will verify that values are released.
data_value_t val = {0};
STOW_GET(STOW_ROLE_INTERNAL, STOW_ID_DEVICE_NAME, &val);
LOG_INF("Device name: %s", val.data.string_value);
STOW_RELEASE(STOW_ID_DEVICE_NAME, &val);
Important
The caller MUST NOT modify the value returned by STOW_GET directly. If modifications are needed, make a copy of the value first.
%%{init: {"theme": "neutral"}}%%
sequenceDiagram
participant Caller
participant Stow
Caller->>Stow: STOW_GET(roles, item_id, &value)
Stow->>Stow: Check read permissions against role(s)
Stow-->>Caller: Populate value with a reference or copy
Note over Caller: Use value.data
Caller->>Stow: STOW_RELEASE(item_id, &value)
Stow->>Stow: Free the reference or copy
Set Items
Use STOW_SET with a data_value_t that includes the correct type tag.
data_value_t val = {
.type = STOW_ITEM_TYPE_INT,
.data.int_value = 42,
};
STOW_SET(STOW_ROLE_INTERNAL, STOW_ID_TEST_INT, val);
%%{init: {"theme": "neutral"}}%%
sequenceDiagram
participant Caller
participant Stow
Caller->>Stow: STOW_SET(role, item_id, value)
Stow->>Stow: Check write permissions against role(s)
Stow->>Stow: Validate against item constraints
Stow->>Stow: Set the new value
Stow->>Stow: Notify subscribers
Stow->>Caller: SUCCESS or error
It may be cumbersome to allocate a buffer for small buffer types. In this case, a STACK_BUFFER can be used to create a stack-allocated buffer_t:
STACK_BUFFER(bytes, 6);
bytes->buf[0] = 0xAB;
data_value_t val = {
.type = STOW_ITEM_TYPE_BUFFER,
.data.buffer_value = bytes,
};
STOW_SET(STOW_ROLE_INTERNAL, STOW_ID_TEST_BUFFER, val);
Subscriptions
The Stow API allows modules to subscribe to items. The subscription callback will fire whenever the associated item is changed. Subscriptions can be by handle or copy:
STOW_SUBSCRIPTION_HANDLEHandle subscriptions simply notify a subscriber that and item changed, not what value it changed to. The callback receives only the item ID. Subscribers must read the current value via
STOW_GET. Notably, an item’s value may change by the time a subscriber reads the current value. Use this when you only care about the latest value.STOW_SUBSCRIPTION_COPYIf guaranteed delivery is needed, copy subscriptions can be used. Copy subscriptions notify subscribers whenever an item’s value changes, including the value that the item was changed to. The callback receives a copy of the value at the moment the update occurred. Use this for guaranteed delivery of every value.
void on_update(event_t* event)
{
struct stow_update_event_payload* payload =
(struct stow_update_event_payload*)event->data.buf;
LOG_INF("New value: %d", payload->value_copy.data.int_value);
}
static struct stow_subscription sub = {
.mode = STOW_SUBSCRIPTION_COPY,
.cb = on_update,
};
stow_subscribe(STOW_ROLE_INTERNAL, STOW_ID_TEST_INT, &sub);
// later:
stow_unsubscribe(STOW_ID_TEST_INT, &sub);
If a subscription will be alive for the full lifetime of the app, a static subscription can be used via STOW_SUBSCRIPTION_DEFINE. These subscriptions are stored in ROM rather than dynamically allocated.
Encode & Decode
Items can be serialized to and from CBOR. Items are encoded when stored in non-volatile storage as well as when transmitted over the Stow Protocol. The value from STOW_DECODE must always be released with STOW_RELEASE.
uint8_t buf[256];
ZCBOR_STATE_E(encoder, 1, buf, sizeof(buf), 1);
data_value_t val = {0};
STOW_GET(STOW_ROLE_INTERNAL, STOW_ID_TEST_INT, &val);
stow_encode(encoder, STOW_ID_TEST_INT, val);
STOW_RELEASE(STOW_ID_TEST_INT, &val);
ZCBOR_STATE_D(decoder, 1, buf, sizeof(buf), 1, 0);
data_value_t decoded = {0};
STOW_DECODE(decoder, STOW_ID_TEST_INT, &decoded);
STOW_RELEASE(STOW_ID_TEST_INT, &decoded);
Custom Interface
Items may optionally override the default get, set, and validation. These function overrides are declared in the schema by name and resolved at link time.
custom_validateCalled after the default constraint check as an additional gate. Returns
trueto accept the value orfalseto reject it. Use sparingly; clients reading the Stow description will have no way to know about application-specific validation rules.custom_getReplaces the default interface
getafter the permission check. This could be used to read a value on demand rather than reading from stored state.custom_setReplaces the default interface
setafter the permission check, constraint validation, and anycustom_validate. This to intercept writes and apply modifications before saving.custom_setcan call the defaultsetimplementation after its custom logic to ensure the new value is set in the Stow.
Example:
int my_item_get(const struct stow_item_const_metadata *item, data_value_t *out_value)
{
out_value->type = STOW_ITEM_TYPE_INT;
out_value->data.int_value = read_sensor();
return SUCCESS;
}
int my_item_set(const struct stow_item_const_metadata* item, data_value_t value)
{
// Perform a modification (e.g. divide by 2)
// Use care to ensure the value is always valid given the item's constraints
value.int_value = value.int_value / 2;
// Call the default set implementation to store the value
item->interface->set(item->value_ptr, value);
return SUCCESS;
}
bool my_item_validate(const struct stow_item_const_metadata *item, data_value_t value)
{
// Only allow even values
return value.data.int_value % 2 == 0;
}
Authentication
Each Stow item has access controls for reading and writing values. Stow operations require providing a bitfield of the caller’s current roles. STOW_ROLE_INTERNAL can be used to access any item. This is meant to be used by internal firmware modules. Other roles can be defined in the schema.
Configuration
# Stow dependencies
CONFIG_ZCBOR=y
CONFIG_SETTINGS=y
CONFIG_NVS=y
CONFIG_SETTINGS_NVS=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
# Enable the Stow library
CONFIG_GANTRY_STOW=y
# Maximum encoded size for items in persistent storage (bytes)
CONFIG_STOW_ITEM_STORAGE_SIZE_MAX=1024
API Reference
-
enum stow_subscription_mode
Subscription mode for stow item subscriptions.
Values:
-
enumerator STOW_SUBSCRIPTION_HANDLE
Subscribers are notified of which value has been updated, the subscriber must read the current value from the stow.
-
enumerator STOW_SUBSCRIPTION_COPY
Used for guaranteed delivery of every value. The notification contains a copy of the value at the time when the notification occurred.
-
enumerator STOW_SUBSCRIPTION_COUNT
-
enumerator STOW_SUBSCRIPTION_HANDLE
-
enum stow_item_type
Supported item data types.
Values:
-
enumerator STOW_ITEM_TYPE_ENUM
-
enumerator STOW_ITEM_TYPE_INT
-
enumerator STOW_ITEM_TYPE_FLOAT
-
enumerator STOW_ITEM_TYPE_STRING
-
enumerator STOW_ITEM_TYPE_BYTE_ARRAY
-
enumerator STOW_ITEM_TYPE_BUFFER
-
enumerator STOW_ITEM_TYPE_STRUCT
-
enumerator STOW_ITEM_TYPE_COUNT
-
enumerator STOW_ITEM_TYPE_ENUM
-
enum stow_storage_type
Storage types for stow items.
Values:
-
enumerator STOW_STORAGE_EPHEMERAL
Items are reset to their default value upon reboot
-
enumerator STOW_STORAGE_PERSISTENT
Items are stored in non-volatile storage. Their values persist across reboots.
-
enumerator STOW_STORAGE_TOFU
Item can be written while at its default value. It cannot be changed again.
-
enumerator STOW_STORAGE_COUNT
-
enumerator STOW_STORAGE_EPHEMERAL
-
typedef void (*stow_subscription_cb)(event_t *event)
Callback used to notify a subscriber when the subscribed value changes.
- Param event:
Pointer to a stow update event
-
typedef uint16_t stow_role_t
Role bitmask type for access control.
Each bit position represents one role from the app’s Stow specification.
Note
STOW_ROLE_INTERNAL bypasses permission checks. This allows firmware to always access Stow items. Consumers of the Stow protocol should take care to ensure that session always have AT LEAST ONE role, otherwise they will have full access.
-
const struct stow_item_interface stow_buffer_interface
Stow interface for buffer items.
Operations expect to receive the address of a buffer_t pointer
-
const struct stow_item_interface stow_byte_array_interface
Stow interface for byte array items.
Operations expect to receive the address of a buffer_t pointer
-
const struct stow_item_interface stow_enum_interface
Stow interface for enum items.
Operations expect to receive the address of an int
-
const struct stow_item_interface stow_float_interface
Stow interface for float items.
Operations expect to receive the address of a float
-
const struct stow_item_interface stow_int_interface
Stow interface for int items.
Operations expect to receive the address of an integer
-
const struct stow_item_interface stow_string_interface
Stow interface for string items.
Operations expect to receive the address of a char pointer
-
const char *const item_type_strings[]
String representation of each item type.
-
void stow_init(void)
Initialize the stow.
Loads persistent stow item values. Must be called before the stow is used.
-
bool stow_is_id_valid(uint32_t id)
Check if a numeric ID is a valid stow item ID.
- Parameters:
id – the numeric ID to check
- Returns:
true If the ID can be cast to a stow_item_id
- Returns:
false If the ID is not a valid item ID
-
int stow_set(stow_role_t current_auth, enum stow_item_id id, data_value_t value)
Set the value of a data item.
- Parameters:
current_auth – The caller’s role bitmask
id – Item ID to modify
value – The desired value
- Returns:
SUCCESS when the value is set
- Returns:
-EACCES when the caller’s role has no overlap with the item’s write permission mask
- Returns:
-EINVAL when the provided value is invalid
- Returns:
-ENOMEM when the value cannot be stored
-
int stow_get(stow_role_t current_auth, enum stow_item_id id, data_value_t *out_value)
Get the current value of a data item.
- Parameters:
current_auth – [in] The caller’s role bitmask
id – [in] Item ID to retrieve
out_value – [out] Pointer to be populated with the item’s current value
- Returns:
SUCCESS when the value was retrieved
- Returns:
-EACCES when the caller’s role has no overlap with the item’s read permission mask
- Returns:
-EINVAL when the output pointer is NULL
- Returns:
-ENOMEM when the value cannot be retrieved
-
void stow_release(enum stow_item_id id, data_value_t *value)
Release a previously retrieved data item value.
stow_get may return a pointer to a memory block depending on the item’s data type. As a result, item values retrieved with stow_get should always be released.
- Parameters:
id – [in] Item ID, used to determine the data type
value – [inout] Pointer to the value to release.
-
int stow_encode(zcbor_state_t *encoder, enum stow_item_id id, data_value_t value)
Encode a data item value as CBOR.
- Parameters:
encoder – CBOR encoder instance
id – Item ID, used to determine the data type
value – The value to encode
- Returns:
SUCCESS when the value was encoded
- Returns:
-ENOMEM when the encoder lacks room to encode the value
-
int stow_decode(zcbor_state_t *decoder, enum stow_item_id id, data_value_t *out_value)
Decode a data item value from CBOR.
- Parameters:
decoder – [in] CBOR decoder instance
id – [in] Item ID, used to determine the data type
out_value – [out] Pointer populated with the decoded value
- Returns:
SUCCESS when the value was decoded
- Returns:
-EBADMSG when the CBOR value could not be decoded
- Returns:
-ENOMEM when memory cannot be allocated for the decoded value
-
int stow_subscribe(stow_role_t current_auth, enum stow_item_id id, struct stow_subscription *subscription)
Subscribe to a data item.
- Parameters:
current_auth – The caller’s role bitmask
id – Item ID to subscribe to
subscription – The stow subscription
- Returns:
SUCCESS when the subscription is added
- Returns:
-EACCES when the caller’s role has no overlap with the item’s read permission mask
- Returns:
-EALREADY when the requested subscription already exists
- Returns:
-ENOMEM when there is no memory to create a subscription
-
int stow_unsubscribe(enum stow_item_id id, struct stow_subscription *subscription)
Unsubscribe from a data item.
- Parameters:
id – Item ID to unsubscribe from
subscription – The subscription to remove
- Returns:
SUCCESS when the subscription is removed from the data item
- Returns:
-ENOENT when the subscription did not exist for the given data item
-
int stow_describe(uint32_t start_id, zcbor_state_t *encoder, uint32_t *next_id_out)
Encode a chunk of the stow description starting at a given item ID.
The encoder’s buffer will be filled with item descriptions starting from
start_id. Encoding stops when all items are encoded or the buffer is full. On return,next_id_outholds the ID to pass asstart_idon the next call. Whennext_id_outequals the total item count, all items have been encoded.- Parameters:
start_id – ID of the first item to encode (pass 0 to start from the beginning)
encoder – CBOR encoder to populate
next_id_out – Populated with the ID of the next item to encode
- Returns:
SUCCESS when all items starting from
start_idwere encoded- Returns:
-ENOMEM when there is no more room in the encoder before all items were encoded
-
DECLARE_EVENT_TYPE(stow_update_event)
Stow update event declaration.
-
void stow_event_on_free(event_t *event)
Release the data copy within a stow_update_event_payload.
- Parameters:
event – Event being freed
-
int stow_storage_load(void)
Load data items from storage.
This should be called during stow initialization
- Returns:
SUCCESS when the stow has been loaded from storage, non-zero on error
-
int stow_storage_save_item(const struct stow_item_const_metadata *item)
Save an item to persistent storage.
The value is only saved if it has changed.
- Parameters:
item – Data item to save
- Returns:
SUCCESS when the item is saved
- Returns:
-ENOMEM when memory is not available
- Returns:
result of settings_save_one on failure
-
int enum_get_name_from_value(const union stow_constraints *constraints, int value, char **out_name)
Get the enum value’s name from its numeric value.
- Parameters:
constraints – [in] the enum’s constraints
value – [in] the numeric enum value
out_name – [out] the name of the enum value, only populated on success
- Returns:
int SUCCESS if the value existed
- Returns:
-EINVAL when the provided value was invalid
-
int enum_get_value_from_name(const union stow_constraints *constraints, char *name, int *out_value)
Get the enum value’s numeric value from its name.
- Parameters:
constraints – [in] the enum’s constraints
name – [in] the name of the enum value
out_value – [out] numeric value of the enum value, only populated on success
- Returns:
int SUCCESS if the name was valid
- Returns:
-EINVAL when the provided name was invalid
-
STOW_RELEASE(id, value)
Convenience macro for stow_release with debug tracing.
-
STOW_DECODE(decoder, id, out_value)
Convenience macro for stow_decode with debug tracing.
-
STOW_SUBSCRIPTION_DEFINE(_name, _mode, _cb, ...)
Statically define stow subscriptions.
Use this instead of a runtime stow_subscribe call for subscriptions that live for the entire process lifetime.
- Parameters:
_name – Base name for subscription, must be unique within the translation unit
_mode – Subscription mode, see stow_subscription_mode
_cb – Callback invoked when any listed item changes
... – One or more item IDs
-
EVENT_ID_STOW_UPDATE
Event ID for stow update events.
-
STOW_ROLE_INTERNAL
Firmware-only; no external client access
-
STOW_ROLE_ANY
Any external client role has access
-
struct stow_subscription
- #include <stow.h>
Stow subscription.
Defines the subscription mode and callback to be called when the item is updated.
-
struct stow_item_dynamic_metadata
- #include <stow.h>
Dynamic metadata for stow items.
-
struct stow_static_subscription
- #include <stow.h>
A static stow subscription.
Created by STOW_SUBSCRIPTION_DEFINE. These subscriptions exist for the full lifetime of the app.
-
struct stow_update_event_payload
- #include <stow_event.h>
Event payload for stow update events.
-
union raw_data_value_t
- #include <stow_types.h>
Union of raw values for each data type.
-
struct data_value_t
- #include <stow_types.h>
Tagged union representing a data item value.
-
struct stow_permissions
- #include <stow_types.h>
Permission bitmasks required to read or write an associated data item.
-
struct data_enum_value
- #include <stow_types.h>
Value and name pair for an enum value.
-
struct stow_enum_constraints
- #include <stow_types.h>
Constraints for enum items.
-
struct stow_int_constraints
- #include <stow_types.h>
Constraints for int items.
-
struct stow_float_constraints
- #include <stow_types.h>
Constraints for float items.
-
struct stow_buffer_constraints
- #include <stow_types.h>
Constraints for variable length buffer items.
-
union stow_constraints
- #include <stow_types.h>
Constraints for item value.
Public Members
-
struct stow_enum_constraints enum_constraints
Constraints for enum items
-
struct stow_int_constraints int_constraints
Constraints for int items
-
struct stow_float_constraints float_constraints
Constraints for float items
-
struct stow_buffer_constraints buffer_constraints
Constraints for variable length buffer types (string, bytes, buffers)
-
struct stow_enum_constraints enum_constraints
-
struct stow_item_custom_interface
- #include <stow_types.h>
Optional application-provided interface overrides for an item.
Each field may be NULL indicating that the function has no custom implementation.
-
struct stow_item_interface
- #include <stow_types.h>
Common interface for each stow item, implemented for each item type.
-
struct stow_item_const_metadata
- #include <stow_types.h>
Constant metadata for stow items.