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_HANDLE

Handle 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_COPY

If 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_validate

Called after the default constraint check as an additional gate. Returns true to accept the value or false to reject it. Use sparingly; clients reading the Stow description will have no way to know about application-specific validation rules.

custom_get

Replaces the default interface get after the permission check. This could be used to read a value on demand rather than reading from stored state.

custom_set

Replaces the default interface set after the permission check, constraint validation, and any custom_validate. This to intercept writes and apply modifications before saving. custom_set can call the default set implementation 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
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
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
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_out holds the ID to pass as start_id on the next call. When next_id_out equals 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_id were 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_SET(current_auth, id, value)

Convenience macro for stow_set with memory tracing.

STOW_GET(current_auth, id, out_value)

Convenience macro for stow_get with memory tracing.

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.

Public Members

int int_value

Value for ints

float float_value

Value for floats

char *string_value

Value for strings

buffer_t *buffer_value

Value for buffers

void *raw_value

Raw pointer value for structs

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_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.