1. Add an API to set Provisioner static oob value 2. Add an API to deinit BLE Mesh stack 3. Add an API to set Provisioner unicast address 4. Add an API to provision devices with fixed address 5. Add an API to store node composition data 6. Add an API to get node with device uuid 7. Add an API to get node with unicast address 8. Add an API to delete node with device uuid 9. Add an API to delete node with unicast address 10. Add an API for Provisioner to update local AppKey 11. Add an API for Provisioner to update local NetKey 12. Support Provisioner persistent functionality 13. Fix Provisioner entering IV Update procedure 14. Fix an issue which may cause client failing to send msg 15. Use bt_mesh.flags to indicate device role 16. Remove several useless macros 17. Callback RSSI of received mesh provisioning packets 18. Modify the Provisioner disable function 19. Change some log level from debug to info 20. Add parameters to Provisioner bind AppKey completion event 21. Fix node ignoring relay messages issue 22. Support using a specific partition for BLE Mesh 23. Fix compile warning when proxy related macros are disabled 24. Clean up BLE Mesh stack included header files 25. NULL can be input if client message needs no parameters 26. Fix compile warning when BT log is disabled 27. Initilize BLE Mesh stack local variables 28. Support using PSRAM for BLE Mesh mutex, queue and task 29. Add a menuconfig option to enable using memory from PSRAM 30. Clean up sdkconfig.defaults of BLE Mesh examples
1104 lines
29 KiB
C
1104 lines
29 KiB
C
/* Bluetooth Mesh */
|
|
|
|
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
|
|
#include "crypto.h"
|
|
#include "adv.h"
|
|
#include "mesh.h"
|
|
#include "transport.h"
|
|
#include "access.h"
|
|
#include "beacon.h"
|
|
#include "lpn.h"
|
|
#include "foundation.h"
|
|
#include "mesh_main.h"
|
|
#include "cfg_srv.h"
|
|
|
|
#ifdef CONFIG_BLE_MESH_LOW_POWER
|
|
|
|
#if defined(CONFIG_BLE_MESH_LPN_AUTO)
|
|
#define LPN_AUTO_TIMEOUT K_SECONDS(CONFIG_BLE_MESH_LPN_AUTO_TIMEOUT)
|
|
#else
|
|
#define LPN_AUTO_TIMEOUT 0
|
|
#endif
|
|
|
|
#define LPN_RECV_DELAY CONFIG_BLE_MESH_LPN_RECV_DELAY
|
|
#define SCAN_LATENCY MIN(CONFIG_BLE_MESH_LPN_SCAN_LATENCY, \
|
|
LPN_RECV_DELAY)
|
|
|
|
#define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(CONFIG_BLE_MESH_LPN_RETRY_TIMEOUT)
|
|
|
|
#define FRIEND_REQ_WAIT K_MSEC(100)
|
|
#define FRIEND_REQ_SCAN K_SECONDS(1)
|
|
#define FRIEND_REQ_TIMEOUT (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN)
|
|
|
|
#define POLL_RETRY_TIMEOUT K_MSEC(100)
|
|
|
|
#define REQ_RETRY_DURATION(lpn) (4 * (LPN_RECV_DELAY + (lpn)->adv_duration + \
|
|
(lpn)->recv_win + POLL_RETRY_TIMEOUT))
|
|
|
|
#define POLL_TIMEOUT_INIT (CONFIG_BLE_MESH_LPN_INIT_POLL_TIMEOUT * 100)
|
|
#define POLL_TIMEOUT_MAX(lpn) ((CONFIG_BLE_MESH_LPN_POLL_TIMEOUT * 100) - \
|
|
REQ_RETRY_DURATION(lpn))
|
|
|
|
/**
|
|
* 1. Should use 20 attempts for BQB test case MESH/NODE/FRND/LPM/BI-02-C.
|
|
* 2. We should use more specific value for each PollTimeout range.
|
|
*/
|
|
#define REQ_ATTEMPTS(lpn) (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 6)
|
|
|
|
#define CLEAR_ATTEMPTS 2
|
|
|
|
#define LPN_CRITERIA ((CONFIG_BLE_MESH_LPN_MIN_QUEUE_SIZE) | \
|
|
(CONFIG_BLE_MESH_LPN_RSSI_FACTOR << 3) | \
|
|
(CONFIG_BLE_MESH_LPN_RECV_WIN_FACTOR << 5))
|
|
|
|
#define POLL_TO(to) { (u8_t)((to) >> 16), (u8_t)((to) >> 8), (u8_t)(to) }
|
|
#define LPN_POLL_TO POLL_TO(CONFIG_BLE_MESH_LPN_POLL_TIMEOUT)
|
|
|
|
/* 2 transmissions, 20ms interval */
|
|
#define POLL_XMIT BLE_MESH_TRANSMIT(1, 20)
|
|
|
|
#define FIRST_POLL_ATTEMPTS 6
|
|
|
|
static void (*lpn_cb)(u16_t friend_addr, bool established);
|
|
|
|
#if !CONFIG_BLE_MESH_NO_LOG
|
|
static const char *state2str(int state)
|
|
{
|
|
switch (state) {
|
|
case BLE_MESH_LPN_DISABLED:
|
|
return "disabled";
|
|
case BLE_MESH_LPN_CLEAR:
|
|
return "clear";
|
|
case BLE_MESH_LPN_TIMER:
|
|
return "timer";
|
|
case BLE_MESH_LPN_ENABLED:
|
|
return "enabled";
|
|
case BLE_MESH_LPN_REQ_WAIT:
|
|
return "req wait";
|
|
case BLE_MESH_LPN_WAIT_OFFER:
|
|
return "wait offer";
|
|
case BLE_MESH_LPN_ESTABLISHED:
|
|
return "established";
|
|
case BLE_MESH_LPN_RECV_DELAY:
|
|
return "recv delay";
|
|
case BLE_MESH_LPN_WAIT_UPDATE:
|
|
return "wait update";
|
|
case BLE_MESH_LPN_OFFER_RECV:
|
|
return "offer recv";
|
|
default:
|
|
return "(unknown)";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static inline void lpn_set_state(int state)
|
|
{
|
|
BT_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state));
|
|
bt_mesh.lpn.state = state;
|
|
}
|
|
|
|
static inline void group_zero(bt_mesh_atomic_t *target)
|
|
{
|
|
#if CONFIG_BLE_MESH_LPN_GROUPS > 32
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
|
|
bt_mesh_atomic_set(&target[i], 0);
|
|
}
|
|
#else
|
|
bt_mesh_atomic_set(target, 0);
|
|
#endif
|
|
}
|
|
|
|
static inline void group_set(bt_mesh_atomic_t *target, bt_mesh_atomic_t *source)
|
|
{
|
|
#if CONFIG_BLE_MESH_LPN_GROUPS > 32
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
|
|
(void)bt_mesh_atomic_or(&target[i], bt_mesh_atomic_get(&source[i]));
|
|
}
|
|
#else
|
|
(void)bt_mesh_atomic_or(target, bt_mesh_atomic_get(source));
|
|
#endif
|
|
}
|
|
|
|
static inline void group_clear(bt_mesh_atomic_t *target, bt_mesh_atomic_t *source)
|
|
{
|
|
#if CONFIG_BLE_MESH_LPN_GROUPS > 32
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
|
|
(void)bt_mesh_atomic_and(&target[i], ~bt_mesh_atomic_get(&source[i]));
|
|
}
|
|
#else
|
|
(void)bt_mesh_atomic_and(target, ~bt_mesh_atomic_get(source));
|
|
#endif
|
|
}
|
|
|
|
static void clear_friendship(bool force, bool disable);
|
|
|
|
static bool scan_after_clear;
|
|
|
|
static void friend_clear_sent(int err, void *user_data)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
/* We're switching away from Low Power behavior, so permanently
|
|
* enable scanning.
|
|
*/
|
|
if (scan_after_clear == false) {
|
|
bt_mesh_scan_enable();
|
|
scan_after_clear = true;
|
|
}
|
|
|
|
lpn->req_attempts++;
|
|
|
|
if (err) {
|
|
BT_ERR("%s, Sending Friend Request failed (err %d)", __func__, err);
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
clear_friendship(false, lpn->disable);
|
|
return;
|
|
}
|
|
|
|
lpn_set_state(BLE_MESH_LPN_CLEAR);
|
|
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_TIMEOUT);
|
|
}
|
|
|
|
static const struct bt_mesh_send_cb clear_sent_cb = {
|
|
.end = friend_clear_sent,
|
|
};
|
|
|
|
static int send_friend_clear(void)
|
|
{
|
|
struct bt_mesh_msg_ctx ctx = {
|
|
.net_idx = bt_mesh.sub[0].net_idx,
|
|
.app_idx = BLE_MESH_KEY_UNUSED,
|
|
.addr = bt_mesh.lpn.frnd,
|
|
.send_ttl = 0,
|
|
};
|
|
struct bt_mesh_net_tx tx = {
|
|
.sub = &bt_mesh.sub[0],
|
|
.ctx = &ctx,
|
|
.src = bt_mesh_primary_addr(),
|
|
.xmit = bt_mesh_net_transmit_get(),
|
|
};
|
|
struct bt_mesh_ctl_friend_clear req = {
|
|
.lpn_addr = sys_cpu_to_be16(tx.src),
|
|
.lpn_counter = sys_cpu_to_be16(bt_mesh.lpn.counter),
|
|
};
|
|
|
|
BT_DBG("%s", __func__);
|
|
|
|
return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req,
|
|
sizeof(req), NULL, &clear_sent_cb, NULL);
|
|
}
|
|
|
|
static void clear_friendship(bool force, bool disable)
|
|
{
|
|
struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get();
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
BT_DBG("force %u disable %u", force, disable);
|
|
|
|
if (!force && lpn->established && !lpn->clear_success &&
|
|
lpn->req_attempts < CLEAR_ATTEMPTS) {
|
|
send_friend_clear();
|
|
lpn->disable = disable;
|
|
return;
|
|
}
|
|
|
|
bt_mesh_rx_reset();
|
|
|
|
k_delayed_work_cancel(&lpn->timer);
|
|
|
|
friend_cred_del(bt_mesh.sub[0].net_idx, lpn->frnd);
|
|
|
|
if (lpn->clear_success) {
|
|
lpn->old_friend = BLE_MESH_ADDR_UNASSIGNED;
|
|
} else {
|
|
lpn->old_friend = lpn->frnd;
|
|
}
|
|
|
|
if (lpn_cb && lpn->frnd != BLE_MESH_ADDR_UNASSIGNED) {
|
|
lpn_cb(lpn->frnd, false);
|
|
}
|
|
|
|
lpn->frnd = BLE_MESH_ADDR_UNASSIGNED;
|
|
lpn->fsn = 0U;
|
|
lpn->req_attempts = 0U;
|
|
lpn->recv_win = 0U;
|
|
lpn->queue_size = 0U;
|
|
lpn->disable = 0U;
|
|
lpn->sent_req = 0U;
|
|
lpn->established = 0U;
|
|
lpn->clear_success = 0U;
|
|
|
|
group_zero(lpn->added);
|
|
group_zero(lpn->pending);
|
|
group_zero(lpn->to_remove);
|
|
|
|
/* Set this to 1 to force group subscription when the next
|
|
* Friendship is created, in case lpn->groups doesn't get
|
|
* modified meanwhile.
|
|
*/
|
|
lpn->groups_changed = 1U;
|
|
|
|
if (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) {
|
|
bt_mesh_heartbeat_send();
|
|
}
|
|
|
|
if (disable) {
|
|
lpn_set_state(BLE_MESH_LPN_DISABLED);
|
|
return;
|
|
}
|
|
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
|
|
|
|
scan_after_clear = false;
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
}
|
|
|
|
static void friend_req_sent(u16_t duration, int err, void *user_data)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
if (err) {
|
|
BT_ERR("%s, Sending Friend Request failed (err %d)", __func__, err);
|
|
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_enable();
|
|
}
|
|
return;
|
|
}
|
|
|
|
lpn->adv_duration = duration;
|
|
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_WAIT);
|
|
lpn_set_state(BLE_MESH_LPN_REQ_WAIT);
|
|
} else {
|
|
k_delayed_work_submit(&lpn->timer,
|
|
duration + FRIEND_REQ_TIMEOUT);
|
|
lpn_set_state(BLE_MESH_LPN_WAIT_OFFER);
|
|
}
|
|
}
|
|
|
|
static const struct bt_mesh_send_cb friend_req_sent_cb = {
|
|
.start = friend_req_sent,
|
|
};
|
|
|
|
static int send_friend_req(struct bt_mesh_lpn *lpn)
|
|
{
|
|
const struct bt_mesh_comp *comp = bt_mesh_comp_get();
|
|
struct bt_mesh_msg_ctx ctx = {
|
|
.net_idx = bt_mesh.sub[0].net_idx,
|
|
.app_idx = BLE_MESH_KEY_UNUSED,
|
|
.addr = BLE_MESH_ADDR_FRIENDS,
|
|
.send_ttl = 0,
|
|
};
|
|
struct bt_mesh_net_tx tx = {
|
|
.sub = &bt_mesh.sub[0],
|
|
.ctx = &ctx,
|
|
.src = bt_mesh_primary_addr(),
|
|
.xmit = POLL_XMIT,
|
|
};
|
|
struct bt_mesh_ctl_friend_req req = {
|
|
.criteria = LPN_CRITERIA,
|
|
.recv_delay = LPN_RECV_DELAY,
|
|
.poll_to = LPN_POLL_TO,
|
|
.prev_addr = sys_cpu_to_be16(lpn->old_friend),
|
|
.num_elem = comp->elem_count,
|
|
.lpn_counter = sys_cpu_to_be16(lpn->counter),
|
|
};
|
|
|
|
BT_DBG("%s", __func__);
|
|
|
|
return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_REQ, &req,
|
|
sizeof(req), NULL, &friend_req_sent_cb, NULL);
|
|
}
|
|
|
|
static void req_sent(u16_t duration, int err, void *user_data)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
BT_DBG("req 0x%02x duration %u err %d state %s",
|
|
lpn->sent_req, duration, err, state2str(lpn->state));
|
|
|
|
if (err) {
|
|
BT_ERR("%s, Sending request failed (err %d)", __func__, err);
|
|
lpn->sent_req = 0U;
|
|
group_zero(lpn->pending);
|
|
return;
|
|
}
|
|
|
|
lpn->req_attempts++;
|
|
lpn->adv_duration = duration;
|
|
|
|
if (lpn->established || IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
lpn_set_state(BLE_MESH_LPN_RECV_DELAY);
|
|
/* We start scanning a bit early to elimitate risk of missing
|
|
* response data due to HCI and other latencies.
|
|
*/
|
|
k_delayed_work_submit(&lpn->timer,
|
|
LPN_RECV_DELAY - SCAN_LATENCY);
|
|
} else {
|
|
lpn_set_state(BLE_MESH_LPN_OFFER_RECV);
|
|
/**
|
|
* Friend Update is replied by Friend Node with TTL set to 0 and Network
|
|
* Transmit set to 30ms which will cause the packet easy to be missed.
|
|
* Regarding this situation, here we can reduce the duration of receiving
|
|
* the first Friend Update.
|
|
*/
|
|
k_delayed_work_submit(&lpn->timer,
|
|
LPN_RECV_DELAY + duration +
|
|
lpn->recv_win);
|
|
}
|
|
}
|
|
|
|
static const struct bt_mesh_send_cb req_sent_cb = {
|
|
.start = req_sent,
|
|
};
|
|
|
|
static int send_friend_poll(void)
|
|
{
|
|
struct bt_mesh_msg_ctx ctx = {
|
|
.net_idx = bt_mesh.sub[0].net_idx,
|
|
.app_idx = BLE_MESH_KEY_UNUSED,
|
|
.addr = bt_mesh.lpn.frnd,
|
|
.send_ttl = 0,
|
|
};
|
|
struct bt_mesh_net_tx tx = {
|
|
.sub = &bt_mesh.sub[0],
|
|
.ctx = &ctx,
|
|
.src = bt_mesh_primary_addr(),
|
|
.xmit = POLL_XMIT,
|
|
.friend_cred = true,
|
|
};
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
u8_t fsn = lpn->fsn;
|
|
int err = 0;
|
|
|
|
BT_DBG("lpn->sent_req 0x%02x", lpn->sent_req);
|
|
|
|
if (lpn->sent_req) {
|
|
if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) {
|
|
lpn->pending_poll = 1U;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
err = bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_POLL, &fsn, 1,
|
|
NULL, &req_sent_cb, NULL);
|
|
if (err == 0) {
|
|
lpn->pending_poll = 0U;
|
|
lpn->sent_req = TRANS_CTL_OP_FRIEND_POLL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
void bt_mesh_lpn_disable(bool force)
|
|
{
|
|
if (bt_mesh.lpn.state == BLE_MESH_LPN_DISABLED) {
|
|
return;
|
|
}
|
|
|
|
clear_friendship(force, true);
|
|
}
|
|
|
|
int bt_mesh_lpn_set(bool enable, bool force)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
if (enable) {
|
|
if (lpn->state != BLE_MESH_LPN_DISABLED) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (lpn->state == BLE_MESH_LPN_DISABLED) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!bt_mesh_is_provisioned()) {
|
|
if (enable) {
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
} else {
|
|
lpn_set_state(BLE_MESH_LPN_DISABLED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (enable) {
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
|
|
send_friend_req(lpn);
|
|
} else {
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_AUTO) &&
|
|
lpn->state == BLE_MESH_LPN_TIMER) {
|
|
k_delayed_work_cancel(&lpn->timer);
|
|
lpn_set_state(BLE_MESH_LPN_DISABLED);
|
|
} else {
|
|
bt_mesh_lpn_disable(force);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void friend_response_received(struct bt_mesh_lpn *lpn)
|
|
{
|
|
BT_DBG("lpn->sent_req 0x%02x", lpn->sent_req);
|
|
|
|
if (lpn->sent_req == TRANS_CTL_OP_FRIEND_POLL) {
|
|
lpn->fsn++;
|
|
}
|
|
|
|
k_delayed_work_cancel(&lpn->timer);
|
|
bt_mesh_scan_disable();
|
|
lpn_set_state(BLE_MESH_LPN_ESTABLISHED);
|
|
lpn->req_attempts = 0U;
|
|
lpn->sent_req = 0U;
|
|
}
|
|
|
|
void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
if (lpn->state == BLE_MESH_LPN_TIMER) {
|
|
BT_DBG("Restarting establishment timer");
|
|
k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) {
|
|
BT_WARN("Unexpected message withouth a preceding Poll");
|
|
return;
|
|
}
|
|
|
|
friend_response_received(lpn);
|
|
|
|
BT_DBG("Requesting more messages from Friend");
|
|
|
|
send_friend_poll();
|
|
}
|
|
|
|
int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_ctl_friend_offer *msg = (void *)buf->data;
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
struct bt_mesh_subnet *sub = rx->sub;
|
|
struct friend_cred *cred = NULL;
|
|
u16_t frnd_counter = 0U;
|
|
int err = 0;
|
|
|
|
if (buf->len < sizeof(*msg)) {
|
|
BT_WARN("Too short Friend Offer");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (lpn->state != BLE_MESH_LPN_WAIT_OFFER) {
|
|
BT_WARN("Ignoring unexpected Friend Offer");
|
|
return 0;
|
|
}
|
|
|
|
if (!msg->recv_win) {
|
|
BT_WARN("Prohibited ReceiveWindow value");
|
|
return -EINVAL;
|
|
}
|
|
|
|
frnd_counter = sys_be16_to_cpu(msg->frnd_counter);
|
|
|
|
BT_INFO("recv_win %u queue_size %u sub_list_size %u rssi %d counter %u",
|
|
msg->recv_win, msg->queue_size, msg->sub_list_size, msg->rssi,
|
|
frnd_counter);
|
|
|
|
lpn->frnd = rx->ctx.addr;
|
|
|
|
cred = friend_cred_create(sub, lpn->frnd, lpn->counter, frnd_counter);
|
|
if (!cred) {
|
|
lpn->frnd = BLE_MESH_ADDR_UNASSIGNED;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* TODO: Add offer acceptance criteria check */
|
|
|
|
k_delayed_work_cancel(&lpn->timer);
|
|
|
|
lpn->recv_win = msg->recv_win;
|
|
lpn->queue_size = msg->queue_size;
|
|
|
|
err = send_friend_poll();
|
|
if (err) {
|
|
friend_cred_clear(cred);
|
|
lpn->frnd = BLE_MESH_ADDR_UNASSIGNED;
|
|
lpn->recv_win = 0U;
|
|
lpn->queue_size = 0U;
|
|
return err;
|
|
}
|
|
|
|
lpn->counter++;
|
|
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->data;
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
u16_t addr = 0U, counter = 0U;
|
|
|
|
if (buf->len < sizeof(*msg)) {
|
|
BT_WARN("Too short Friend Clear Confirm");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (lpn->state != BLE_MESH_LPN_CLEAR) {
|
|
BT_WARN("Ignoring unexpected Friend Clear Confirm");
|
|
return 0;
|
|
}
|
|
|
|
addr = sys_be16_to_cpu(msg->lpn_addr);
|
|
counter = sys_be16_to_cpu(msg->lpn_counter);
|
|
|
|
BT_DBG("LPNAddress 0x%04x LPNCounter 0x%04x", addr, counter);
|
|
|
|
if (addr != bt_mesh_primary_addr() || counter != lpn->counter) {
|
|
BT_WARN("Invalid parameters in Friend Clear Confirm");
|
|
return 0;
|
|
}
|
|
|
|
lpn->clear_success = 1U;
|
|
clear_friendship(false, lpn->disable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lpn_group_add(u16_t group)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
u16_t *free_slot = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) {
|
|
if (lpn->groups[i] == group) {
|
|
bt_mesh_atomic_clear_bit(lpn->to_remove, i);
|
|
return;
|
|
}
|
|
|
|
if (!free_slot && lpn->groups[i] == BLE_MESH_ADDR_UNASSIGNED) {
|
|
free_slot = &lpn->groups[i];
|
|
}
|
|
}
|
|
|
|
if (!free_slot) {
|
|
BT_WARN("Friend Subscription List exceeded!");
|
|
return;
|
|
}
|
|
|
|
*free_slot = group;
|
|
lpn->groups_changed = 1U;
|
|
}
|
|
|
|
static void lpn_group_del(u16_t group)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) {
|
|
if (lpn->groups[i] == group) {
|
|
if (bt_mesh_atomic_test_bit(lpn->added, i) ||
|
|
bt_mesh_atomic_test_bit(lpn->pending, i)) {
|
|
bt_mesh_atomic_set_bit(lpn->to_remove, i);
|
|
lpn->groups_changed = 1U;
|
|
} else {
|
|
lpn->groups[i] = BLE_MESH_ADDR_UNASSIGNED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline int group_popcount(bt_mesh_atomic_t *target)
|
|
{
|
|
#if CONFIG_BLE_MESH_LPN_GROUPS > 32
|
|
int i, count = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
|
|
count += popcount(bt_mesh_atomic_get(&target[i]));
|
|
}
|
|
#else
|
|
return popcount(bt_mesh_atomic_get(target));
|
|
#endif
|
|
}
|
|
|
|
static bool sub_update(u8_t op)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
int added_count = group_popcount(lpn->added);
|
|
struct bt_mesh_msg_ctx ctx = {
|
|
.net_idx = bt_mesh.sub[0].net_idx,
|
|
.app_idx = BLE_MESH_KEY_UNUSED,
|
|
.addr = lpn->frnd,
|
|
.send_ttl = 0,
|
|
};
|
|
struct bt_mesh_net_tx tx = {
|
|
.sub = &bt_mesh.sub[0],
|
|
.ctx = &ctx,
|
|
.src = bt_mesh_primary_addr(),
|
|
.xmit = POLL_XMIT,
|
|
.friend_cred = true,
|
|
};
|
|
struct bt_mesh_ctl_friend_sub req = {0};
|
|
size_t i = 0U, g = 0U;
|
|
|
|
BT_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req);
|
|
|
|
if (lpn->sent_req) {
|
|
return false;
|
|
}
|
|
|
|
for (i = 0U, g = 0U; i < ARRAY_SIZE(lpn->groups); i++) {
|
|
if (lpn->groups[i] == BLE_MESH_ADDR_UNASSIGNED) {
|
|
continue;
|
|
}
|
|
|
|
if (op == TRANS_CTL_OP_FRIEND_SUB_ADD) {
|
|
if (bt_mesh_atomic_test_bit(lpn->added, i)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (!bt_mesh_atomic_test_bit(lpn->to_remove, i)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (added_count + g >= lpn->queue_size) {
|
|
BT_WARN("%s, Friend Queue Size exceeded", __func__);
|
|
break;
|
|
}
|
|
|
|
req.addr_list[g++] = sys_cpu_to_be16(lpn->groups[i]);
|
|
bt_mesh_atomic_set_bit(lpn->pending, i);
|
|
|
|
if (g == ARRAY_SIZE(req.addr_list)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g == 0U) {
|
|
group_zero(lpn->pending);
|
|
return false;
|
|
}
|
|
|
|
req.xact = lpn->xact_next++;
|
|
|
|
if (bt_mesh_ctl_send(&tx, op, &req, 1 + g * 2, NULL,
|
|
&req_sent_cb, NULL) < 0) {
|
|
group_zero(lpn->pending);
|
|
return false;
|
|
}
|
|
|
|
lpn->xact_pending = req.xact;
|
|
lpn->sent_req = op;
|
|
return true;
|
|
}
|
|
|
|
static void update_timeout(struct bt_mesh_lpn *lpn)
|
|
{
|
|
if (lpn->established) {
|
|
BT_WARN("No response from Friend during ReceiveWindow");
|
|
bt_mesh_scan_disable();
|
|
lpn_set_state(BLE_MESH_LPN_ESTABLISHED);
|
|
k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT);
|
|
} else {
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
|
|
if (lpn->req_attempts < FIRST_POLL_ATTEMPTS) {
|
|
BT_WARN("Retrying first Friend Poll");
|
|
lpn->sent_req = 0U;
|
|
if (send_friend_poll() == 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
BT_ERR("Timed out waiting for first Friend Update");
|
|
clear_friendship(false, false);
|
|
}
|
|
}
|
|
|
|
static void lpn_timeout(struct k_work *work)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
BT_DBG("state: %s", state2str(lpn->state));
|
|
|
|
switch (lpn->state) {
|
|
case BLE_MESH_LPN_DISABLED:
|
|
break;
|
|
case BLE_MESH_LPN_CLEAR:
|
|
clear_friendship(false, bt_mesh.lpn.disable);
|
|
break;
|
|
case BLE_MESH_LPN_TIMER:
|
|
BT_DBG("Starting to look for Friend nodes");
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
/* fall through */
|
|
case BLE_MESH_LPN_ENABLED:
|
|
send_friend_req(lpn);
|
|
break;
|
|
case BLE_MESH_LPN_REQ_WAIT:
|
|
bt_mesh_scan_enable();
|
|
k_delayed_work_submit(&lpn->timer,
|
|
lpn->adv_duration + FRIEND_REQ_SCAN);
|
|
lpn_set_state(BLE_MESH_LPN_WAIT_OFFER);
|
|
break;
|
|
case BLE_MESH_LPN_WAIT_OFFER:
|
|
BT_WARN("No acceptable Friend Offers received");
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_disable();
|
|
}
|
|
lpn->counter++;
|
|
lpn_set_state(BLE_MESH_LPN_ENABLED);
|
|
lpn->sent_req = 0U;
|
|
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
|
|
break;
|
|
case BLE_MESH_LPN_OFFER_RECV:
|
|
if (lpn->req_attempts < FIRST_POLL_ATTEMPTS) {
|
|
BT_WARN("Retrying the first Friend Poll, %d attempts", lpn->req_attempts);
|
|
lpn->sent_req = 0U;
|
|
send_friend_poll();
|
|
break;
|
|
}
|
|
|
|
BT_ERR("Timeout waiting for the first Friend Update");
|
|
clear_friendship(true, false);
|
|
break;
|
|
case BLE_MESH_LPN_ESTABLISHED:
|
|
if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) {
|
|
u8_t req = lpn->sent_req;
|
|
|
|
lpn->sent_req = 0U;
|
|
|
|
if (!req || req == TRANS_CTL_OP_FRIEND_POLL) {
|
|
send_friend_poll();
|
|
} else {
|
|
sub_update(req);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
BT_ERR("No response from Friend after %u retries",
|
|
lpn->req_attempts);
|
|
lpn->req_attempts = 0U;
|
|
clear_friendship(false, false);
|
|
break;
|
|
case BLE_MESH_LPN_RECV_DELAY:
|
|
k_delayed_work_submit(&lpn->timer,
|
|
lpn->adv_duration + SCAN_LATENCY +
|
|
lpn->recv_win);
|
|
bt_mesh_scan_enable();
|
|
lpn_set_state(BLE_MESH_LPN_WAIT_UPDATE);
|
|
break;
|
|
case BLE_MESH_LPN_WAIT_UPDATE:
|
|
update_timeout(lpn);
|
|
break;
|
|
default:
|
|
__ASSERT(0, "Unhandled LPN state");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void bt_mesh_lpn_group_add(u16_t group)
|
|
{
|
|
BT_DBG("group 0x%04x", group);
|
|
|
|
lpn_group_add(group);
|
|
|
|
if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) {
|
|
return;
|
|
}
|
|
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD);
|
|
}
|
|
|
|
void bt_mesh_lpn_group_del(u16_t *groups, size_t group_count)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < group_count; i++) {
|
|
if (groups[i] != BLE_MESH_ADDR_UNASSIGNED) {
|
|
BT_DBG("group 0x%04x", groups[i]);
|
|
lpn_group_del(groups[i]);
|
|
}
|
|
}
|
|
|
|
if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) {
|
|
return;
|
|
}
|
|
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_REM);
|
|
}
|
|
|
|
static s32_t poll_timeout(struct bt_mesh_lpn *lpn)
|
|
{
|
|
/* If we're waiting for segment acks keep polling at high freq */
|
|
if (bt_mesh_tx_in_progress()) {
|
|
return MIN(POLL_TIMEOUT_MAX(lpn), K_SECONDS(1));
|
|
}
|
|
|
|
if (lpn->poll_timeout < POLL_TIMEOUT_MAX(lpn)) {
|
|
lpn->poll_timeout *= 2;
|
|
lpn->poll_timeout = MIN(lpn->poll_timeout,
|
|
POLL_TIMEOUT_MAX(lpn));
|
|
}
|
|
|
|
BT_DBG("Poll Timeout is %ums", lpn->poll_timeout);
|
|
|
|
return lpn->poll_timeout;
|
|
}
|
|
|
|
int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_ctl_friend_sub_confirm *msg = (void *)buf->data;
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
if (buf->len < sizeof(*msg)) {
|
|
BT_WARN("Too short Friend Subscription Confirm");
|
|
return -EINVAL;
|
|
}
|
|
|
|
BT_DBG("xact 0x%02x", msg->xact);
|
|
|
|
if (!lpn->sent_req) {
|
|
BT_WARN("No pending subscription list message");
|
|
return 0;
|
|
}
|
|
|
|
if (msg->xact != lpn->xact_pending) {
|
|
BT_WARN("Transaction mismatch (0x%02x != 0x%02x)",
|
|
msg->xact, lpn->xact_pending);
|
|
return 0;
|
|
}
|
|
|
|
if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_ADD) {
|
|
group_set(lpn->added, lpn->pending);
|
|
group_zero(lpn->pending);
|
|
} else if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_REM) {
|
|
int i;
|
|
|
|
group_clear(lpn->added, lpn->pending);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) {
|
|
if (bt_mesh_atomic_test_and_clear_bit(lpn->pending, i) &&
|
|
bt_mesh_atomic_test_and_clear_bit(lpn->to_remove, i)) {
|
|
lpn->groups[i] = BLE_MESH_ADDR_UNASSIGNED;
|
|
}
|
|
}
|
|
} else {
|
|
BT_WARN("Unexpected Friend Subscription Confirm");
|
|
return 0;
|
|
}
|
|
|
|
friend_response_received(lpn);
|
|
|
|
if (lpn->groups_changed) {
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD);
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_REM);
|
|
|
|
if (!lpn->sent_req) {
|
|
lpn->groups_changed = 0U;
|
|
}
|
|
}
|
|
|
|
if (lpn->pending_poll) {
|
|
send_friend_poll();
|
|
}
|
|
|
|
if (!lpn->sent_req) {
|
|
k_delayed_work_submit(&lpn->timer, poll_timeout(lpn));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_ctl_friend_update *msg = (void *)buf->data;
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
struct bt_mesh_subnet *sub = rx->sub;
|
|
u32_t iv_index = 0U;
|
|
|
|
if (buf->len < sizeof(*msg)) {
|
|
BT_WARN("Too short Friend Update");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) {
|
|
BT_WARN("Unexpected friend update");
|
|
return 0;
|
|
}
|
|
|
|
if (sub->kr_phase == BLE_MESH_KR_PHASE_2 && !rx->new_key) {
|
|
BT_WARN("Ignoring Phase 2 KR Update secured using old key");
|
|
return 0;
|
|
}
|
|
|
|
if (bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_INITIATOR) &&
|
|
(bt_mesh_atomic_test_bit(bt_mesh.flags, BLE_MESH_IVU_IN_PROGRESS) ==
|
|
BLE_MESH_IV_UPDATE(msg->flags))) {
|
|
bt_mesh_beacon_ivu_initiator(false);
|
|
}
|
|
|
|
if (!lpn->established) {
|
|
struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get();
|
|
|
|
/* This is normally checked on the transport layer, however
|
|
* in this state we're also still accepting master
|
|
* credentials so we need to ensure the right ones (Friend
|
|
* Credentials) were used for this message.
|
|
*/
|
|
if (!rx->friend_cred) {
|
|
BT_WARN("Friend Update with wrong credentials");
|
|
return -EINVAL;
|
|
}
|
|
|
|
lpn->established = 1U;
|
|
|
|
BT_INFO("Friendship established with 0x%04x", lpn->frnd);
|
|
|
|
if (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) {
|
|
bt_mesh_heartbeat_send();
|
|
}
|
|
|
|
if (lpn_cb) {
|
|
lpn_cb(lpn->frnd, true);
|
|
}
|
|
|
|
/* Set initial poll timeout */
|
|
lpn->poll_timeout = MIN(POLL_TIMEOUT_MAX(lpn),
|
|
POLL_TIMEOUT_INIT);
|
|
}
|
|
|
|
friend_response_received(lpn);
|
|
|
|
iv_index = sys_be32_to_cpu(msg->iv_index);
|
|
|
|
BT_INFO("flags 0x%02x iv_index 0x%08x md %u", msg->flags, iv_index,
|
|
msg->md);
|
|
|
|
if (bt_mesh_kr_update(sub, BLE_MESH_KEY_REFRESH(msg->flags),
|
|
rx->new_key)) {
|
|
bt_mesh_net_beacon_update(sub);
|
|
}
|
|
|
|
bt_mesh_net_iv_update(iv_index, BLE_MESH_IV_UPDATE(msg->flags));
|
|
|
|
if (lpn->groups_changed) {
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD);
|
|
sub_update(TRANS_CTL_OP_FRIEND_SUB_REM);
|
|
|
|
if (!lpn->sent_req) {
|
|
lpn->groups_changed = 0U;
|
|
}
|
|
}
|
|
|
|
if (msg->md) {
|
|
BT_DBG("Requesting for more messages");
|
|
send_friend_poll();
|
|
}
|
|
|
|
if (!lpn->sent_req) {
|
|
k_delayed_work_submit(&lpn->timer, poll_timeout(lpn));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_lpn_poll(void)
|
|
{
|
|
if (!bt_mesh.lpn.established) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
BT_DBG("Requesting more messages");
|
|
|
|
return send_friend_poll();
|
|
}
|
|
|
|
void bt_mesh_lpn_set_cb(void (*cb)(u16_t friend_addr, bool established))
|
|
{
|
|
lpn_cb = cb;
|
|
}
|
|
|
|
int bt_mesh_lpn_init(void)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
BT_DBG("%s", __func__);
|
|
|
|
k_delayed_work_init(&lpn->timer, lpn_timeout);
|
|
|
|
if (lpn->state == BLE_MESH_LPN_ENABLED) {
|
|
if (!IS_ENABLED(CONFIG_BLE_MESH_LPN_ESTABLISHMENT)) {
|
|
bt_mesh_scan_enable();
|
|
}
|
|
|
|
send_friend_req(lpn);
|
|
} else {
|
|
bt_mesh_scan_enable();
|
|
|
|
if (IS_ENABLED(CONFIG_BLE_MESH_LPN_AUTO)) {
|
|
BT_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT);
|
|
lpn_set_state(BLE_MESH_LPN_TIMER);
|
|
k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_lpn_deinit(void)
|
|
{
|
|
struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
|
|
|
|
bt_mesh_lpn_disable(true);
|
|
|
|
k_delayed_work_free(&lpn->timer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_BLE_MESH_LOW_POWER */
|