feat(ble_mesh): Device Firmware Update (Zephyr v4.0.0)

This commit is contained in:
luoxu
2024-11-19 17:51:49 +08:00
committed by Luo Xu
parent 9615bfa2bd
commit 8417c7006d
21 changed files with 9105 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define BT_MESH_DFD_OP_RECEIVERS_ADD BT_MESH_MODEL_OP_2(0x83, 0x11)
#define BT_MESH_DFD_OP_RECEIVERS_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x12)
#define BT_MESH_DFD_OP_RECEIVERS_STATUS BT_MESH_MODEL_OP_2(0x83, 0x13)
#define BT_MESH_DFD_OP_RECEIVERS_GET BT_MESH_MODEL_OP_2(0x83, 0x14)
#define BT_MESH_DFD_OP_RECEIVERS_LIST BT_MESH_MODEL_OP_2(0x83, 0x15)
#define BT_MESH_DFD_OP_CAPABILITIES_GET BT_MESH_MODEL_OP_2(0x83, 0x16)
#define BT_MESH_DFD_OP_CAPABILITIES_STATUS BT_MESH_MODEL_OP_2(0x83, 0x17)
#define BT_MESH_DFD_OP_GET BT_MESH_MODEL_OP_2(0x83, 0x18)
#define BT_MESH_DFD_OP_START BT_MESH_MODEL_OP_2(0x83, 0x19)
#define BT_MESH_DFD_OP_SUSPEND BT_MESH_MODEL_OP_2(0x83, 0x1a)
#define BT_MESH_DFD_OP_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x1b)
#define BT_MESH_DFD_OP_APPLY BT_MESH_MODEL_OP_2(0x83, 0x1c)
#define BT_MESH_DFD_OP_STATUS BT_MESH_MODEL_OP_2(0x83, 0x1d)
#define BT_MESH_DFD_OP_UPLOAD_GET BT_MESH_MODEL_OP_2(0x83, 0x1e)
#define BT_MESH_DFD_OP_UPLOAD_START BT_MESH_MODEL_OP_2(0x83, 0x1f)
#define BT_MESH_DFD_OP_UPLOAD_START_OOB BT_MESH_MODEL_OP_2(0x83, 0x20)
#define BT_MESH_DFD_OP_UPLOAD_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x21)
#define BT_MESH_DFD_OP_UPLOAD_STATUS BT_MESH_MODEL_OP_2(0x83, 0x22)
#define BT_MESH_DFD_OP_FW_GET BT_MESH_MODEL_OP_2(0x83, 0x23)
#define BT_MESH_DFD_OP_FW_GET_BY_INDEX BT_MESH_MODEL_OP_2(0x83, 0x24)
#define BT_MESH_DFD_OP_FW_DELETE BT_MESH_MODEL_OP_2(0x83, 0x25)
#define BT_MESH_DFD_OP_FW_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x26)
#define BT_MESH_DFD_OP_FW_STATUS BT_MESH_MODEL_OP_2(0x83, 0x27)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__
#include <zephyr/bluetooth/mesh.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_mesh_dfd_start_params {
uint16_t app_idx, timeout_base, slot_idx, group;
enum bt_mesh_blob_xfer_mode xfer_mode;
uint8_t ttl;
bool apply;
};
enum bt_mesh_dfd_status bt_mesh_dfd_srv_receiver_add(struct bt_mesh_dfd_srv *srv, uint16_t addr,
uint8_t img_idx);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_receivers_delete_all(struct bt_mesh_dfd_srv *srv);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_start(struct bt_mesh_dfd_srv *srv,
struct bt_mesh_dfd_start_params *params);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_suspend(struct bt_mesh_dfd_srv *srv);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_cancel(struct bt_mesh_dfd_srv *srv,
struct bt_mesh_msg_ctx *ctx);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_apply(struct bt_mesh_dfd_srv *srv);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete(struct bt_mesh_dfd_srv *srv, size_t *fwid_len,
const uint8_t **fwid);
enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete_all(struct bt_mesh_dfd_srv *srv);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ */
+44
View File
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#define BT_MESH_DFU_OP_UPDATE_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x08)
#define BT_MESH_DFU_OP_UPDATE_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x09)
#define BT_MESH_DFU_OP_UPDATE_METADATA_CHECK BT_MESH_MODEL_OP_2(0x83, 0x0a)
#define BT_MESH_DFU_OP_UPDATE_METADATA_STATUS BT_MESH_MODEL_OP_2(0x83, 0x0b)
#define BT_MESH_DFU_OP_UPDATE_GET BT_MESH_MODEL_OP_2(0x83, 0x0c)
#define BT_MESH_DFU_OP_UPDATE_START BT_MESH_MODEL_OP_2(0x83, 0x0d)
#define BT_MESH_DFU_OP_UPDATE_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x0e)
#define BT_MESH_DFU_OP_UPDATE_APPLY BT_MESH_MODEL_OP_2(0x83, 0x0f)
#define BT_MESH_DFU_OP_UPDATE_STATUS BT_MESH_MODEL_OP_2(0x83, 0x10)
#define DFU_UPDATE_INFO_STATUS_MSG_MINLEN (4 + CONFIG_BT_MESH_DFU_FWID_MAXLEN + \
CONFIG_BT_MESH_DFU_URI_MAXLEN)
#define DFU_UPDATE_START_MSG_MAXLEN (12 + CONFIG_BT_MESH_DFU_METADATA_MAXLEN)
static inline uint16_t dfu_metadata_checksum(struct net_buf_simple *buf)
{
/* Simple Fletcher-16 checksum to ensure duplicate start messages don't
* have different metadata.
*/
struct net_buf_simple_state state;
uint8_t sum[2] = {0, 0};
net_buf_simple_save(buf, &state);
while (buf->len) {
uint8_t byte = net_buf_simple_pull_u8(buf);
sum[0] += byte;
sum[1] += sum[0];
}
net_buf_simple_restore(buf, &state);
return (sum[0] << 8U) | sum[1];
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net_buf.h>
#include <zephyr/sys/byteorder.h>
#include <string.h>
#include <zephyr/bluetooth/mesh.h>
#include "crypto.h"
#include "access.h"
int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf,
struct bt_mesh_dfu_metadata *metadata)
{
if (buf->len < 12) {
return -EMSGSIZE;
}
metadata->fw_ver.major = net_buf_simple_pull_u8(buf);
metadata->fw_ver.minor = net_buf_simple_pull_u8(buf);
metadata->fw_ver.revision = net_buf_simple_pull_le16(buf);
metadata->fw_ver.build_num = net_buf_simple_pull_le32(buf);
metadata->fw_size = net_buf_simple_pull_le24(buf);
metadata->fw_core_type = net_buf_simple_pull_u8(buf);
if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) {
if (buf->len < 6) {
return -EMSGSIZE;
}
metadata->comp_hash = net_buf_simple_pull_le32(buf);
metadata->elems = net_buf_simple_pull_le16(buf);
}
metadata->user_data = buf->len > 0 ? buf->data : NULL;
metadata->user_data_len = buf->len;
return 0;
}
int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata,
struct net_buf_simple *buf)
{
size_t md_len_min = 12 + metadata->user_data_len;
if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) {
md_len_min += 6;
}
if (net_buf_simple_tailroom(buf) < md_len_min) {
return -EMSGSIZE;
}
net_buf_simple_add_u8(buf, metadata->fw_ver.major);
net_buf_simple_add_u8(buf, metadata->fw_ver.minor);
net_buf_simple_add_le16(buf, metadata->fw_ver.revision);
net_buf_simple_add_le32(buf, metadata->fw_ver.build_num);
net_buf_simple_add_le24(buf, metadata->fw_size);
net_buf_simple_add_u8(buf, metadata->fw_core_type);
net_buf_simple_add_le32(buf, metadata->comp_hash);
net_buf_simple_add_le16(buf, metadata->elems);
if (metadata->user_data_len > 0) {
net_buf_simple_add_mem(buf, metadata->user_data, metadata->user_data_len);
}
return 0;
}
int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, uint8_t *key, uint32_t *hash)
{
uint8_t mac[16];
int err;
struct bt_mesh_sg sg = {.data = buf->data, .len = buf->len};
err = bt_mesh_aes_cmac_raw_key(key, &sg, 1, mac);
if (err) {
return err;
}
*hash = sys_get_le32(mac);
return 0;
}
int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash)
{
NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX);
int err;
err = bt_mesh_comp_data_get_page_0(&buf, 0);
if (err) {
return err;
}
err = bt_mesh_dfu_metadata_comp_hash_get(&buf, key, hash);
return err;
}
@@ -0,0 +1,402 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/settings/settings.h>
#include "dfu_slot.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/sys/util.h>
#include <common/bt_str.h>
#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_dfu_slot);
#define SLOT_ENTRY_BUFLEN 25
#define DFU_SLOT_SETTINGS_PATH "bt/mesh-dfu/slot"
#define HEADER_SIZE offsetof(struct slot, slot.fwid)
#define PROP_HEADER "h"
#define PROP_FWID "id"
#define PROP_METADATA "m"
static sys_slist_t list;
static struct slot {
uint32_t idx;
struct bt_mesh_dfu_slot slot;
sys_snode_t n;
} slots[CONFIG_BT_MESH_DFU_SLOT_CNT];
static uint32_t slot_index;
static char *slot_entry_encode(uint16_t idx, char buf[SLOT_ENTRY_BUFLEN],
const char *property)
{
snprintf(buf, SLOT_ENTRY_BUFLEN, DFU_SLOT_SETTINGS_PATH "/%x/%s", idx,
property);
return buf;
}
static bool slot_eq(const struct bt_mesh_dfu_slot *slot,
const uint8_t *fwid, size_t fwid_len)
{
return (slot->fwid_len == fwid_len) &&
!memcmp(fwid, slot->fwid, fwid_len);
}
static bool is_slot_committed(struct slot *slot_to_check)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (s == slot_to_check) {
return true;
}
}
return false;
}
static int slot_store(const struct slot *slot_to_store)
{
uint16_t idx = ARRAY_INDEX(slots, slot_to_store);
char buf[SLOT_ENTRY_BUFLEN];
int err;
err = settings_save_one(slot_entry_encode(idx, buf, PROP_HEADER),
slot_to_store, HEADER_SIZE);
if (err) {
return err;
}
err = settings_save_one(slot_entry_encode(idx, buf, PROP_FWID),
slot_to_store->slot.fwid, slot_to_store->slot.fwid_len);
if (err) {
return err;
}
err = settings_save_one(slot_entry_encode(idx, buf,
PROP_METADATA),
slot_to_store->slot.metadata, slot_to_store->slot.metadata_len);
return err;
}
static void slot_erase(struct slot *slot_to_erase)
{
uint16_t idx = ARRAY_INDEX(slots, slot_to_erase);
char buf[SLOT_ENTRY_BUFLEN];
(void)settings_delete(slot_entry_encode(idx, buf, PROP_HEADER));
(void)settings_delete(slot_entry_encode(idx, buf, PROP_FWID));
(void)settings_delete(slot_entry_encode(idx, buf, PROP_METADATA));
}
static void slot_index_defrag(void)
{
slot_index = 0;
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
s->idx = ++slot_index;
slot_store(s);
}
}
int bt_mesh_dfu_slot_count(void)
{
int cnt = 0;
sys_snode_t *n;
SYS_SLIST_FOR_EACH_NODE(&list, n) {
cnt++;
}
return cnt;
}
struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void)
{
struct slot *slot = NULL;
for (int i = 0; i < ARRAY_SIZE(slots); ++i) {
if (slots[i].idx == 0) {
slot = &slots[i];
break;
}
}
if (!slot) {
LOG_WRN("No space");
return NULL;
}
if (slot_index == UINT32_MAX) {
slot_index_defrag();
}
slot->slot.fwid_len = 0;
slot->slot.metadata_len = 0;
slot->slot.size = 0;
slot->idx = ++slot_index;
LOG_DBG("Reserved slot #%u", slot - &slots[0]);
return &slot->slot;
}
int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size,
const uint8_t *metadata, size_t metadata_len)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (metadata_len > CONFIG_BT_MESH_DFU_METADATA_MAXLEN) {
return -EFBIG;
}
if (slot->idx == 0 || is_slot_committed(slot)) {
return -EINVAL;
}
slot->slot.size = size;
slot->slot.metadata_len = metadata_len;
memcpy(slot->slot.metadata, metadata, metadata_len);
return 0;
}
int bt_mesh_dfu_slot_fwid_set(struct bt_mesh_dfu_slot *dfu_slot,
const uint8_t *fwid, size_t fwid_len)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) {
return -EFBIG;
}
if (slot->idx == 0 || is_slot_committed(slot)) {
return -EINVAL;
}
for (int i = 0; i < ARRAY_SIZE(slots); i++) {
if (slots[i].idx != 0 &&
slot_eq(&slots[i].slot, fwid, fwid_len)) {
return is_slot_committed(&slots[i]) ?
-EEXIST : -EALREADY;
}
}
slot->slot.fwid_len = fwid_len;
memcpy(slot->slot.fwid, fwid, fwid_len);
return 0;
}
int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot)
{
int err;
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (slot->idx == 0 ||
slot->slot.fwid_len == 0 ||
slot->slot.size == 0 ||
is_slot_committed(slot)) {
return -EINVAL;
}
err = slot_store(slot);
if (err) {
LOG_WRN("Store failed (err: %d)", err);
return err;
}
sys_slist_append(&list, &slot->n);
LOG_DBG("Stored slot #%u: %s", ARRAY_INDEX(slots, slot),
bt_hex(slot->slot.fwid, slot->slot.fwid_len));
return 0;
}
void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (is_slot_committed(slot)) {
return;
}
slot->idx = 0;
}
int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (!sys_slist_find_and_remove(&list, &slot->n)) {
return -EINVAL;
}
int idx = ARRAY_INDEX(slots, slot);
LOG_DBG("%u", idx);
slot_erase(slot);
slot->idx = 0;
return 0;
}
void bt_mesh_dfu_slot_del_all(void)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
slot_erase(s);
s->idx = 0;
}
sys_slist_init(&list);
}
const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (!img_idx--) {
return &s->slot;
}
}
return NULL;
}
int bt_mesh_dfu_slot_get(const uint8_t *fwid, size_t fwid_len, struct bt_mesh_dfu_slot **slot)
{
struct slot *s;
int idx = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (slot_eq(&s->slot, fwid, fwid_len)) {
if (slot) {
*slot = &s->slot;
}
return idx;
}
idx++;
}
return -ENOENT;
}
int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *s;
int idx = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (&s->slot == dfu_slot) {
return idx;
}
idx++;
}
return -ENOENT;
}
size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data)
{
enum bt_mesh_dfu_iter iter;
size_t cnt = 0;
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
cnt++;
if (!cb) {
continue;
}
iter = cb(&s->slot, user_data);
if (iter != BT_MESH_DFU_ITER_CONTINUE) {
break;
}
}
return cnt;
}
static int slot_data_load(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const char *prop;
size_t len;
uint16_t idx;
idx = strtol(key, NULL, 16);
if (idx >= ARRAY_SIZE(slots)) {
return 0;
}
len = settings_name_next(key, &prop);
if (!strncmp(prop, PROP_HEADER, len)) {
if (read_cb(cb_arg, &slots[idx], HEADER_SIZE) > 0) {
struct slot *s, *prev = NULL;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (s->idx > slots[idx].idx) {
break;
}
prev = s;
}
if (prev == NULL) {
sys_slist_prepend(&list, &slots[idx].n);
} else {
sys_slist_insert(&list, &prev->n, &slots[idx].n);
}
if (slots[idx].idx >= slot_index) {
slot_index = slots[idx].idx + 1;
}
}
return 0;
}
if (!strncmp(prop, PROP_FWID, len)) {
if (read_cb(cb_arg, &slots[idx].slot.fwid,
sizeof(slots[idx].slot.fwid)) < 0) {
slots[idx].idx = 0;
sys_slist_find_and_remove(&list, &slots[idx].n);
return 0;
}
slots[idx].slot.fwid_len = len_rd;
return 0;
}
if (!strncmp(prop, PROP_METADATA, len)) {
if (read_cb(cb_arg, &slots[idx].slot.metadata,
sizeof(slots[idx].slot.metadata)) < 0) {
slots[idx].idx = 0;
sys_slist_find_and_remove(&list, &slots[idx].n);
return 0;
}
slots[idx].slot.metadata_len = len_rd;
return 0;
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh_dfu_slots, DFU_SLOT_SETTINGS_PATH, NULL,
slot_data_load, NULL, NULL);
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/mesh.h>
/** @brief Slot iteration callback.
*
* @param slot A valid DFU image slot.
* @param user_data User data passed to @ref bt_mesh_dfu_slot_foreach.
*
* @return Iteration action determining next step.
*/
typedef enum bt_mesh_dfu_iter (*bt_mesh_dfu_slot_cb_t)(
const struct bt_mesh_dfu_slot *slot, void *user_data);
/** @brief Get the number of slots committed to the firmware list.
*
* @return Number of committed slots.
*/
int bt_mesh_dfu_slot_count(void);
/** @brief Reserve a new DFU image slot for a distributable image.
*
* A DFU image slot represents a single distributable DFU image with all its
* metadata. The slot data must be set using @ref bt_mesh_dfu_slot_info_set and
* @ref bt_mesh_dfu_slot_fwid_set, and the slot committed using
* @ref bt_mesh_dfu_slot_commit for the slot to be considered part of the slot
* list.
*
* @return A pointer to the reserved slot, or NULL if allocation failed.
*/
struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void);
/** @brief Set the size and metadata for a reserved slot.
*
* @param dfu_slot Pointer to the reserved slot for which to set the
* metadata.
* @param size The size of the image.
* @param metadata Metadata or NULL.
* @param metadata_len Length of the metadata, at most @c
* CONFIG_BT_MESH_DFU_METADATA_MAXLEN.
*
* @return 0 on success, (negative) error code otherwise.
*/
int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size,
const uint8_t *metadata, size_t metadata_len);
/** @brief Set the new fwid for the incoming image for a reserved slot.
*
* @param dfu_slot Pointer to the reserved slot for which to set the fwid.
* @param fwid Fwid to set.
* @param fwid_len Length of the fwid, at most @c
* CONFIG_BT_MESH_DFU_FWID_MAXLEN.
*
* @return 0 on success, (negative) error code otherwise.
*/
int bt_mesh_dfu_slot_fwid_set(struct bt_mesh_dfu_slot *dfu_slot,
const uint8_t *fwid, size_t fwid_len);
/** @brief Commit the reserved slot to the list of slots, and store it
* persistently.
*
* If the commit fails for any reason, the slot will still be in the reserved
* state after this call.
*
* @param dfu_slot Pointer to the reserved slot.
*
* @return 0 on success, (negative) error code otherwise.
*/
int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot);
/** @brief Release a reserved slot so that it can be reserved again.
*
* @param dfu_slot Pointer to the reserved slot.
*/
void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot);
/** @brief Delete a committed DFU image slot.
*
* @param slot Slot to delete. Must be a valid pointer acquired from this
* module.
*
* @return 0 on success, or (negative) error code on failure.
*/
int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *slot);
/** @brief Delete all DFU image slots.
*
* @return 0 on success, or (negative) error code on failure.
*/
void bt_mesh_dfu_slot_del_all(void);
/** @brief Get the DFU image slot at the given firmware image list index.
*
* @param idx DFU image slot index.
*
* @return The DFU image slot at the given index, or NULL if no slot exists with the
* given index.
*/
const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx);
/** @brief Get the committed DFU image slot for the image with the given
* firmware ID.
*
* @param fwid Firmware ID.
* @param fwid_len Firmware ID length.
* @param slot Slot pointer to fill.
*
* @return Slot index on success, or negative error code on failure.
*/
int bt_mesh_dfu_slot_get(const uint8_t *fwid, size_t fwid_len, struct bt_mesh_dfu_slot **slot);
/** @brief Get the index in the firmware image list for the given slot.
*
* @param slot Slot to find.
*
* @return Slot index on success, or negative error code on failure.
*/
int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *slot);
/** @brief Iterate through all DFU image slots.
*
* Calls the callback for every DFU image slot or until the callback returns
* something other than @ref BT_MESH_DFU_ITER_CONTINUE.
*
* @param cb Callback to call for each slot, or NULL to just count the
* number of slots.
* @param user_data User data to pass to the callback.
*
* @return The number of slots iterated over.
*/
size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data);
@@ -0,0 +1,617 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/mesh.h>
#include "dfu.h"
#include "blob.h"
#include "access.h"
#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_dfu_srv);
#define UPDATE_IDX_NONE 0xff
BUILD_ASSERT((DFU_UPDATE_START_MSG_MAXLEN + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_START) +
BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX,
"The Firmware Update Start message does not fit into the maximum incoming SDU size.");
BUILD_ASSERT((DFU_UPDATE_INFO_STATUS_MSG_MINLEN +
BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_INFO_STATUS) + BT_MESH_MIC_SHORT)
<= BT_MESH_TX_SDU_MAX,
"The Firmware Update Info Status message does not fit into the maximum outgoing SDU "
"size.");
static void store_state(struct bt_mesh_dfu_srv *srv)
{
bt_mesh_model_data_store(srv->mod, false, NULL, &srv->update,
sizeof(srv->update));
}
static void erase_state(struct bt_mesh_dfu_srv *srv)
{
bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0);
}
static void xfer_failed(struct bt_mesh_dfu_srv *srv)
{
if (!bt_mesh_dfu_srv_is_busy(srv) ||
srv->update.idx >= srv->img_count) {
return;
}
erase_state(srv);
if (srv->cb->end) {
srv->cb->end(srv, &srv->imgs[srv->update.idx], false);
}
}
static enum bt_mesh_dfu_status metadata_check(struct bt_mesh_dfu_srv *srv,
uint8_t idx,
struct net_buf_simple *buf,
enum bt_mesh_dfu_effect *effect)
{
*effect = BT_MESH_DFU_EFFECT_NONE;
if (idx >= srv->img_count) {
return BT_MESH_DFU_ERR_FW_IDX;
}
if (!srv->cb->check) {
return BT_MESH_DFU_SUCCESS;
}
if (srv->cb->check(srv, &srv->imgs[idx], buf, effect)) {
*effect = BT_MESH_DFU_EFFECT_NONE;
return BT_MESH_DFU_ERR_METADATA;
}
return BT_MESH_DFU_SUCCESS;
}
static void apply_rsp_sent(int err, void *cb_params)
{
struct bt_mesh_dfu_srv *srv = cb_params;
if (err) {
/* return phase back to give client one more chance. */
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK;
LOG_WRN("Apply response failed, wait for retry (err %d)", err);
return;
}
LOG_DBG("");
if (!srv->cb->apply || srv->update.idx == UPDATE_IDX_NONE) {
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
store_state(srv);
LOG_DBG("Prerequisites for apply callback are wrong");
return;
}
store_state(srv);
err = srv->cb->apply(srv, &srv->imgs[srv->update.idx]);
if (err) {
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
store_state(srv);
LOG_DBG("Application apply callback failed (err %d)", err);
}
}
static void apply_rsp_sending(uint16_t duration, int err, void *cb_params)
{
if (err) {
apply_rsp_sent(err, cb_params);
}
}
static void verify(struct bt_mesh_dfu_srv *srv)
{
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY;
if (srv->update.idx >= srv->img_count) {
bt_mesh_dfu_srv_rejected(srv);
return;
}
if (!srv->cb->end) {
bt_mesh_dfu_srv_verified(srv);
return;
}
srv->cb->end(srv, &srv->imgs[srv->update.idx], true);
if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY) {
store_state(srv);
}
}
static int handle_info_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
uint8_t idx, limit;
if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) {
LOG_INF("Still applying, not responding");
return -EBUSY;
}
idx = net_buf_simple_pull_u8(buf);
limit = net_buf_simple_pull_u8(buf);
LOG_DBG("from %u (limit: %u)", idx, limit);
NET_BUF_SIMPLE_DEFINE(rsp, BT_MESH_TX_SDU_MAX);
bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_INFO_STATUS);
net_buf_simple_add_u8(&rsp, srv->img_count);
net_buf_simple_add_u8(&rsp, idx);
for (; idx < srv->img_count && limit > 0; ++idx) {
uint32_t entry_len;
if (!srv->imgs[idx].fwid) {
continue;
}
entry_len = 2 + srv->imgs[idx].fwid_len;
if (srv->imgs[idx].uri) {
entry_len += strlen(srv->imgs[idx].uri);
}
if (net_buf_simple_tailroom(&rsp) + BT_MESH_MIC_SHORT < entry_len) {
break;
}
net_buf_simple_add_u8(&rsp, srv->imgs[idx].fwid_len);
net_buf_simple_add_mem(&rsp, srv->imgs[idx].fwid,
srv->imgs[idx].fwid_len);
if (srv->imgs[idx].uri) {
size_t len = strlen(srv->imgs[idx].uri);
net_buf_simple_add_u8(&rsp, len);
net_buf_simple_add_mem(&rsp, srv->imgs[idx].uri, len);
} else {
net_buf_simple_add_u8(&rsp, 0);
}
limit--;
}
if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) {
ctx->send_ttl = srv->update.ttl;
}
bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL);
return 0;
}
static int handle_metadata_check(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
enum bt_mesh_dfu_status status;
enum bt_mesh_dfu_effect effect;
uint8_t idx;
BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS, 2);
bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS);
idx = net_buf_simple_pull_u8(buf);
status = metadata_check(srv, idx, buf, &effect);
LOG_DBG("%u", idx);
net_buf_simple_add_u8(&rsp, (status & BIT_MASK(3)) | (effect << 3));
net_buf_simple_add_u8(&rsp, idx);
if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) {
ctx->send_ttl = srv->update.ttl;
}
bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL);
return 0;
}
static void update_status_rsp(struct bt_mesh_dfu_srv *srv,
struct bt_mesh_msg_ctx *ctx,
enum bt_mesh_dfu_status status,
const struct bt_mesh_send_cb *send_cb)
{
BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_STATUS, 14);
bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_STATUS);
net_buf_simple_add_u8(&buf, ((status & BIT_MASK(3)) |
(srv->update.phase << 5)));
if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) {
net_buf_simple_add_u8(&buf, srv->update.ttl);
net_buf_simple_add_u8(&buf, srv->update.effect);
net_buf_simple_add_le16(&buf, srv->update.timeout_base);
net_buf_simple_add_le64(&buf, srv->blob.state.xfer.id);
net_buf_simple_add_u8(&buf, srv->update.idx);
ctx->send_ttl = srv->update.ttl;
}
bt_mesh_model_send(srv->mod, ctx, &buf, send_cb, srv);
}
static int handle_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
LOG_DBG("");
update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL);
return 0;
}
static inline bool is_active_update(struct bt_mesh_dfu_srv *srv, uint8_t idx,
uint16_t timeout_base,
const uint64_t *blob_id, uint8_t ttl,
uint16_t meta_checksum)
{
return (srv->update.idx != idx || srv->blob.state.xfer.id != *blob_id ||
srv->update.ttl != ttl ||
srv->update.timeout_base != timeout_base ||
srv->update.meta != meta_checksum);
}
static int handle_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
const struct bt_mesh_blob_io *io;
uint16_t timeout_base, meta_checksum;
enum bt_mesh_dfu_status status;
uint8_t ttl, idx;
uint64_t blob_id;
int err;
struct net_buf_simple_state buf_state;
ttl = net_buf_simple_pull_u8(buf);
timeout_base = net_buf_simple_pull_le16(buf);
blob_id = net_buf_simple_pull_le64(buf);
idx = net_buf_simple_pull_u8(buf);
meta_checksum = dfu_metadata_checksum(buf);
LOG_DBG("%u ttl: %u extra time: %u", idx, ttl, timeout_base);
if ((!buf->len || meta_checksum == srv->update.meta) &&
srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ERR &&
srv->update.ttl == ttl &&
srv->update.timeout_base == timeout_base &&
srv->update.idx == idx &&
srv->blob.state.xfer.id == blob_id) {
srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE;
status = BT_MESH_DFU_SUCCESS;
store_state(srv);
/* blob srv will resume the transfer. */
LOG_DBG("Resuming transfer");
goto rsp;
}
if (bt_mesh_dfu_srv_is_busy(srv)) {
if (is_active_update(srv, idx, timeout_base, &blob_id, ttl,
meta_checksum)) {
status = BT_MESH_DFU_ERR_WRONG_PHASE;
} else {
status = BT_MESH_DFU_SUCCESS;
srv->update.ttl = ttl;
srv->blob.state.xfer.id = blob_id;
}
LOG_WRN("Busy. Phase: %u", srv->update.phase);
goto rsp;
}
net_buf_simple_save(buf, &buf_state);
status = metadata_check(srv, idx, buf,
(enum bt_mesh_dfu_effect *)&srv->update.effect);
net_buf_simple_restore(buf, &buf_state);
if (status != BT_MESH_DFU_SUCCESS) {
goto rsp;
}
srv->update.ttl = ttl;
srv->update.timeout_base = timeout_base;
srv->update.meta = meta_checksum;
io = NULL;
err = srv->cb->start(srv, &srv->imgs[idx], buf, &io);
if (err == -EALREADY || (!err && bt_mesh_has_addr(ctx->addr))) {
/* This image has already been received or this is a
* self-update. Skip the transfer phase and proceed to
* verifying update.
*/
status = BT_MESH_DFU_SUCCESS;
srv->update.idx = idx;
srv->blob.state.xfer.id = blob_id;
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY;
update_status_rsp(srv, ctx, status, NULL);
verify(srv);
return 0;
}
if (err == -ENOMEM) {
status = BT_MESH_DFU_ERR_RESOURCES;
goto rsp;
}
if (err == -EBUSY) {
status = BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE;
goto rsp;
}
if (err || !io || !io->wr) {
status = BT_MESH_DFU_ERR_INTERNAL;
goto rsp;
}
err = bt_mesh_blob_srv_recv(&srv->blob, blob_id, io,
ttl, timeout_base);
if (err) {
status = BT_MESH_DFU_ERR_BLOB_XFER_BUSY;
goto rsp;
}
srv->update.idx = idx;
srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE;
status = BT_MESH_DFU_SUCCESS;
store_state(srv);
rsp:
update_status_rsp(srv, ctx, status, NULL);
return 0;
}
static int handle_cancel(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
if (srv->update.idx == UPDATE_IDX_NONE) {
goto rsp;
}
LOG_DBG("");
bt_mesh_blob_srv_cancel(&srv->blob);
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
xfer_failed(srv);
rsp:
update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL);
return 0;
}
static int handle_apply(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
static const struct bt_mesh_send_cb send_cb = {
.start = apply_rsp_sending,
.end = apply_rsp_sent,
};
LOG_DBG("");
if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) {
update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL);
return 0;
}
if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_OK) {
LOG_WRN("Apply: Invalid phase %u", srv->update.phase);
update_status_rsp(srv, ctx, BT_MESH_DFU_ERR_WRONG_PHASE, NULL);
return 0;
}
/* Postponing the apply callback until the response has been sent, in
* case it triggers a reboot:
*/
srv->update.phase = BT_MESH_DFU_PHASE_APPLYING;
update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, &send_cb);
return 0;
}
const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[] = {
{ BT_MESH_DFU_OP_UPDATE_INFO_GET, BT_MESH_LEN_EXACT(2), handle_info_get },
{ BT_MESH_DFU_OP_UPDATE_METADATA_CHECK, BT_MESH_LEN_MIN(1), handle_metadata_check },
{ BT_MESH_DFU_OP_UPDATE_GET, BT_MESH_LEN_EXACT(0), handle_get },
{ BT_MESH_DFU_OP_UPDATE_START, BT_MESH_LEN_MIN(12), handle_start },
{ BT_MESH_DFU_OP_UPDATE_CANCEL, BT_MESH_LEN_EXACT(0), handle_cancel },
{ BT_MESH_DFU_OP_UPDATE_APPLY, BT_MESH_LEN_EXACT(0), handle_apply },
BT_MESH_MODEL_OP_END,
};
static int dfu_srv_init(const struct bt_mesh_model *mod)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
srv->mod = mod;
srv->update.idx = UPDATE_IDX_NONE;
if (!srv->cb || !srv->cb->start || !srv->imgs || srv->img_count == 0 ||
srv->img_count == UPDATE_IDX_NONE) {
LOG_ERR("Invalid DFU Server initialization");
return -EINVAL;
}
if (IS_ENABLED(CONFIG_BT_MESH_MODEL_EXTENSIONS)) {
bt_mesh_model_extend(mod, srv->blob.mod);
}
return 0;
}
static int dfu_srv_settings_set(const struct bt_mesh_model *mod, const char *name,
size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
ssize_t len;
if (len_rd < sizeof(srv->update)) {
return -EINVAL;
}
len = read_cb(cb_arg, &srv->update, sizeof(srv->update));
if (len < 0) {
return len;
}
LOG_DBG("Recovered transfer (phase: %u, idx: %u)", srv->update.phase,
srv->update.idx);
if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) {
LOG_DBG("Settings recovered mid-transfer, setting transfer error");
srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR;
} else if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY_OK) {
LOG_DBG("Settings recovered before application, setting verification fail");
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL;
}
return 0;
}
static void dfu_srv_reset(const struct bt_mesh_model *mod)
{
struct bt_mesh_dfu_srv *srv = mod->rt->user_data;
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
erase_state(srv);
}
const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb = {
.init = dfu_srv_init,
.settings_set = dfu_srv_settings_set,
.reset = dfu_srv_reset,
};
static void blob_suspended(struct bt_mesh_blob_srv *b)
{
struct bt_mesh_dfu_srv *srv = CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob);
srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR;
store_state(srv);
}
static void blob_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success)
{
struct bt_mesh_dfu_srv *srv =
CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob);
LOG_DBG("success: %u", success);
if (!success) {
srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR;
xfer_failed(srv);
return;
}
verify(srv);
}
static int blob_recover(struct bt_mesh_blob_srv *b,
struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_io **io)
{
struct bt_mesh_dfu_srv *srv =
CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob);
if (!srv->cb->recover ||
srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR ||
srv->update.idx >= srv->img_count) {
return -ENOTSUP;
}
return srv->cb->recover(srv, &srv->imgs[srv->update.idx], io);
}
const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb = {
.suspended = blob_suspended,
.end = blob_end,
.recover = blob_recover,
};
void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv)
{
if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) {
LOG_WRN("Wrong state");
return;
}
LOG_DBG("");
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK;
store_state(srv);
}
void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv)
{
if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) {
LOG_WRN("Wrong state");
return;
}
LOG_DBG("");
srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL;
store_state(srv);
}
void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv)
{
if (srv->update.phase == BT_MESH_DFU_PHASE_IDLE) {
LOG_WRN("Wrong state");
return;
}
(void)bt_mesh_blob_srv_cancel(&srv->blob);
}
void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv)
{
if (srv->update.phase != BT_MESH_DFU_PHASE_APPLYING) {
LOG_WRN("Wrong state");
return;
}
LOG_DBG("");
srv->update.phase = BT_MESH_DFU_PHASE_IDLE;
store_state(srv);
}
bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv)
{
return srv->update.phase != BT_MESH_DFU_PHASE_IDLE &&
srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR &&
srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_FAIL;
}
uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv)
{
if (!bt_mesh_dfu_srv_is_busy(srv)) {
return 0U;
}
if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) {
return bt_mesh_blob_srv_progress(&srv->blob);
}
return 100U;
}
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__
#include <zephyr/bluetooth/mesh.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup bt_mesh_dfd Firmware Distribution models
* @ingroup bt_mesh
* @{
*/
/** Firmware distribution status. */
enum bt_mesh_dfd_status {
/** The message was processed successfully. */
BT_MESH_DFD_SUCCESS,
/** Insufficient resources on the node. */
BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES,
/** The operation cannot be performed while the Server is in the current
* phase.
*/
BT_MESH_DFD_ERR_WRONG_PHASE,
/** An internal error occurred on the node. */
BT_MESH_DFD_ERR_INTERNAL,
/** The requested firmware image is not stored on the Distributor. */
BT_MESH_DFD_ERR_FW_NOT_FOUND,
/** The AppKey identified by the AppKey Index is not known to the node.
*/
BT_MESH_DFD_ERR_INVALID_APPKEY_INDEX,
/** There are no Target nodes in the Distribution Receivers List
* state.
*/
BT_MESH_DFD_ERR_RECEIVERS_LIST_EMPTY,
/** Another firmware image distribution is in progress. */
BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION,
/** Another upload is in progress. */
BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD,
/** The URI scheme name indicated by the Update URI is not supported. */
BT_MESH_DFD_ERR_URI_NOT_SUPPORTED,
/** The format of the Update URI is invalid. */
BT_MESH_DFD_ERR_URI_MALFORMED,
/** The URI is currently unreachable. */
BT_MESH_DFD_ERR_URI_UNREACHABLE,
/** The Check Firmware OOB procedure did not find any new firmware. */
BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE,
/** The suspension of the Distribute Firmware procedure failed. */
BT_MESH_DFD_ERR_SUSPEND_FAILED,
};
/** Firmware distribution phases. */
enum bt_mesh_dfd_phase {
/** No firmware distribution is in progress. */
BT_MESH_DFD_PHASE_IDLE,
/** Firmware distribution is in progress. */
BT_MESH_DFD_PHASE_TRANSFER_ACTIVE,
/** The Transfer BLOB procedure has completed successfully. */
BT_MESH_DFD_PHASE_TRANSFER_SUCCESS,
/** The Apply Firmware on Target Nodes procedure is being executed. */
BT_MESH_DFD_PHASE_APPLYING_UPDATE,
/** The Distribute Firmware procedure has completed successfully. */
BT_MESH_DFD_PHASE_COMPLETED,
/** The Distribute Firmware procedure has failed. */
BT_MESH_DFD_PHASE_FAILED,
/** The Cancel Firmware Update procedure is being executed. */
BT_MESH_DFD_PHASE_CANCELING_UPDATE,
/** The Transfer BLOB procedure is suspended. */
BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED,
};
/** Firmware upload phases. */
enum bt_mesh_dfd_upload_phase {
/** No firmware upload is in progress. */
BT_MESH_DFD_UPLOAD_PHASE_IDLE,
/** The Store Firmware procedure is being executed. */
BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE,
/** The Store Firmware procedure or Store Firmware OOB procedure failed.
*/
BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR,
/** The Store Firmware procedure or the Store Firmware OOB procedure
* completed successfully.
*/
BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS,
};
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_H__ */
@@ -0,0 +1,313 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @defgroup bt_mesh_dfd_srv Firmware Distribution Server model
* @ingroup bt_mesh_dfd
* @{
* @brief API for the Firmware Distribution Server model
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__
#include <zephyr/bluetooth/mesh/access.h>
#include <zephyr/bluetooth/mesh/dfd.h>
#include <zephyr/bluetooth/mesh/blob_srv.h>
#include <zephyr/bluetooth/mesh/blob_cli.h>
#include <zephyr/bluetooth/mesh/dfu_cli.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX
#define CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX 0
#endif
#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE
#define CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE 0
#endif
#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE
#define CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE 0
#endif
struct bt_mesh_dfd_srv;
#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD
/**
*
* @brief Initialization parameters for the @ref bt_mesh_dfd_srv with OOB
* upload support.
*
* @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb instance.
* @param[in] _oob_schemes Array of OOB schemes supported by the server,
* each scheme being a code point from the
* Bluetooth SIG Assigned Numbers document.
* @param[in] _oob_schemes_count Number of schemes in @c _oob_schemes.
*/
#define BT_MESH_DFD_SRV_OOB_INIT(_cb, _oob_schemes, _oob_schemes_count) \
{ \
.cb = _cb, \
.dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \
.upload = { \
.blob = { .cb = &_bt_mesh_dfd_srv_blob_cb }, \
}, \
.oob_schemes = { \
.schemes = _oob_schemes, \
.count = _oob_schemes_count, \
}, \
}
#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */
/**
*
* @brief Initialization parameters for the @ref bt_mesh_dfd_srv.
*
* @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb instance.
*/
#define BT_MESH_DFD_SRV_INIT(_cb) \
{ \
.cb = _cb, \
.dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \
.upload = { \
.blob = { .cb = &_bt_mesh_dfd_srv_blob_cb }, \
}, \
}
/**
*
* @brief Firmware Distribution Server model Composition Data entry.
*
* @param _srv Pointer to a @ref bt_mesh_dfd_srv instance.
*/
#define BT_MESH_MODEL_DFD_SRV(_srv) \
BT_MESH_MODEL_DFU_CLI(&(_srv)->dfu), \
BT_MESH_MODEL_BLOB_SRV(&(_srv)->upload.blob), \
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFD_SRV, _bt_mesh_dfd_srv_op, NULL, \
_srv, &_bt_mesh_dfd_srv_cb)
/** Firmware Distribution Server callbacks: */
struct bt_mesh_dfd_srv_cb {
/** @brief Slot receive callback.
*
* Called at the start of an upload procedure. The callback must fill
* @c io with a pointer to a writable BLOB stream for the Firmware Distribution
* Server to write the firmware image to.
*
* @param srv Firmware Distribution Server model instance.
* @param slot DFU image slot being received.
* @param io BLOB stream response pointer.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*recv)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot,
const struct bt_mesh_blob_io **io);
#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD
/** @brief Firmware upload OOB start callback.
*
* Called at the start of an OOB firmware upload. The application must
* start a firmware check using an OOB mechanism, and then call
* @ref bt_mesh_dfd_srv_oob_check_complete. Depending on the return
* value of this function, the application must then start storing the
* firmware image using an OOB mechanism, and call
* @ref bt_mesh_dfd_srv_oob_store_complete. This callback is mandatory
* to support OOB uploads.
*
* @param srv Firmware Distribution Server model instance.
* @param slot Slot to be used for the upload.
* @param uri Pointer to buffer containing the URI used to
* check for new firmware.
* @param uri_len Length of the URI buffer.
* @param fwid Pointer to buffer containing the current
* firmware ID to be used when checking for
* availability of new firmware.
* @param fwid_len Length of the current firmware ID. Must be set
* to the length of the new firmware ID if it is
* available, or to 0 if new firmware is not
* available.
*
* @return BT_MESH_DFD_SUCCESS on success, or error code otherwise.
*/
int (*start_oob_upload)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot,
const char *uri, uint8_t uri_len,
const uint8_t *fwid, uint16_t fwid_len);
/** @brief Cancel store OOB callback
*
* Called when an OOB store is cancelled. The application must stop
* any ongoing OOB image transfer. This callback is mandatory to
* support OOB uploads.
*
* @param srv Firmware Distribution Server model instance.
* @param slot DFU image slot to cancel
*/
void (*cancel_oob_upload)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot);
/** @brief Get the progress of an ongoing OOB store
*
* Called by the Firmware Distribution Server model when it needs to
* get the current progress of an ongoing OOB store from the
* application. This callback is mandatory to support OOB uploads.
*
* @param srv Firmware Distribution Server model instance.
* @param slot DFU image slot to get progress for.
*
* @return The current progress of the ongoing OOB store, in percent.
*/
uint8_t (*oob_progress_get)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot);
#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */
/** @brief Slot delete callback.
*
* Called when the Firmware Distribution Server is about to delete a DFU image slot.
* All allocated data associated with the firmware slot should be
* deleted.
*
* @param srv Firmware Update Server instance.
* @param slot DFU image slot being deleted.
*/
void (*del)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot);
/** @brief Slot send callback.
*
* Called at the start of a distribution procedure. The callback must
* fill @c io with a pointer to a readable BLOB stream for the Firmware
* Distribution Server to read the firmware image from.
*
* @param srv Firmware Distribution Server model instance.
* @param slot DFU image slot being sent.
* @param io BLOB stream response pointer.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*send)(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot,
const struct bt_mesh_blob_io **io);
/** @brief Phase change callback (Optional).
*
* Called whenever the phase of the Firmware Distribution Server changes.
*
* @param srv Firmware Distribution Server model instance.
* @param phase New Firmware Distribution phase.
*/
void (*phase)(struct bt_mesh_dfd_srv *srv, enum bt_mesh_dfd_phase phase);
};
/** Firmware Distribution Server instance. */
struct bt_mesh_dfd_srv {
const struct bt_mesh_dfd_srv_cb *cb;
const struct bt_mesh_model *mod;
struct bt_mesh_dfu_cli dfu;
struct bt_mesh_dfu_target targets[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX];
struct bt_mesh_blob_target_pull pull_ctxs[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX];
const struct bt_mesh_blob_io *io;
uint16_t target_cnt;
uint16_t slot_idx;
bool apply;
enum bt_mesh_dfd_phase phase;
struct bt_mesh_blob_cli_inputs inputs;
struct {
enum bt_mesh_dfd_upload_phase phase;
struct bt_mesh_dfu_slot *slot;
const struct flash_area *area;
struct bt_mesh_blob_srv blob;
#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD
bool is_oob;
bool is_pending_oob_check;
struct {
uint8_t uri_len;
uint8_t uri[CONFIG_BT_MESH_DFU_URI_MAXLEN];
uint16_t current_fwid_len;
uint8_t current_fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN];
struct bt_mesh_msg_ctx ctx;
} oob;
#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */
} upload;
#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD
struct {
const uint8_t *schemes;
const uint8_t count;
} oob_schemes;
#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */
};
#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD
/** @brief Call when an OOB check has completed or failed
*
* This should be called by the application after an OOB check started by the @c start_oob_upload
* callback has completed or failed. The @p status param should be set to one of the following
* values:
*
* * @c BT_MESH_DFD_SUCCESS if the check was successful and a new firmware ID was found.
* * @c BT_MESH_DFD_ERR_URI_MALFORMED if the URI is not formatted correctly.
* * @c BT_MESH_DFD_ERR_URI_NOT_SUPPORTED if the URI scheme is not supported by the node.
* * @c BT_MESH_DFD_ERR_URI_UNREACHABLE if the URI can't be reached.
* * @c BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE if the check completes successfully but no new
* firmware is available.
*
* If this function returns 0, the application should then download the firmware to the
* slot. If an error code is returned, the application should abort the OOB upload.
*
* @param srv Firmware Distribution Server model instance.
* @param slot The slot used in the OOB upload.
* @param status The result of the firmware check.
* @param fwid If the check was successful and new firmware found, this should point to a
* buffer containing the new firmware ID to store.
* @param fwid_len The length of the firmware ID pointed to by @p fwid.
*
* @return 0 on success, (negative) error code otherwise.
*/
int bt_mesh_dfd_srv_oob_check_complete(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot, int status,
uint8_t *fwid, size_t fwid_len);
/** @brief Call when an OOB store has completed or failed
*
* This should be called by the application after an OOB store started after a successful call to
* @c bt_mesh_dfd_srv_oob_check_complete has completed successfully or failed.
*
* @param srv Firmware Distribution Server model instance.
* @param slot The slot used when storing the firmware image.
* @param success @c true if the OOB store completed successfully, @c false otherwise.
* @param size The size of the stored firmware image, in bytes.
* @param metadata Pointer to the metadata received OOB, or @c NULL if no metadata was
* received.
* @param metadata_len Size of the metadata pointed to by @p metadata.
*
* @return 0 on success, (negative) error code otherwise.
*/
int bt_mesh_dfd_srv_oob_store_complete(struct bt_mesh_dfd_srv *srv,
const struct bt_mesh_dfu_slot *slot, bool success,
size_t size, const uint8_t *metadata, size_t metadata_len);
#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */
/** @cond INTERNAL_HIDDEN */
extern const struct bt_mesh_model_op _bt_mesh_dfd_srv_op[];
extern const struct bt_mesh_model_cb _bt_mesh_dfd_srv_cb;
extern const struct bt_mesh_dfu_cli_cb _bt_mesh_dfd_srv_dfu_cb;
extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfd_srv_blob_cb;
/** @endcond */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_H__ */
/** @} */
@@ -0,0 +1,171 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__
#include <sys/types.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/mesh/blob.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup bt_mesh_dfu Bluetooth Mesh Device Firmware Update
* @ingroup bt_mesh
* @{
*/
#ifndef CONFIG_BT_MESH_DFU_FWID_MAXLEN
#define CONFIG_BT_MESH_DFU_FWID_MAXLEN 0
#endif
#ifndef CONFIG_BT_MESH_DFU_METADATA_MAXLEN
#define CONFIG_BT_MESH_DFU_METADATA_MAXLEN 0
#endif
#ifndef CONFIG_BT_MESH_DFU_URI_MAXLEN
#define CONFIG_BT_MESH_DFU_URI_MAXLEN 0
#endif
#ifndef CONFIG_BT_MESH_DFU_SLOT_CNT
#define CONFIG_BT_MESH_DFU_SLOT_CNT 0
#endif
/** DFU transfer phase. */
enum bt_mesh_dfu_phase {
/** Ready to start a Receive Firmware procedure. */
BT_MESH_DFU_PHASE_IDLE,
/** The Transfer BLOB procedure failed. */
BT_MESH_DFU_PHASE_TRANSFER_ERR,
/** The Receive Firmware procedure is being executed. */
BT_MESH_DFU_PHASE_TRANSFER_ACTIVE,
/** The Verify Firmware procedure is being executed. */
BT_MESH_DFU_PHASE_VERIFY,
/** The Verify Firmware procedure completed successfully. */
BT_MESH_DFU_PHASE_VERIFY_OK,
/** The Verify Firmware procedure failed. */
BT_MESH_DFU_PHASE_VERIFY_FAIL,
/** The Apply New Firmware procedure is being executed. */
BT_MESH_DFU_PHASE_APPLYING,
/** Firmware transfer has been canceled. */
BT_MESH_DFU_PHASE_TRANSFER_CANCELED,
/** Firmware applying succeeded. */
BT_MESH_DFU_PHASE_APPLY_SUCCESS,
/** Firmware applying failed. */
BT_MESH_DFU_PHASE_APPLY_FAIL,
/** Phase of a node was not yet retrieved. */
BT_MESH_DFU_PHASE_UNKNOWN,
};
/** DFU status. */
enum bt_mesh_dfu_status {
/** The message was processed successfully. */
BT_MESH_DFU_SUCCESS,
/** Insufficient resources on the node */
BT_MESH_DFU_ERR_RESOURCES,
/** The operation cannot be performed while the Server is in the current
* phase.
*/
BT_MESH_DFU_ERR_WRONG_PHASE,
/** An internal error occurred on the node. */
BT_MESH_DFU_ERR_INTERNAL,
/** The message contains a firmware index value that is not expected. */
BT_MESH_DFU_ERR_FW_IDX,
/** The metadata check failed. */
BT_MESH_DFU_ERR_METADATA,
/** The Server cannot start a firmware update. */
BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE,
/** Another BLOB transfer is in progress. */
BT_MESH_DFU_ERR_BLOB_XFER_BUSY,
};
/** Expected effect of a DFU transfer. */
enum bt_mesh_dfu_effect {
/** No changes to node Composition Data. */
BT_MESH_DFU_EFFECT_NONE,
/** Node Composition Data changed and the node does not support remote
* provisioning.
*/
BT_MESH_DFU_EFFECT_COMP_CHANGE_NO_RPR,
/** Node Composition Data changed, and remote provisioning is supported.
* The node supports remote provisioning and Composition Data Page
* 0x80. Page 0x80 contains different Composition Data than Page 0x0.
*/
BT_MESH_DFU_EFFECT_COMP_CHANGE,
/** Node will be unprovisioned after the update. */
BT_MESH_DFU_EFFECT_UNPROV,
};
/** Action for DFU iteration callbacks. */
enum bt_mesh_dfu_iter {
/** Stop iterating. */
BT_MESH_DFU_ITER_STOP,
/** Continue iterating. */
BT_MESH_DFU_ITER_CONTINUE,
};
/** DFU image instance.
*
* Each DFU image represents a single updatable firmware image.
*/
struct bt_mesh_dfu_img {
/** Firmware ID. */
const void *fwid;
/** Length of the firmware ID. */
size_t fwid_len;
/** Update URI, or NULL. */
const char *uri;
};
/** DFU image slot for DFU distribution. */
struct bt_mesh_dfu_slot {
/** Size of the firmware in bytes. */
size_t size;
/** Length of the firmware ID. */
size_t fwid_len;
/** Length of the metadata. */
size_t metadata_len;
/** Firmware ID. */
uint8_t fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN];
/** Metadata. */
uint8_t metadata[CONFIG_BT_MESH_DFU_METADATA_MAXLEN];
};
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_H__ */
@@ -0,0 +1,409 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @defgroup bt_mesh_dfu_cli Firmware Uppdate Client model
* @ingroup bt_mesh_dfu
* @{
* @brief API for the Bluetooth Mesh Firmware Update Client model
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__
#include <zephyr/bluetooth/mesh/access.h>
#include <zephyr/bluetooth/mesh/blob_cli.h>
#include <zephyr/bluetooth/mesh/dfu.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_mesh_dfu_cli;
/**
*
* @brief Initialization parameters for the @ref bt_mesh_dfu_cli.
*
* @sa bt_mesh_dfu_cli_cb.
*
* @param _handlers Handler callback structure.
*/
#define BT_MESH_DFU_CLI_INIT(_handlers) \
{ \
.cb = _handlers, \
.blob = { .cb = &_bt_mesh_dfu_cli_blob_handlers }, \
}
/**
*
* @brief Firmware Update Client model Composition Data entry.
*
* @param _cli Pointer to a @ref bt_mesh_dfu_cli instance.
*/
#define BT_MESH_MODEL_DFU_CLI(_cli) \
BT_MESH_MODEL_BLOB_CLI(&(_cli)->blob), \
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_CLI, _bt_mesh_dfu_cli_op, NULL, \
_cli, &_bt_mesh_dfu_cli_cb)
/** DFU Target node. */
struct bt_mesh_dfu_target {
/** BLOB Target node */
struct bt_mesh_blob_target blob;
/** Image index on the Target node */
uint8_t img_idx;
/** Expected DFU effect, see @ref bt_mesh_dfu_effect. */
uint8_t effect;
/** Current DFU status, see @ref bt_mesh_dfu_status. */
uint8_t status;
/** Current DFU phase, see @ref bt_mesh_dfu_phase. */
uint8_t phase;
};
/** Metadata status response. */
struct bt_mesh_dfu_metadata_status {
/** Image index. */
uint8_t idx;
/** Status code. */
enum bt_mesh_dfu_status status;
/** Effect of transfer. */
enum bt_mesh_dfu_effect effect;
};
/** DFU Target node status parameters. */
struct bt_mesh_dfu_target_status {
/** Status of the previous operation. */
enum bt_mesh_dfu_status status;
/** Phase of the current DFU transfer. */
enum bt_mesh_dfu_phase phase;
/** The effect the update will have on the Target device's state. */
enum bt_mesh_dfu_effect effect;
/** BLOB ID used in the transfer. */
uint64_t blob_id;
/** Image index to transfer. */
uint8_t img_idx;
/** TTL used in the transfer. */
uint8_t ttl;
/** Additional response time for the Target nodes, in 10-second increments.
*
* The extra time can be used to give the Target nodes more time to respond
* to messages from the Client. The actual timeout will be calculated
* according to the following formula:
*
* @verbatim
* timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms * TTL)
* @endverbatim
*
* If a Target node fails to respond to a message from the Client within the
* configured transfer timeout, the Target node is dropped.
*/
uint16_t timeout_base;
};
/** @brief DFU image callback.
*
* The image callback is called for every DFU image on the Target node when
* calling @ref bt_mesh_dfu_cli_imgs_get.
*
* @param cli Firmware Update Client model instance.
* @param ctx Message context of the received message.
* @param idx Image index.
* @param total Total number of images on the Target node.
* @param img Image information for the given image index.
* @param cb_data Callback data.
*
* @retval BT_MESH_DFU_ITER_STOP Stop iterating through the image list and
* return from @ref bt_mesh_dfu_cli_imgs_get.
* @retval BT_MESH_DFU_ITER_CONTINUE Continue iterating through the image list
* if any images remain.
*/
typedef enum bt_mesh_dfu_iter (*bt_mesh_dfu_img_cb_t)(
struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t idx,
uint8_t total, const struct bt_mesh_dfu_img *img, void *cb_data);
/** Firmware Update Client event callbacks. */
struct bt_mesh_dfu_cli_cb {
/** @brief BLOB transfer is suspended.
*
* Called when the BLOB transfer is suspended due to response timeout from all Target nodes.
*
* @param cli Firmware Update Client model instance.
*/
void (*suspended)(struct bt_mesh_dfu_cli *cli);
/** @brief DFU ended.
*
* Called when the DFU transfer ends, either because all Target nodes were
* lost or because the transfer was completed successfully.
*
* @param cli Firmware Update Client model instance.
* @param reason Reason for ending.
*/
void (*ended)(struct bt_mesh_dfu_cli *cli,
enum bt_mesh_dfu_status reason);
/** @brief DFU transfer applied on all active Target nodes.
*
* Called at the end of the apply procedure started by @ref
* bt_mesh_dfu_cli_apply.
*
* @param cli Firmware Update Client model instance.
*/
void (*applied)(struct bt_mesh_dfu_cli *cli);
/** @brief DFU transfer confirmed on all active Target nodes.
*
* Called at the end of the apply procedure started by @ref
* bt_mesh_dfu_cli_confirm.
*
* @param cli Firmware Update Client model instance.
*/
void (*confirmed)(struct bt_mesh_dfu_cli *cli);
/** @brief DFU Target node was lost.
*
* A DFU Target node was dropped from the receivers list. The Target node's
* @c status is set to reflect the reason for the failure.
*
* @param cli Firmware Update Client model instance.
* @param target DFU Target node that was lost.
*/
void (*lost_target)(struct bt_mesh_dfu_cli *cli,
struct bt_mesh_dfu_target *target);
};
/** Firmware Update Client model instance.
*
* Should be initialized with @ref BT_MESH_DFU_CLI_INIT.
*/
struct bt_mesh_dfu_cli {
/** Callback structure. */
const struct bt_mesh_dfu_cli_cb *cb;
/** Underlying BLOB Transfer Client. */
struct bt_mesh_blob_cli blob;
/* runtime state */
uint32_t op;
const struct bt_mesh_model *mod;
struct {
const struct bt_mesh_dfu_slot *slot;
const struct bt_mesh_blob_io *io;
struct bt_mesh_blob_xfer blob;
uint8_t state;
uint8_t flags;
} xfer;
struct {
uint8_t ttl;
uint8_t type;
uint8_t img_cnt;
uint16_t addr;
struct k_sem sem;
void *params;
bt_mesh_dfu_img_cb_t img_cb;
} req;
};
/** BLOB parameters for Firmware Update Client transfer: */
struct bt_mesh_dfu_cli_xfer_blob_params {
/* Logarithmic representation of the block size. */
uint8_t block_size_log;
/** Base chunk size. May be smaller for the last chunk. */
uint16_t chunk_size;
};
/** Firmware Update Client transfer parameters: */
struct bt_mesh_dfu_cli_xfer {
/** BLOB ID to use for this transfer, or 0 to set it randomly. */
uint64_t blob_id;
/** DFU image slot to transfer. */
const struct bt_mesh_dfu_slot *slot;
/** Transfer mode (Push (Push BLOB Transfer Mode) or Pull (Pull BLOB Transfer Mode)) */
enum bt_mesh_blob_xfer_mode mode;
/** BLOB parameters to be used for the transfer, or NULL to retrieve Target nodes'
* capabilities before sending a firmware.
*/
const struct bt_mesh_dfu_cli_xfer_blob_params *blob_params;
};
/** @brief Start distributing a DFU.
*
* Starts distribution of the firmware in the given slot to the list of DFU
* Target nodes in @c ctx. The transfer runs in the background, and its end is
* signalled through the @ref bt_mesh_dfu_cli_cb::ended callback.
*
* @note The BLOB Transfer Client transfer inputs @c targets list must point to a list of @ref
* bt_mesh_dfu_target nodes.
*
* @param cli Firmware Update Client model instance.
* @param inputs BLOB Transfer Client transfer inputs.
* @param io BLOB stream to read BLOB from.
* @param xfer Firmware Update Client transfer parameters.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_send(struct bt_mesh_dfu_cli *cli,
const struct bt_mesh_blob_cli_inputs *inputs,
const struct bt_mesh_blob_io *io,
const struct bt_mesh_dfu_cli_xfer *xfer);
/** @brief Suspend a DFU transfer.
*
* @param cli Firmware Update Client instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli);
/** @brief Resume the suspended transfer.
*
* @param cli Firmware Update Client instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli);
/** @brief Cancel a DFU transfer.
*
* Will cancel the ongoing DFU transfer, or the transfer on a specific Target
* node if @c ctx is valid.
*
* @param cli Firmware Update Client model instance.
* @param ctx Message context, or NULL to cancel the ongoing DFU transfer.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli,
struct bt_mesh_msg_ctx *ctx);
/** @brief Apply the completed DFU transfer.
*
* A transfer can only be applied after it has ended successfully. The Firmware
* Update Client's @c applied callback is called at the end of the apply procedure.
*
* @param cli Firmware Update Client model instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli);
/** @brief Confirm that the active transfer has been applied on the Target nodes.
*
* A transfer can only be confirmed after it has been applied. The Firmware Update
* Client's @c confirmed callback is called at the end of the confirm
* procedure.
*
* Target nodes that have reported the effect as @ref BT_MESH_DFU_EFFECT_UNPROV
* are expected to not respond to the query, and will fail if they do.
*
* @param cli Firmware Update Client model instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli);
/** @brief Get progress as a percentage of completion.
*
* @param cli Firmware Update Client model instance.
*
* @return The progress of the current transfer in percent, or 0 if no
* transfer is active.
*/
uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli);
/** @brief Check whether a DFU transfer is in progress.
*
* @param cli Firmware Update Client model instance.
*
* @return true if the BLOB Transfer Client is currently participating in a transfer,
* false otherwise.
*/
bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli);
/** @brief Perform a DFU image list request.
*
* Requests the full list of DFU images on a Target node, and iterates through
* them, calling the @c cb for every image.
*
* The DFU image list request can be used to determine which image index the
* Target node holds its different firmwares in.
*
* Waits for a response until the procedure timeout expires.
*
* @param cli Firmware Update Client model instance.
* @param ctx Message context.
* @param cb Callback to call for each image index.
* @param cb_data Callback data to pass to @c cb.
* @param max_count Max number of images to return.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_imgs_get(struct bt_mesh_dfu_cli *cli,
struct bt_mesh_msg_ctx *ctx,
bt_mesh_dfu_img_cb_t cb, void *cb_data,
uint8_t max_count);
/** @brief Perform a metadata check for the given DFU image slot.
*
* The metadata check procedure allows the Firmware Update Client to check if a Target
* node will accept a transfer of this DFU image slot, and what the effect would be.
*
* Waits for a response until the procedure timeout expires.
*
* @param cli Firmware Update Client model instance.
* @param ctx Message context.
* @param img_idx Target node's image index to check.
* @param slot DFU image slot to check for.
* @param rsp Metadata status response buffer.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_metadata_check(struct bt_mesh_dfu_cli *cli,
struct bt_mesh_msg_ctx *ctx, uint8_t img_idx,
const struct bt_mesh_dfu_slot *slot,
struct bt_mesh_dfu_metadata_status *rsp);
/** @brief Get the status of a Target node.
*
* @param cli Firmware Update Client model instance.
* @param ctx Message context.
* @param rsp Response data buffer.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_cli_status_get(struct bt_mesh_dfu_cli *cli,
struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_dfu_target_status *rsp);
/** @brief Get the current procedure timeout value.
*
* @return The configured procedure timeout.
*/
int32_t bt_mesh_dfu_cli_timeout_get(void);
/** @brief Set the procedure timeout value.
*
* @param timeout The new procedure timeout.
*/
void bt_mesh_dfu_cli_timeout_set(int32_t timeout);
/** @cond INTERNAL_HIDDEN */
extern const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers;
extern const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb;
extern const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[];
/** @endcond */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_CLI_H__ */
/** @} */
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @defgroup bt_mesh_dfu_metadata Bluetooth Mesh Device Firmware Update (DFU) metadata
* @ingroup bt_mesh_dfu
* @{
* @brief Common types and functions for the Bluetooth Mesh DFU metadata.
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__
#include <stdint.h>
#include <sys/types.h>
#include <zephyr/kernel.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Firmware version. */
struct bt_mesh_dfu_metadata_fw_ver {
/** Firmware major version. */
uint8_t major;
/** Firmware minor version. */
uint8_t minor;
/** Firmware revision. */
uint16_t revision;
/** Firmware build number. */
uint32_t build_num;
};
/** Firmware core type. */
enum bt_mesh_dfu_metadata_fw_core_type {
/** Application core. */
BT_MESH_DFU_FW_CORE_TYPE_APP = BIT(0),
/** Network core. */
BT_MESH_DFU_FW_CORE_TYPE_NETWORK = BIT(1),
/** Application-specific BLOB. */
BT_MESH_DFU_FW_CORE_TYPE_APP_SPECIFIC_BLOB = BIT(2),
};
/** Firmware metadata. */
struct bt_mesh_dfu_metadata {
/** New firmware version. */
struct bt_mesh_dfu_metadata_fw_ver fw_ver;
/** New firmware size. */
uint32_t fw_size;
/** New firmware core type. */
enum bt_mesh_dfu_metadata_fw_core_type fw_core_type;
/** Hash of incoming Composition Data. */
uint32_t comp_hash;
/** New number of node elements. */
uint16_t elems;
/** Application-specific data for new firmware. This field is optional. */
uint8_t *user_data;
/** Length of the application-specific field. */
uint32_t user_data_len;
};
/** @brief Decode a firmware metadata from a network buffer.
*
* @param buf Buffer containing a raw metadata to be decoded.
* @param metadata Pointer to a metadata structure to be filled.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf,
struct bt_mesh_dfu_metadata *metadata);
/** @brief Encode a firmware metadata into a network buffer.
*
* @param metadata Firmware metadata to be encoded.
* @param buf Buffer to store the encoded metadata.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata,
struct net_buf_simple *buf);
/** @brief Compute hash of the Composition Data state.
*
* The format of the Composition Data is defined in MshPRTv1.1: 4.2.2.1.
*
* @param buf Pointer to buffer holding Composition Data.
* @param key 128-bit key to be used in the hash computation.
* @param hash Pointer to a memory location to which the hash will be stored.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, uint8_t *key, uint32_t *hash);
/** @brief Compute hash of the Composition Data Page 0 of this device.
*
* @param key 128-bit key to be used in the hash computation.
* @param hash Pointer to a memory location to which the hash will be stored.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_METADATA_H__ */
/** @} */
@@ -0,0 +1,266 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @defgroup bt_mesh_dfu_srv Firmware Update Server model
* @ingroup bt_mesh_dfu
* @{
* @brief API for the Bluetooth Mesh Firmware Update Server model
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__
#include <zephyr/bluetooth/mesh/dfu.h>
#include <zephyr/bluetooth/mesh/blob_srv.h>
#include <zephyr/bluetooth/mesh/access.h>
#ifdef __cplusplus
extern "C" {
#endif
struct bt_mesh_dfu_srv;
/**
*
* @brief Initialization parameters for @ref bt_mesh_dfu_srv.
*
* @param _handlers DFU handler function structure.
* @param _imgs List of @ref bt_mesh_dfu_img managed by this Server.
* @param _img_count Number of DFU images managed by this Server.
*/
#define BT_MESH_DFU_SRV_INIT(_handlers, _imgs, _img_count) \
{ \
.blob = { .cb = &_bt_mesh_dfu_srv_blob_cb }, .cb = _handlers, \
.imgs = _imgs, .img_count = _img_count, \
}
/**
*
* @brief Firmware Update Server model entry.
*
* @param _srv Pointer to a @ref bt_mesh_dfu_srv instance.
*/
#define BT_MESH_MODEL_DFU_SRV(_srv) \
BT_MESH_MODEL_BLOB_SRV(&(_srv)->blob), \
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_SRV, _bt_mesh_dfu_srv_op, NULL, \
_srv, &_bt_mesh_dfu_srv_cb)
/** @brief Firmware Update Server event callbacks. */
struct bt_mesh_dfu_srv_cb {
/** @brief Transfer check callback.
*
* The transfer check can be used to validate the incoming transfer
* before it starts. The contents of the metadata is implementation
* specific, and should contain all the information the application
* needs to determine whether this image should be accepted, and what
* the effect of the transfer would be.
*
* If applying the image will have an effect on the provisioning state
* of the mesh stack, this can be communicated through the @c effect
* return parameter.
*
* The metadata check can be performed both as part of starting a new
* transfer and as a separate procedure.
*
* This handler is optional.
*
* @param srv Firmware Update Server instance.
* @param img DFU image the metadata check is performed on.
* @param metadata Image metadata.
* @param effect Return parameter for the image effect on the
* provisioning state of the mesh stack.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*check)(struct bt_mesh_dfu_srv *srv,
const struct bt_mesh_dfu_img *img,
struct net_buf_simple *metadata,
enum bt_mesh_dfu_effect *effect);
/** @brief Transfer start callback.
*
* Called when the Firmware Update Server is ready to start a new DFU transfer.
* The application must provide an initialized BLOB stream to be used
* during the DFU transfer.
*
* The following error codes are treated specially, and should be used
* to communicate these issues:
* - @c -ENOMEM: The device cannot fit this image.
* - @c -EBUSY: The application is temporarily unable to accept the
* transfer.
* - @c -EALREADY: The device has already received and verified this
* image, and there's no need to transfer it again. The Firmware Update model
* will skip the transfer phase, and mark the image as verified.
*
* This handler is mandatory.
*
* @param srv Firmware Update Server instance.
* @param img DFU image being updated.
* @param metadata Image metadata.
* @param io BLOB stream return parameter. Must be set to a
* valid BLOB stream by the callback.
*
* @return 0 on success, or (negative) error code otherwise. Return
* codes @c -ENOMEM, @c -EBUSY @c -EALREADY will be passed to
* the updater, other error codes are reported as internal
* errors.
*/
int (*start)(struct bt_mesh_dfu_srv *srv,
const struct bt_mesh_dfu_img *img,
struct net_buf_simple *metadata,
const struct bt_mesh_blob_io **io);
/** @brief Transfer end callback.
*
* This handler is optional.
*
* If the transfer is successful, the application should verify the
* firmware image, and call either @ref bt_mesh_dfu_srv_verified or
* @ref bt_mesh_dfu_srv_rejected depending on the outcome.
*
* If the transfer fails, the Firmware Update Server will be available for new
* transfers immediately after this function returns.
*
* @param srv Firmware Update Server instance.
* @param img DFU image that failed the update.
* @param success Whether the DFU transfer was successful.
*/
void (*end)(struct bt_mesh_dfu_srv *srv,
const struct bt_mesh_dfu_img *img, bool success);
/** @brief Transfer recovery callback.
*
* If the device reboots in the middle of a transfer, the Firmware Update Server
* calls this function when the Bluetooth Mesh subsystem is started.
*
* This callback is optional, but transfers will not be recovered after
* a reboot without it.
*
* @param srv Firmware Update Server instance.
* @param img DFU image being updated.
* @param io BLOB stream return parameter. Must be set to a valid BLOB
* stream by the callback.
*
* @return 0 on success, or (negative) error code to abandon the
* transfer.
*/
int (*recover)(struct bt_mesh_dfu_srv *srv,
const struct bt_mesh_dfu_img *img,
const struct bt_mesh_blob_io **io);
/** @brief Transfer apply callback.
*
* Called after a transfer has been validated, and the updater sends an
* apply message to the Target nodes.
*
* This handler is optional.
*
* @param srv Firmware Update Server instance.
* @param img DFU image that should be applied.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*apply)(struct bt_mesh_dfu_srv *srv,
const struct bt_mesh_dfu_img *img);
};
/** @brief Firmware Update Server instance.
*
* Should be initialized with @ref BT_MESH_DFU_SRV_INIT.
*/
struct bt_mesh_dfu_srv {
/** Underlying BLOB Transfer Server. */
struct bt_mesh_blob_srv blob;
/** Callback structure. */
const struct bt_mesh_dfu_srv_cb *cb;
/** List of updatable images. */
const struct bt_mesh_dfu_img *imgs;
/** Number of updatable images. */
size_t img_count;
/* Runtime state */
const struct bt_mesh_model *mod;
struct {
/* Effect of transfer, @see bt_mesh_dfu_effect. */
uint8_t effect;
/* Current phase, @see bt_mesh_dfu_phase. */
uint8_t phase;
uint8_t ttl;
uint8_t idx;
uint16_t timeout_base;
uint16_t meta;
} update;
};
/** @brief Accept the received DFU transfer.
*
* Should be called at the end of a successful DFU transfer.
*
* If the DFU transfer completes successfully, the application should verify
* the image validity (including any image authentication or integrity checks),
* and call this function if the image is ready to be applied.
*
* @param srv Firmware Update Server instance.
*/
void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv);
/** @brief Reject the received DFU transfer.
*
* Should be called at the end of a successful DFU transfer.
*
* If the DFU transfer completes successfully, the application should verify
* the image validity (including any image authentication or integrity checks),
* and call this function if one of the checks fail.
*
* @param srv Firmware Update Server instance.
*/
void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv);
/** @brief Cancel the ongoing DFU transfer.
*
* @param srv Firmware Update Server instance.
*/
void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv);
/** @brief Confirm that the received DFU transfer was applied.
*
* Should be called as a result of the @ref bt_mesh_dfu_srv_cb.apply callback.
*
* @param srv Firmware Update Server instance.
*/
void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv);
/** @brief Check if the Firmware Update Server is busy processing a transfer.
*
* @param srv Firmware Update Server instance.
*
* @return true if a DFU procedure is in progress, false otherwise.
*/
bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv);
/** @brief Get the progress of the current DFU procedure, in percent.
*
* @param srv Firmware Update Server instance.
*
* @return The current transfer progress in percent.
*/
uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv);
/** @cond INTERNAL_HIDDEN */
extern const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[];
extern const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb;
extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb;
/** @endcond */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFU_SRV_H__ */
/** @} */
@@ -0,0 +1,262 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__
#include <sys/types.h>
#include <zephyr/kernel.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup bt_mesh_blob Bluetooth Mesh BLOB model API
* @ingroup bt_mesh
* @{
*/
#ifndef CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX
#define CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX 0
#endif
/** BLOB transfer mode. */
enum bt_mesh_blob_xfer_mode {
/** No valid transfer mode. */
BT_MESH_BLOB_XFER_MODE_NONE,
/** Push mode (Push BLOB Transfer Mode). */
BT_MESH_BLOB_XFER_MODE_PUSH,
/** Pull mode (Pull BLOB Transfer Mode). */
BT_MESH_BLOB_XFER_MODE_PULL,
/** Both modes are valid. */
BT_MESH_BLOB_XFER_MODE_ALL,
};
/** Transfer phase. */
enum bt_mesh_blob_xfer_phase {
/** The BLOB Transfer Server is awaiting configuration. */
BT_MESH_BLOB_XFER_PHASE_INACTIVE,
/** The BLOB Transfer Server is ready to receive a BLOB transfer. */
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START,
/** The BLOB Transfer Server is waiting for the next block of data. */
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK,
/** The BLOB Transfer Server is waiting for the next chunk of data. */
BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK,
/** The BLOB was transferred successfully. */
BT_MESH_BLOB_XFER_PHASE_COMPLETE,
/** The BLOB transfer is paused. */
BT_MESH_BLOB_XFER_PHASE_SUSPENDED,
};
/** BLOB model status codes. */
enum bt_mesh_blob_status {
/** The message was processed successfully. */
BT_MESH_BLOB_SUCCESS,
/** The Block Number field value is not within the range of blocks being
* transferred.
*/
BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM,
/** The block size is smaller than the size indicated by the Min Block
* Size Log state or is larger than the size indicated by the Max Block
* Size Log state.
*/
BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE,
/** The chunk size exceeds the size indicated by the Max Chunk Size
* state, or the number of chunks exceeds the number specified by the
* Max Total Chunks state.
*/
BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE,
/** The operation cannot be performed while the server is in the current
* phase.
*/
BT_MESH_BLOB_ERR_WRONG_PHASE,
/** A parameter value in the message cannot be accepted. */
BT_MESH_BLOB_ERR_INVALID_PARAM,
/** The message contains a BLOB ID value that is not expected. */
BT_MESH_BLOB_ERR_WRONG_BLOB_ID,
/** There is not enough space available in memory to receive the BLOB.
*/
BT_MESH_BLOB_ERR_BLOB_TOO_LARGE,
/** The transfer mode is not supported by the BLOB Transfer Server
* model.
*/
BT_MESH_BLOB_ERR_UNSUPPORTED_MODE,
/** An internal error occurred on the node. */
BT_MESH_BLOB_ERR_INTERNAL,
/** The requested information cannot be provided while the server is in
* the current phase.
*/
BT_MESH_BLOB_ERR_INFO_UNAVAILABLE,
};
/** BLOB transfer data block. */
struct bt_mesh_blob_block {
/** Block size in bytes */
size_t size;
/** Offset in bytes from the start of the BLOB. */
off_t offset;
/** Block number */
uint16_t number;
/** Number of chunks in block. */
uint16_t chunk_count;
/** Bitmap of missing chunks. */
uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX,
8)];
};
/** BLOB data chunk. */
struct bt_mesh_blob_chunk {
/** Offset of the chunk data from the start of the block. */
off_t offset;
/** Chunk data size. */
size_t size;
/** Chunk data. */
uint8_t *data;
};
/** BLOB transfer. */
struct bt_mesh_blob_xfer {
/** BLOB ID. */
uint64_t id;
/** Total BLOB size in bytes. */
size_t size;
/** BLOB transfer mode. */
enum bt_mesh_blob_xfer_mode mode;
/* Logarithmic representation of the block size. */
uint8_t block_size_log;
/** Base chunk size. May be smaller for the last chunk. */
uint16_t chunk_size;
};
/** BLOB stream interaction mode. */
enum bt_mesh_blob_io_mode {
/** Read data from the stream. */
BT_MESH_BLOB_READ,
/** Write data to the stream. */
BT_MESH_BLOB_WRITE,
};
/** BLOB stream. */
struct bt_mesh_blob_io {
/** @brief Open callback.
*
* Called when the reader is opened for reading.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
* @param mode Direction of the stream (read/write).
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*open)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
enum bt_mesh_blob_io_mode mode);
/** @brief Close callback.
*
* Called when the reader is closed.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
*/
void (*close)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer);
/** @brief Block start callback.
*
* Called when a new block is opened for sending. Each block is only
* sent once, and are always sent in increasing order. The data chunks
* inside a single block may be requested out of order and multiple
* times.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
* @param block Block that was started.
*/
int (*block_start)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block);
/** @brief Block end callback.
*
* Called when the current block has been transmitted in full.
* No data from this block will be requested again, and the application
* data associated with this block may be discarded.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
* @param block Block that finished sending.
*/
void (*block_end)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block);
/** @brief Chunk data write callback.
*
* Used by the BLOB Transfer Server on incoming data.
*
* Each block is divided into chunks of data. This callback is called
* when a new chunk of data is received. Chunks may be received in
* any order within their block.
*
* If the callback returns successfully, this chunk will be marked as
* received, and will not be received again unless the block is
* restarted due to a transfer suspension. If the callback returns a
* non-zero value, the chunk remains unreceived, and the BLOB Transfer
* Client will attempt to resend it later.
*
* Note that the Client will only perform a limited number of attempts
* at delivering a chunk before dropping a Target node from the transfer.
* The number of retries performed by the Client is implementation
* specific.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
* @param block Block the chunk is part of.
* @param chunk Received chunk.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*wr)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block,
const struct bt_mesh_blob_chunk *chunk);
/** @brief Chunk data read callback.
*
* Used by the BLOB Transfer Client to fetch outgoing data.
*
* The Client calls the chunk data request callback to populate a chunk
* message going out to the Target nodes. The data request callback
* may be called out of order and multiple times for each offset, and
* cannot be used as an indication of progress.
*
* Returning a non-zero status code on the chunk data request callback
* results in termination of the transfer.
*
* @param io BLOB stream.
* @param xfer BLOB transfer.
* @param block Block the chunk is part of.
* @param chunk Chunk to get the data of. The buffer pointer to by the
* @c data member should be filled by the callback.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int (*rd)(const struct bt_mesh_blob_io *io,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_block *block,
const struct bt_mesh_blob_chunk *chunk);
};
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_H__ */
@@ -0,0 +1,454 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_
#include <sys/types.h>
#include <zephyr/bluetooth/mesh/access.h>
#include <zephyr/bluetooth/mesh/blob.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup bt_mesh_blob_cli Bluetooth Mesh BLOB Transfer Client model API
* @ingroup bt_mesh
* @{
*/
struct bt_mesh_blob_cli;
/**
*
* @brief BLOB Transfer Client model Composition Data entry.
*
* @param _cli Pointer to a @ref bt_mesh_blob_cli instance.
*/
#define BT_MESH_MODEL_BLOB_CLI(_cli) \
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_CLI, _bt_mesh_blob_cli_op, \
NULL, _cli, &_bt_mesh_blob_cli_cb)
/** Target node's Pull mode (Pull BLOB Transfer Mode) context used
* while sending chunks to the Target node.
*/
struct bt_mesh_blob_target_pull {
/** Timestamp when the Block Report Timeout Timer expires for this Target node. */
int64_t block_report_timestamp;
/** Missing chunks reported by this Target node. */
uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, 8)];
};
/** BLOB Transfer Client Target node. */
struct bt_mesh_blob_target {
/** Linked list node */
sys_snode_t n;
/** Target node address. */
uint16_t addr;
/** Target node's Pull mode context.
* Needs to be initialized when sending a BLOB in Pull mode.
*/
struct bt_mesh_blob_target_pull *pull;
/** BLOB transfer status, see @ref bt_mesh_blob_status. */
uint8_t status;
uint8_t procedure_complete:1, /* Procedure has been completed. */
acked:1, /* Message has been acknowledged. Not used when sending. */
timedout:1, /* Target node didn't respond after specified timeout. */
skip:1; /* Skip Target node from broadcast. */
};
/** BLOB transfer information.
*
* If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_INACTIVE,
* the fields below @c phase are not initialized.
* If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START,
* the fields below @c id are not initialized.
*/
struct bt_mesh_blob_xfer_info {
/** BLOB transfer status. */
enum bt_mesh_blob_status status;
/** BLOB transfer mode. */
enum bt_mesh_blob_xfer_mode mode;
/** BLOB transfer phase. */
enum bt_mesh_blob_xfer_phase phase;
/** BLOB ID. */
uint64_t id;
/** BLOB size in octets. */
uint32_t size;
/** Logarithmic representation of the block size. */
uint8_t block_size_log;
/** MTU size in octets. */
uint16_t mtu_size;
/** Bit field indicating blocks that were not received. */
const uint8_t *missing_blocks;
};
/** BLOB Transfer Client transfer inputs. */
struct bt_mesh_blob_cli_inputs {
/** Linked list of Target nodes. Each node should point to @ref
* bt_mesh_blob_target::n.
*/
sys_slist_t targets;
/** AppKey index to send with. */
uint16_t app_idx;
/** Group address destination for the BLOB transfer, or @ref
* BT_MESH_ADDR_UNASSIGNED to send every message to each Target
* node individually.
*/
uint16_t group;
/** Time to live value of BLOB transfer messages. */
uint8_t ttl;
/** Additional response time for the Target nodes, in 10-second increments.
*
* The extra time can be used to give the Target nodes more time to respond
* to messages from the Client. The actual timeout will be calculated
* according to the following formula:
*
* @verbatim
* timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms * TTL)
* @endverbatim
*
* If a Target node fails to respond to a message from the Client within the
* configured transfer timeout, the Target node is dropped.
*/
uint16_t timeout_base;
};
/** Transfer capabilities of a Target node. */
struct bt_mesh_blob_cli_caps {
/** Max BLOB size. */
size_t max_size;
/** Logarithmic representation of the minimum block size. */
uint8_t min_block_size_log;
/** Logarithmic representation of the maximum block size. */
uint8_t max_block_size_log;
/** Max number of chunks per block. */
uint16_t max_chunks;
/** Max chunk size. */
uint16_t max_chunk_size;
/** Max MTU size. */
uint16_t mtu_size;
/** Supported transfer modes. */
enum bt_mesh_blob_xfer_mode modes;
};
/** BLOB Transfer Client state. */
enum bt_mesh_blob_cli_state {
/** No transfer is active. */
BT_MESH_BLOB_CLI_STATE_NONE,
/** Retrieving transfer capabilities. */
BT_MESH_BLOB_CLI_STATE_CAPS_GET,
/** Sending transfer start. */
BT_MESH_BLOB_CLI_STATE_START,
/** Sending block start. */
BT_MESH_BLOB_CLI_STATE_BLOCK_START,
/** Sending block chunks. */
BT_MESH_BLOB_CLI_STATE_BLOCK_SEND,
/** Checking block status. */
BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK,
/** Checking transfer status. */
BT_MESH_BLOB_CLI_STATE_XFER_CHECK,
/** Cancelling transfer. */
BT_MESH_BLOB_CLI_STATE_CANCEL,
/** Transfer is suspended. */
BT_MESH_BLOB_CLI_STATE_SUSPENDED,
/** Checking transfer progress. */
BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET,
};
/** Event handler callbacks for the BLOB Transfer Client model.
*
* All handlers are optional.
*/
struct bt_mesh_blob_cli_cb {
/** @brief Capabilities retrieval completion callback.
*
* Called when the capabilities retrieval procedure completes, indicating that
* a common set of acceptable transfer parameters have been established
* for the given list of Target nodes. All compatible Target nodes have
* status code @ref BT_MESH_BLOB_SUCCESS.
*
* @param cli BLOB Transfer Client instance.
* @param caps Safe transfer capabilities if the transfer capabilities
* of at least one Target node has satisfied the Client, or NULL otherwise.
*/
void (*caps)(struct bt_mesh_blob_cli *cli,
const struct bt_mesh_blob_cli_caps *caps);
/** @brief Target node loss callback.
*
* Called whenever a Target node has been lost due to some error in the
* transfer. Losing a Target node is not considered a fatal error for
* the Client until all Target nodes have been lost.
*
* @param cli BLOB Transfer Client instance.
* @param target Target node that was lost.
* @param reason Reason for the Target node loss.
*/
void (*lost_target)(struct bt_mesh_blob_cli *cli,
struct bt_mesh_blob_target *target,
enum bt_mesh_blob_status reason);
/** @brief Transfer is suspended.
*
* Called when the transfer is suspended due to response timeout from all Target nodes.
*
* @param cli BLOB Transfer Client instance.
*/
void (*suspended)(struct bt_mesh_blob_cli *cli);
/** @brief Transfer end callback.
*
* Called when the transfer ends.
*
* @param cli BLOB Transfer Client instance.
* @param xfer Completed transfer.
* @param success Status of the transfer.
* Is @c true if at least one Target
* node received the whole transfer.
*/
void (*end)(struct bt_mesh_blob_cli *cli,
const struct bt_mesh_blob_xfer *xfer, bool success);
/** @brief Transfer progress callback
*
* The content of @c info is invalidated upon exit from the callback.
* Therefore it needs to be copied if it is planned to be used later.
*
* @param cli BLOB Transfer Client instance.
* @param target Target node that responded to the request.
* @param info BLOB transfer information.
*/
void (*xfer_progress)(struct bt_mesh_blob_cli *cli,
struct bt_mesh_blob_target *target,
const struct bt_mesh_blob_xfer_info *info);
/** @brief End of Get Transfer Progress procedure.
*
* Called when all Target nodes have responded or the procedure timed-out.
*
* @param cli BLOB Transfer Client instance.
*/
void (*xfer_progress_complete)(struct bt_mesh_blob_cli *cli);
};
/** @cond INTERNAL_HIDDEN */
struct blob_cli_broadcast_ctx {
/** Called for every Target node in unicast mode, or once in case of multicast mode. */
void (*send)(struct bt_mesh_blob_cli *cli, uint16_t dst);
/** Called after every @ref blob_cli_broadcast_ctx::send callback. */
void (*send_complete)(struct bt_mesh_blob_cli *cli, uint16_t dst);
/** If @ref blob_cli_broadcast_ctx::acked is true, called after all Target nodes
* have confirmed reception by @ref blob_cli_broadcast_rsp. Otherwise, called
* after transmission has been completed.
*/
void (*next)(struct bt_mesh_blob_cli *cli);
/** If true, every transmission needs to be confirmed by @ref blob_cli_broadcast_rsp before
* @ref blob_cli_broadcast_ctx::next is called.
*/
bool acked;
/** If true, the message is always sent in a unicast way. */
bool force_unicast;
/** If true, non-responsive Target nodes won't be dropped after transfer has timed out. */
bool optional;
/** Set to true by the BLOB Transfer Client between blob_cli_broadcast
* and broadcast_complete calls.
*/
bool is_inited;
/* Defines a time in ms by which the broadcast API postpones sending the message to a next
* target or completing the broadcast.
*/
uint32_t post_send_delay_ms;
};
/** INTERNAL_HIDDEN @endcond */
/** BLOB Transfer Client model instance. */
struct bt_mesh_blob_cli {
/** Event handler callbacks */
const struct bt_mesh_blob_cli_cb *cb;
/* Runtime state */
const struct bt_mesh_model *mod;
struct {
struct bt_mesh_blob_target *target;
struct blob_cli_broadcast_ctx ctx;
struct k_work_delayable retry;
/* Represents Client Timeout timer in a timestamp. Used in Pull mode only. */
int64_t cli_timestamp;
struct k_work_delayable complete;
uint16_t pending;
uint8_t retries;
uint8_t sending : 1,
cancelled : 1;
} tx;
const struct bt_mesh_blob_io *io;
const struct bt_mesh_blob_cli_inputs *inputs;
const struct bt_mesh_blob_xfer *xfer;
uint32_t chunk_interval_ms;
uint16_t block_count;
uint16_t chunk_idx;
uint16_t mtu_size;
enum bt_mesh_blob_cli_state state;
struct bt_mesh_blob_block block;
struct bt_mesh_blob_cli_caps caps;
};
/** @brief Retrieve transfer capabilities for a list of Target nodes.
*
* Queries the availability and capabilities of all Target nodes, producing a
* cumulative set of transfer capabilities for the Target nodes, and returning
* it through the @ref bt_mesh_blob_cli_cb::caps callback.
*
* Retrieving the capabilities may take several seconds, depending on the
* number of Target nodes and mesh network performance. The end of the procedure
* is indicated through the @ref bt_mesh_blob_cli_cb::caps callback.
*
* This procedure is not required, but strongly recommended as a
* preparation for a transfer to maximize performance and the chances of
* success.
*
* @param cli BLOB Transfer Client instance.
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli,
const struct bt_mesh_blob_cli_inputs *inputs);
/** @brief Perform a BLOB transfer.
*
* Starts sending the transfer to the Target nodes. Only Target nodes with a
* @c status of @ref BT_MESH_BLOB_SUCCESS will be considered.
*
* The transfer will keep going either until all Target nodes have been dropped, or
* the full BLOB has been sent.
*
* The BLOB transfer may take several minutes, depending on the number of
* Target nodes, size of the BLOB and mesh network performance. The end of the
* transfer is indicated through the @ref bt_mesh_blob_cli_cb::end callback.
*
* A Client only supports one transfer at the time.
*
* @param cli BLOB Transfer Client instance.
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
* @param xfer Statically allocated transfer parameters.
* @param io BLOB stream to read the transfer from.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli,
const struct bt_mesh_blob_cli_inputs *inputs,
const struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_io *io);
/** @brief Suspend the active transfer.
*
* @param cli BLOB Transfer Client instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli);
/** @brief Resume the suspended transfer.
*
* @param cli BLOB Transfer Client instance.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli);
/** @brief Cancel an ongoing transfer.
*
* @param cli BLOB Transfer Client instance.
*/
void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli);
/** @brief Get the progress of BLOB transfer.
*
* This function can only be used if the BLOB Transfer Client is currently
* not performing a BLOB transfer.
* To get progress of the active BLOB transfer, use the
* @ref bt_mesh_blob_cli_xfer_progress_active_get function.
*
* @param cli BLOB Transfer Client instance.
* @param inputs Statically allocated BLOB Transfer Client transfer inputs.
*
* @return 0 on success, or (negative) error code otherwise.
*/
int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli,
const struct bt_mesh_blob_cli_inputs *inputs);
/** @brief Get the current progress of the active transfer in percent.
*
* @param cli BLOB Transfer Client instance.
*
* @return The current transfer progress, or 0 if no transfer is active.
*/
uint8_t bt_mesh_blob_cli_xfer_progress_active_get(struct bt_mesh_blob_cli *cli);
/** @brief Get the current state of the BLOB Transfer Client.
*
* @param cli BLOB Transfer Client instance.
*
* @return true if the BLOB Transfer Client is currently participating in a transfer or
* retrieving the capabilities and false otherwise.
*/
bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli);
/** @brief Set chunk sending interval in ms
*
* This function is optional, and can be used to define how fast chunks are sent in the BLOB Client
* Model.
* Without an added delay, for example a Bluetooth Mesh DFU can cause network blockage by
* constantly sending the next chunks, especially if the chunks are sent to group addresses or
* multiple unicast addresses.
*
* @note: Big intervals may cause timeouts. Increasing the @c timeout_base accordingly can
* circumvent this.
*
* @param cli BLOB Transfer Client instance.
* @param interval_ms the delay before each chunk is sent out in ms.
*/
void bt_mesh_blob_cli_set_chunk_interval_ms(struct bt_mesh_blob_cli *cli, uint32_t interval_ms);
/** @cond INTERNAL_HIDDEN */
extern const struct bt_mesh_model_op _bt_mesh_blob_cli_op[];
extern const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb;
/** @endcond */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_CLI_H_ */
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_
#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_
#include <zephyr/bluetooth/mesh/access.h>
#include <zephyr/bluetooth/mesh/blob.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup bt_mesh_blob_srv Bluetooth Mesh BLOB Transfer Server model API
* @ingroup bt_mesh
* @{
*/
struct bt_mesh_blob_srv;
/**
*
* @brief Max number of blocks in a single transfer.
*/
#if defined(CONFIG_BT_MESH_BLOB_SRV)
#define BT_MESH_BLOB_BLOCKS_MAX \
(DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_SIZE_MAX, \
CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN))
#else
#define BT_MESH_BLOB_BLOCKS_MAX 1
#endif
/**
*
* @brief BLOB Transfer Server model composition data entry.
*
* @param _srv Pointer to a @ref bt_mesh_blob_srv instance.
*/
#define BT_MESH_MODEL_BLOB_SRV(_srv) \
BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_SRV, _bt_mesh_blob_srv_op, \
NULL, _srv, &_bt_mesh_blob_srv_cb)
/** @brief BLOB Transfer Server model event handlers.
*
* All callbacks are optional.
*/
struct bt_mesh_blob_srv_cb {
/** @brief Transfer start callback.
*
* Called when the transfer has started with the prepared BLOB ID.
*
* @param srv BLOB Transfer Server instance.
* @param ctx Message context for the incoming start message. The
* entire transfer will be sent from the same source
* address.
* @param xfer Transfer parameters.
*
* @return 0 on success, or (negative) error code to reject the
* transfer.
*/
int (*start)(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx,
struct bt_mesh_blob_xfer *xfer);
/** @brief Transfer end callback.
*
* Called when the transfer ends, either because it was cancelled, or
* because it finished successfully. A new transfer may be prepared.
*
* @note The transfer may end before it's started if the start
* parameters are invalid.
*
* @param srv BLOB Transfer Server instance.
* @param id BLOB ID of the cancelled transfer.
* @param success Whether the transfer was successful.
*/
void (*end)(struct bt_mesh_blob_srv *srv, uint64_t id, bool success);
/** @brief Transfer suspended callback.
*
* Called if the Server timed out while waiting for a transfer packet.
* A suspended transfer may resume later from the start of the current
* block. Any received chunks in the current block should be discarded,
* they will be received again if the transfer resumes.
*
* The transfer will call @c resumed again when resuming.
*
* @note The BLOB Transfer Server does not run a timer in the suspended state,
* and it's up to the application to determine whether the
* transfer should be permanently cancelled. Without interaction,
* the transfer will be suspended indefinitely, and the BLOB Transfer
* Server will not accept any new transfers.
*
* @param srv BLOB Transfer Server instance.
*/
void (*suspended)(struct bt_mesh_blob_srv *srv);
/** @brief Transfer resume callback.
*
* Called if the transfer is resumed after being suspended.
*
* @param srv BLOB Transfer Server instance.
*/
void (*resume)(struct bt_mesh_blob_srv *srv);
/** @brief Transfer recovery callback.
*
* Called when the Bluetooth Mesh subsystem is started if the device is rebooted
* in the middle of a transfer.
*
* Transfers will not be resumed after a reboot if this callback is not
* defined.
*
* @param srv BLOB Transfer Server instance.
* @param xfer Transfer to resume.
* @param io BLOB stream return parameter. Must be set to a valid
* BLOB stream by the callback.
*
* @return 0 on success, or (negative) error code to abandon the
* transfer.
*/
int (*recover)(struct bt_mesh_blob_srv *srv,
struct bt_mesh_blob_xfer *xfer,
const struct bt_mesh_blob_io **io);
};
/** @brief BLOB Transfer Server model instance. */
struct bt_mesh_blob_srv {
/** Event handler callbacks. */
const struct bt_mesh_blob_srv_cb *cb;
/* Runtime state: */
const struct bt_mesh_blob_io *io;
struct k_work_delayable rx_timeout;
struct bt_mesh_blob_block block;
const struct bt_mesh_model *mod;
enum bt_mesh_blob_xfer_phase phase;
struct bt_mesh_blob_srv_state {
struct bt_mesh_blob_xfer xfer;
uint16_t cli;
uint16_t app_idx;
uint16_t timeout_base;
uint16_t mtu_size;
uint8_t ttl;
/* Bitfield of pending blocks. */
ATOMIC_DEFINE(blocks, BT_MESH_BLOB_BLOCKS_MAX);
} state;
/* Pull mode (Pull BLOB Transfer Mode) behavior. */
struct {
uint16_t chunk_idx;
struct k_work_delayable report;
} pull;
};
/** @brief Prepare BLOB Transfer Server for an incoming transfer.
*
* Before a BLOB Transfer Server can receive a transfer, the transfer must be prepared
* through some application level mechanism. The BLOB Transfer Server will only accept
* incoming transfers with a matching BLOB ID.
*
* @param srv BLOB Transfer Server instance.
* @param id BLOB ID to accept.
* @param io BLOB stream to write the incoming BLOB to.
* @param ttl Time to live value to use in responses to the BLOB Transfer Client.
* @param timeout_base Extra time for the Client to respond in addition to the
* base 10 seconds, in 10-second increments.
*
* @return 0 on success, or (negative) error code on failure.
*/
int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, uint64_t id,
const struct bt_mesh_blob_io *io, uint8_t ttl,
uint16_t timeout_base);
/** @brief Cancel the current BLOB transfer.
*
* Tells the BLOB Transfer Client to drop this device from the list of Targets for the
* current transfer. Note that the client may continue sending the transfer to
* other Targets.
*
* @param srv BLOB Transfer Server instance.
*
* @return 0 on success, or (negative) error code on failure.
*/
int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv);
/** @brief Get the current state of the BLOB Transfer Server.
*
* @param srv BLOB Transfer Server instance.
*
* @return true if the BLOB Transfer Server is currently participating in a transfer,
* false otherwise.
*/
bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv);
/** @brief Get the current progress of the active transfer in percent.
*
* @param srv BLOB Transfer Server instance.
*
* @return The current transfer progress, or 0 if no transfer is active.
*/
uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv);
/** @cond INTERNAL_HIDDEN */
extern const struct bt_mesh_model_op _bt_mesh_blob_srv_op[];
extern const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb;
/** @endcond */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_BLOB_SRV_H_ */
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/sys/util.h>
#define BT_MESH_BLOB_OP_XFER_GET BT_MESH_MODEL_OP_2(0x83, 0x00)
#define BT_MESH_BLOB_OP_XFER_START BT_MESH_MODEL_OP_2(0x83, 0x01)
#define BT_MESH_BLOB_OP_XFER_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x02)
#define BT_MESH_BLOB_OP_XFER_STATUS BT_MESH_MODEL_OP_2(0x83, 0x03)
#define BT_MESH_BLOB_OP_BLOCK_GET BT_MESH_MODEL_OP_2(0x83, 0x05)
#define BT_MESH_BLOB_OP_BLOCK_START BT_MESH_MODEL_OP_2(0x83, 0x04)
#define BT_MESH_BLOB_OP_CHUNK BT_MESH_MODEL_OP_1(0x66)
#define BT_MESH_BLOB_OP_BLOCK_STATUS BT_MESH_MODEL_OP_1(0x67)
#define BT_MESH_BLOB_OP_BLOCK_REPORT BT_MESH_MODEL_OP_1(0x68)
#define BT_MESH_BLOB_OP_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x06)
#define BT_MESH_BLOB_OP_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x07)
#define BLOB_BLOCK_NOT_SET 0xffff
#define BLOB_CHUNK_SDU_OVERHEAD \
(BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK) + 2 + BT_MESH_MIC_SHORT)
#define BLOB_CHUNK_SIZE_MAX(sdu_max) ((sdu_max) - BLOB_CHUNK_SDU_OVERHEAD)
#define BLOB_CHUNK_SDU_LEN(chunk_size) (BLOB_CHUNK_SDU_OVERHEAD + (chunk_size))
#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \
CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX)
#define BLOB_RX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX)
#else
#define BLOB_RX_CHUNK_SIZE CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE
#endif
#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \
CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX)
#define BLOB_TX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX)
#else
#define BLOB_TX_CHUNK_SIZE CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE
#endif
/* Utility macros for calculating log2 of a number at compile time.
* Used to determine the log2 representation of the block size, which
* is configured as a raw number, but encoded as log2.
*
* The macros expand to a series of ternary expressions, effectively
* searching through power of twos until a match is found.
* According to MshMBTv1.0, the block size cannot be larger than 2^20,
* so we'll stop the search at 20.
*/
#define _BLOB_LOG_2_CEIL(l, x) ((x) <= (1U << l)) ? l :
#define _BLOB_LOG_2_FLOOR(l, x) ((x) < (1U << (l + 1))) ? l :
#define BLOB_BLOCK_SIZE_LOG_CEIL(x) (LISTIFY(20, _BLOB_LOG_2_CEIL, (), x) 20)
#define BLOB_BLOCK_SIZE_LOG_FLOOR(x) (LISTIFY(20, _BLOB_LOG_2_FLOOR, (), x) 20)
/* Log2 representation of the minimum block size */
#define BLOB_BLOCK_SIZE_LOG_MIN BLOB_BLOCK_SIZE_LOG_CEIL(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN)
/* Log2 representation of the maximum block size */
#define BLOB_BLOCK_SIZE_LOG_MAX BLOB_BLOCK_SIZE_LOG_FLOOR(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX)
#if defined(CONFIG_BT_MESH_BLOB_SRV)
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN ( \
MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \
CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + \
MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \
CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3))
#else
#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN sizeof(((struct bt_mesh_blob_srv *)0)->block.missing)
#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + sizeof(((struct bt_mesh_blob_srv *)0)->block.missing))
#endif
#define BLOB_XFER_STATUS_MSG_MAXLEN (17 + sizeof(((struct bt_mesh_blob_srv *)0)->state.blocks))
enum bt_mesh_blob_chunks_missing {
BT_MESH_BLOB_CHUNKS_MISSING_ALL,
BT_MESH_BLOB_CHUNKS_MISSING_NONE,
BT_MESH_BLOB_CHUNKS_MISSING_SOME,
BT_MESH_BLOB_CHUNKS_MISSING_ENCODED,
};
static inline size_t blob_block_size(size_t xfer_size, uint8_t block_size_log,
uint32_t idx)
{
if (((idx + 1U) << block_size_log) <= xfer_size) {
return (1U << block_size_log);
}
return xfer_size & BIT_MASK(block_size_log);
}
static inline void blob_chunk_missing_set(uint8_t *missing_chunks,
int idx, bool missing)
{
WRITE_BIT(missing_chunks[idx / 8], idx % 8, missing);
}
static inline bool
blob_chunk_missing_get(const uint8_t *missing_chunks, int idx)
{
return !!(missing_chunks[idx / 8] & BIT(idx % 8));
}
static inline void blob_chunk_missing_set_all(struct bt_mesh_blob_block *block)
{
size_t bytes = block->chunk_count / 8;
memset(block->missing, 0xff, bytes);
if (block->chunk_count % 8) {
block->missing[bytes] = BIT_MASK(block->chunk_count % 8);
}
}
static inline void blob_chunk_missing_set_none(struct bt_mesh_blob_block *block)
{
memset(block->missing, 0, sizeof(block->missing));
}
/** @brief Perform a message broadcast to all BLOB Transfer Client Target nodes.
*
* Will send to a group or each Target node individually, repeating until
* all Target nodes have responded or the retry time has run out.
*
* @param cli BLOB Transfer Client instance
* @param ctx Broadcast context
*/
void blob_cli_broadcast(struct bt_mesh_blob_cli *cli,
const struct blob_cli_broadcast_ctx *ctx);
/** @brief Register that a Target node responded to a broadcast.
*
* @param cli BLOB Transfer Client instance
* @param target Target node that responded.
*/
void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli,
struct bt_mesh_blob_target *target);
/** @brief Notify the BLOB Transfer Client that the requested transmission is complete.
*
* Should be called once for each call to the @ref blob_cli_broadcast_ctx.send
* callback.
*
* @param cli BLOB Transfer Client instance.
*/
void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli);
/** @brief Aborts any ongoing BLOB Transfer Client operations.
*
* @param cli BLOB Transfer Client instance.
*/
void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff