diff options
Diffstat (limited to 'src/libs/mynewt-nimble/nimble/host/mesh/src/lpn.c')
-rw-r--r-- | src/libs/mynewt-nimble/nimble/host/mesh/src/lpn.c | 1056 |
1 files changed, 1056 insertions, 0 deletions
diff --git a/src/libs/mynewt-nimble/nimble/host/mesh/src/lpn.c b/src/libs/mynewt-nimble/nimble/host/mesh/src/lpn.c new file mode 100644 index 00000000..ec012a5f --- /dev/null +++ b/src/libs/mynewt-nimble/nimble/host/mesh/src/lpn.c @@ -0,0 +1,1056 @@ +/* Bluetooth Mesh */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "syscfg/syscfg.h" +#define MESH_LOG_MODULE BLE_MESH_LOW_POWER_LOG + +#if MYNEWT_VAL(BLE_MESH_LOW_POWER) + +#include <stdint.h> + +#include "mesh/mesh.h" +#include "mesh_priv.h" +#include "crypto.h" +#include "adv.h" +#include "net.h" +#include "transport.h" +#include "access.h" +#include "beacon.h" +#include "foundation.h" +#include "lpn.h" + +#if MYNEWT_VAL(BLE_MESH_LPN_AUTO) +#define LPN_AUTO_TIMEOUT K_SECONDS(MYNEWT_VAL(BLE_MESH_LPN_AUTO_TIMEOUT)) +#else +#define LPN_AUTO_TIMEOUT 0 +#endif + +#define LPN_RECV_DELAY MYNEWT_VAL(BLE_MESH_LPN_RECV_DELAY) +#define SCAN_LATENCY min(MYNEWT_VAL(BLE_MESH_LPN_SCAN_LATENCY), \ + LPN_RECV_DELAY) + +#define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(MYNEWT_VAL(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 (MYNEWT_VAL(BLE_MESH_LPN_INIT_POLL_TIMEOUT) * 100) +#define POLL_TIMEOUT_MAX(lpn) ((MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT) * 100) - \ + REQ_RETRY_DURATION(lpn)) +#define REQ_ATTEMPTS(lpn) (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 4) + +#define CLEAR_ATTEMPTS 2 + +#define LPN_CRITERIA ((MYNEWT_VAL(BLE_MESH_LPN_MIN_QUEUE_SIZE)) | \ + (MYNEWT_VAL(BLE_MESH_LPN_RSSI_FACTOR) << 3) | \ + (MYNEWT_VAL(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(MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT)) + +/* 2 transmissions, 20ms interval */ +#define POLL_XMIT BT_MESH_TRANSMIT(1, 20) + +static void (*lpn_cb)(u16_t friend_addr, bool established); + +#if MYNEWT_VAL(BLE_MESH_LOW_POWER_LOG_LVL) == LOG_LEVEL_DEBUG +static const char *state2str(int state) +{ + switch (state) { + case BT_MESH_LPN_DISABLED: + return "disabled"; + case BT_MESH_LPN_CLEAR: + return "clear"; + case BT_MESH_LPN_TIMER: + return "timer"; + case BT_MESH_LPN_ENABLED: + return "enabled"; + case BT_MESH_LPN_REQ_WAIT: + return "req wait"; + case BT_MESH_LPN_WAIT_OFFER: + return "wait offer"; + case BT_MESH_LPN_ESTABLISHED: + return "established"; + case BT_MESH_LPN_RECV_DELAY: + return "recv delay"; + case BT_MESH_LPN_WAIT_UPDATE: + return "wait update"; + default: + return "(unknown)"; + } +} +#endif + +static inline void lpn_set_state(int state) +{ +#if MYNEWT_VAL(BLE_MESH_LOW_POWER_LOG_LVL) == LOG_LEVEL_DEBUG + BT_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state)); +#endif + bt_mesh.lpn.state = state; +} + +static inline void group_zero(atomic_t *target) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + atomic_set(&target[i], 0); + } +#else + atomic_set(target, 0); +#endif +} + +static inline void group_set(atomic_t *target, atomic_t *source) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + atomic_or(&target[i], atomic_get(&source[i])); + } +#else + atomic_or(target, atomic_get(source)); +#endif +} + +static inline void group_clear(atomic_t *target, atomic_t *source) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + atomic_and(&target[i], ~atomic_get(&source[i])); + } +#else + atomic_and(target, ~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("Sending Friend Request failed (err %d)", err); + lpn_set_state(BT_MESH_LPN_ENABLED); + clear_friendship(false, lpn->disable); + return; + } + + lpn_set_state(BT_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 = BT_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(""); + + 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 = BT_MESH_ADDR_UNASSIGNED; + } else { + lpn->old_friend = lpn->frnd; + } + + if (lpn_cb && lpn->frnd != BT_MESH_ADDR_UNASSIGNED) { + lpn_cb(lpn->frnd, false); + } + + lpn->frnd = BT_MESH_ADDR_UNASSIGNED; + lpn->fsn = 0; + lpn->req_attempts = 0; + lpn->recv_win = 0; + lpn->queue_size = 0; + lpn->disable = 0; + lpn->sent_req = 0; + lpn->established = 0; + lpn->clear_success = 0; + + 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 = 1; + + if (cfg->hb_pub.feat & BT_MESH_FEAT_LOW_POWER) { + bt_mesh_heartbeat_send(); + } + + if (disable) { + lpn_set_state(BT_MESH_LPN_DISABLED); + return; + } + + lpn_set_state(BT_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("Sending Friend Request failed (err %d)", err); + return; + } + + lpn->adv_duration = duration; + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_WAIT); + lpn_set_state(BT_MESH_LPN_REQ_WAIT); + } else { + k_delayed_work_submit(&lpn->timer, + duration + FRIEND_REQ_TIMEOUT); + lpn_set_state(BT_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 = BT_MESH_KEY_UNUSED, + .addr = BT_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(""); + + 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 MYNEWT_VAL(BLE_MESH_LOW_POWER_LOG_LVL) == LOG_LEVEL_DEBUG + BT_DBG("req 0x%02x duration %u err %d state %s", + lpn->sent_req, duration, err, state2str(lpn->state)); +#endif + + if (err) { + BT_ERR("Sending request failed (err %d)", err); + lpn->sent_req = 0; + group_zero(lpn->pending); + return; + } + + lpn->req_attempts++; + lpn->adv_duration = duration; + + if (lpn->established || IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + lpn_set_state(BT_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 { + 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 = BT_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 = 1; + } + + 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 = 0; + lpn->sent_req = TRANS_CTL_OP_FRIEND_POLL; + } + + return err; +} + +void bt_mesh_lpn_disable(bool force) +{ + if (bt_mesh.lpn.state == BT_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 != BT_MESH_LPN_DISABLED) { + return 0; + } + } else { + if (lpn->state == BT_MESH_LPN_DISABLED) { + return 0; + } + } + + if (!bt_mesh_is_provisioned()) { + if (enable) { + lpn_set_state(BT_MESH_LPN_ENABLED); + } else { + lpn_set_state(BT_MESH_LPN_DISABLED); + } + + return 0; + } + + if (enable) { + lpn_set_state(BT_MESH_LPN_ENABLED); + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + + send_friend_req(lpn); + } else { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO) && + lpn->state == BT_MESH_LPN_TIMER) { + k_delayed_work_cancel(&lpn->timer); + lpn_set_state(BT_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(BT_MESH_LPN_ESTABLISHED); + lpn->req_attempts = 0; + lpn->sent_req = 0; +} + +void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (lpn->state == BT_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 os_mbuf *buf) +{ + struct bt_mesh_ctl_friend_offer *msg = (void *)buf->om_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->om_len < sizeof(*msg)) { + BT_WARN("Too short Friend Offer"); + return -EINVAL; + } + + if (lpn->state != BT_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 = BT_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 = BT_MESH_ADDR_UNASSIGNED; + lpn->recv_win = 0; + lpn->queue_size = 0; + return err; + } + + lpn->counter++; + + return 0; +} + +int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct os_mbuf *buf) +{ + struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->om_data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + u16_t addr, counter; + + if (buf->om_len < sizeof(*msg)) { + BT_WARN("Too short Friend Clear Confirm"); + return -EINVAL; + } + + if (lpn->state != BT_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 = 1; + 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) { + atomic_clear_bit(lpn->to_remove, i); + return; + } + + if (!free_slot && lpn->groups[i] == BT_MESH_ADDR_UNASSIGNED) { + free_slot = &lpn->groups[i]; + } + } + + if (!free_slot) { + BT_WARN("Friend Subscription List exceeded!"); + return; + } + + *free_slot = group; + lpn->groups_changed = 1; +} + +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 (atomic_test_bit(lpn->added, i) || + atomic_test_bit(lpn->pending, i)) { + atomic_set_bit(lpn->to_remove, i); + lpn->groups_changed = 1; + } else { + lpn->groups[i] = BT_MESH_ADDR_UNASSIGNED; + } + } + } +} + +static inline int group_popcount(atomic_t *target) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { + count += popcount(atomic_get(&target[i])); + } +#else + return popcount(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 = BT_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] == BT_MESH_ADDR_UNASSIGNED) { + continue; + } + + if (op == TRANS_CTL_OP_FRIEND_SUB_ADD) { + if (atomic_test_bit(lpn->added, i)) { + continue; + } + } else { + if (!atomic_test_bit(lpn->to_remove, i)) { + continue; + } + } + + if (added_count + g >= lpn->queue_size) { + BT_WARN("Friend Queue Size exceeded"); + break; + } + + req.addr_list[g++] = sys_cpu_to_be16(lpn->groups[i]); + 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(BT_MESH_LPN_ESTABLISHED); + k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT); + } else { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + + if (lpn->req_attempts < 6) { + BT_WARN("Retrying first Friend Poll"); + lpn->sent_req = 0; + if (send_friend_poll() == 0) { + return; + } + } + + BT_ERR("Timed out waiting for first Friend Update"); + clear_friendship(false, false); + } +} + +static void lpn_timeout(struct ble_npl_event *work) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + +#if MYNEWT_VAL(BLE_MESH_LOW_POWER_LOG_LVL) == LOG_LEVEL_DEBUG + BT_DBG("state: %s", state2str(lpn->state)); +#endif + + switch (lpn->state) { + case BT_MESH_LPN_DISABLED: + break; + case BT_MESH_LPN_CLEAR: + clear_friendship(false, bt_mesh.lpn.disable); + break; + case BT_MESH_LPN_TIMER: + BT_DBG("Starting to look for Friend nodes"); + lpn_set_state(BT_MESH_LPN_ENABLED); + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + /* fall through */ + case BT_MESH_LPN_ENABLED: + send_friend_req(lpn); + break; + case BT_MESH_LPN_REQ_WAIT: + bt_mesh_scan_enable(); + k_delayed_work_submit(&lpn->timer, + lpn->adv_duration + FRIEND_REQ_SCAN); + lpn_set_state(BT_MESH_LPN_WAIT_OFFER); + break; + case BT_MESH_LPN_WAIT_OFFER: + BT_WARN("No acceptable Friend Offers received"); + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { + bt_mesh_scan_disable(); + } + lpn->counter++; + lpn_set_state(BT_MESH_LPN_ENABLED); + lpn->sent_req = 0U; + k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); + break; + case BT_MESH_LPN_ESTABLISHED: + if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) { + u8_t req = lpn->sent_req; + + lpn->sent_req = 0; + + 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 = 0; + clear_friendship(false, false); + break; + case BT_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(BT_MESH_LPN_WAIT_UPDATE); + break; + case BT_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] != BT_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", (unsigned) lpn->poll_timeout); + + return lpn->poll_timeout; +} + +int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, + struct os_mbuf *buf) +{ + struct bt_mesh_ctl_friend_sub_confirm *msg = (void *)buf->om_data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (buf->om_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 (atomic_test_and_clear_bit(lpn->pending, i) && + atomic_test_and_clear_bit(lpn->to_remove, i)) { + lpn->groups[i] = BT_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 = 0; + } + } + + 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 os_mbuf *buf) +{ + struct bt_mesh_ctl_friend_update *msg = (void *)buf->om_data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + struct bt_mesh_subnet *sub = rx->sub; + u32_t iv_index; + + if (buf->om_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 == BT_MESH_KR_PHASE_2 && !rx->new_key) { + BT_WARN("Ignoring Phase 2 KR Update secured using old key"); + return 0; + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR) && + (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS) == + BT_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 = 1; + + BT_INFO("Friendship established with 0x%04x", lpn->frnd); + + if (cfg->hb_pub.feat & BT_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_DBG("flags 0x%02x iv_index 0x%08x md %u", msg->flags, + (unsigned) iv_index, msg->md); + + if (bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(msg->flags), + rx->new_key)) { + bt_mesh_net_beacon_update(sub); + } + + bt_mesh_net_iv_update(iv_index, BT_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 = 0; + } + } + + 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(""); + + k_delayed_work_init(&lpn->timer, lpn_timeout); + + if (lpn->state == BT_MESH_LPN_ENABLED) { + if (IS_ENABLED(CONFIG_BT_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_BT_MESH_LPN_AUTO)) { + BT_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT); + lpn_set_state(BT_MESH_LPN_TIMER); + k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT); + } + } + + return 0; +} + +#endif /* MYNEWT_VAL(BLE_MESH_LOW_POWER) */ |