kopia lustrzana https://github.com/espressif/esp-idf
1058 wiersze
27 KiB
C
1058 wiersze
27 KiB
C
/* Bluetooth Mesh */
|
|
|
|
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
|
|
#include "sdkconfig.h"
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
|
|
#include "mesh_buf.h"
|
|
#include "mesh_kernel.h"
|
|
#include "mesh_trace.h"
|
|
#include "mesh_main.h"
|
|
|
|
#include "crypto.h"
|
|
#include "adv.h"
|
|
#include "mesh.h"
|
|
#include "net.h"
|
|
#include "transport.h"
|
|
#include "access.h"
|
|
#include "beacon.h"
|
|
#include "foundation.h"
|
|
#include "lpn.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))
|
|
|
|
/* Update 4 to 20 for BQB test case MESH/NODE/FRND/LPM/BI-02-C */
|
|
#define REQ_ATTEMPTS(lpn) (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 4)
|
|
|
|
#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)
|
|
|
|
static void (*lpn_cb)(u16_t friend_addr, bool established);
|
|
|
|
#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
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";
|
|
default:
|
|
return "(unknown)";
|
|
}
|
|
}
|
|
#endif /* CONFIG_BLE_MESH_DEBUG_LOW_POWER */
|
|
|
|
static inline void lpn_set_state(int state)
|
|
{
|
|
#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
BT_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state));
|
|
#endif
|
|
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 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.
|
|
*/
|
|
bt_mesh_scan_enable();
|
|
|
|
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_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 (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);
|
|
}
|
|
|
|
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);
|
|
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 = 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;
|
|
|
|
#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
BT_DBG("req 0x%02x duration %u err %d state %s",
|
|
lpn->sent_req, duration, err, state2str(lpn->state));
|
|
#endif
|
|
|
|
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);
|
|
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;
|
|
|
|
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)
|
|
{
|
|
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(false);
|
|
}
|
|
}
|
|
|
|
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;
|
|
u16_t frnd_counter;
|
|
int err;
|
|
|
|
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_DBG("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++;
|
|
|
|
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, counter;
|
|
|
|
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;
|
|
size_t i, g;
|
|
|
|
BT_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req);
|
|
|
|
if (lpn->sent_req) {
|
|
return false;
|
|
}
|
|
|
|
for (i = 0, g = 0; 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 == 0) {
|
|
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 < 6) {
|
|
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;
|
|
|
|
#if defined(CONFIG_BLE_MESH_DEBUG_LOW_POWER)
|
|
BT_DBG("state: %s", state2str(lpn->state));
|
|
#endif
|
|
|
|
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);
|
|
k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
|
|
break;
|
|
case BLE_MESH_LPN_OFFER_RECV:
|
|
BT_WARN("No Friend Update received after the first Friend Poll");
|
|
lpn->sent_req = 0U;
|
|
send_friend_poll();
|
|
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;
|
|
|
|
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) {
|
|
/* 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 (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_DBG("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_disable();
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
#endif /* CONFIG_BLE_MESH_LOW_POWER */
|