| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdint.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/iterable_sections.h> |
| #include <zephyr/net_buf.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/mesh.h> |
| |
| #include "crypto.h" |
| #include "mesh.h" |
| #include "net.h" |
| #include "transport.h" |
| #include "heartbeat.h" |
| #include "access.h" |
| #include "beacon.h" |
| #include "foundation.h" |
| #include "lpn.h" |
| |
| #define LOG_LEVEL CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_mesh_lpn); |
| |
| /** |
| * Log modes other than the deferred may cause unintended delays during processing of log messages. |
| * This in turns will affect scheduling of the receive delay and receive window. |
| */ |
| #if !defined(CONFIG_TEST) && !defined(CONFIG_ARCH_POSIX) && \ |
| defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_DEFERRED) && \ |
| (LOG_LEVEL >= LOG_LEVEL_INF) |
| #warning Frienship feature may work unstable when non-deferred log mode is selected. Use the \ |
| CONFIG_LOG_MODE_DEFERRED Kconfig option when Low Power node feature is enabled. |
| #endif |
| |
| #if defined(CONFIG_BT_MESH_ADV_LEGACY) |
| #define RX_DELAY_CORRECTION(lpn) ((lpn)->adv_duration) |
| #else |
| #define RX_DELAY_CORRECTION(lpn) 0 |
| #endif |
| |
| #if defined(CONFIG_BT_MESH_LPN_AUTO) |
| #define LPN_AUTO_TIMEOUT (CONFIG_BT_MESH_LPN_AUTO_TIMEOUT * MSEC_PER_SEC) |
| #else |
| #define LPN_AUTO_TIMEOUT 0 |
| #endif |
| |
| #define LPN_RECV_DELAY CONFIG_BT_MESH_LPN_RECV_DELAY |
| #define SCAN_LATENCY MIN(CONFIG_BT_MESH_LPN_SCAN_LATENCY, \ |
| LPN_RECV_DELAY) |
| |
| #define FRIEND_REQ_RETRY_TIMEOUT K_SECONDS(CONFIG_BT_MESH_LPN_RETRY_TIMEOUT) |
| |
| #define FRIEND_REQ_WAIT 100 |
| #define FRIEND_REQ_SCAN (1 * MSEC_PER_SEC) |
| #define FRIEND_REQ_TIMEOUT (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN) |
| |
| #define POLL_RETRY_TIMEOUT 100 |
| |
| #define REQ_RETRY_DURATION(lpn) (LPN_RECV_DELAY + (lpn)->adv_duration + \ |
| (lpn)->recv_win + POLL_RETRY_TIMEOUT) |
| |
| #define POLL_TIMEOUT_INIT (CONFIG_BT_MESH_LPN_INIT_POLL_TIMEOUT * 100) |
| |
| #define POLL_TIMEOUT (CONFIG_BT_MESH_LPN_POLL_TIMEOUT * 100) |
| |
| #define REQ_ATTEMPTS_MAX 6 |
| #define REQ_ATTEMPTS(lpn) MIN(REQ_ATTEMPTS_MAX, \ |
| POLL_TIMEOUT / REQ_RETRY_DURATION(lpn)) |
| |
| #define POLL_TIMEOUT_MAX(lpn) (POLL_TIMEOUT - \ |
| (REQ_ATTEMPTS(lpn) * REQ_RETRY_DURATION(lpn))) |
| |
| #define CLEAR_ATTEMPTS 3 |
| |
| #define LPN_CRITERIA ((CONFIG_BT_MESH_LPN_MIN_QUEUE_SIZE) | \ |
| (CONFIG_BT_MESH_LPN_RSSI_FACTOR << 3) | \ |
| (CONFIG_BT_MESH_LPN_RECV_WIN_FACTOR << 5)) |
| |
| #define POLL_TO(to) { (uint8_t)((to) >> 16), (uint8_t)((to) >> 8), (uint8_t)(to) } |
| #define LPN_POLL_TO POLL_TO(CONFIG_BT_MESH_LPN_POLL_TIMEOUT) |
| |
| /* 1 transmission, 20ms interval */ |
| #define POLL_XMIT BT_MESH_TRANSMIT(0, 20) |
| |
| #if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) |
| 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 /* CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG */ |
| |
| static int32_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()) { |
| LOG_DBG("Tx is in progress. Keep polling"); |
| return MIN(POLL_TIMEOUT_MAX(lpn), 1 * MSEC_PER_SEC); |
| } |
| |
| if (lpn->poll_timeout < POLL_TIMEOUT_MAX(lpn)) { |
| lpn->poll_timeout *= 2; |
| lpn->poll_timeout = |
| MIN(lpn->poll_timeout, POLL_TIMEOUT_MAX(lpn)); |
| } |
| |
| LOG_DBG("Poll Timeout is %ums", lpn->poll_timeout); |
| |
| return lpn->poll_timeout; |
| } |
| |
| static inline void lpn_set_state(int state) |
| { |
| #if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) |
| LOG_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++) { |
| (void)atomic_or(&target[i], atomic_get(&source[i])); |
| } |
| #else |
| (void)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++) { |
| (void)atomic_and(&target[i], ~atomic_get(&source[i])); |
| } |
| #else |
| (void)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; |
| |
| bt_mesh_scan_enable(); |
| |
| lpn->req_attempts++; |
| |
| if (err) { |
| LOG_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_work_reschedule(&lpn->timer, K_MSEC(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.lpn.sub->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.lpn.sub, |
| .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.lpn_counter), |
| }; |
| |
| LOG_DBG(""); |
| |
| return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req, |
| sizeof(req), &clear_sent_cb, NULL); |
| } |
| |
| static void clear_friendship(bool force, bool disable) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| bool was_established = lpn->established; |
| uint16_t frnd = lpn->frnd; |
| uint16_t net_idx = lpn->sub->net_idx; |
| |
| LOG_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(); |
| |
| /* Disable LPN while clearing, in case the work handler gets a chance to fire. */ |
| lpn_set_state(BT_MESH_LPN_DISABLED); |
| /* The timer handler returns without any actions if this fails. */ |
| (void)k_work_cancel_delayable(&lpn->timer); |
| |
| if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT) || disable) { |
| bt_mesh_scan_disable(); |
| } |
| |
| if (lpn->clear_success) { |
| lpn->old_friend = BT_MESH_ADDR_UNASSIGNED; |
| } else { |
| lpn->old_friend = lpn->frnd; |
| } |
| |
| for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) { |
| if (lpn->sub->keys[i].valid) { |
| bt_mesh_friend_cred_destroy(&lpn->cred[i]); |
| } |
| } |
| |
| lpn->frnd = BT_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; |
| lpn->sub = NULL; |
| |
| 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; |
| |
| bt_mesh_hb_feature_changed(BT_MESH_FEAT_LOW_POWER); |
| |
| if (!disable) { |
| lpn_set_state(BT_MESH_LPN_ENABLED); |
| |
| k_work_reschedule(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); |
| |
| if (!IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { |
| bt_mesh_scan_enable(); |
| } |
| } |
| |
| if (was_established) { |
| STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) { |
| if (cb->terminated) { |
| cb->terminated(net_idx, frnd); |
| } |
| } |
| } |
| } |
| |
| static void friend_req_send_end(int err, void *user_data) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| if (lpn->state != BT_MESH_LPN_ENABLED) { |
| return; |
| } |
| |
| if (err) { |
| LOG_ERR("Sending Friend Request failed (err %d)", err); |
| return; |
| } |
| |
| lpn->adv_duration = k_uptime_get_32() - lpn->adv_start_time; |
| |
| if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { |
| k_work_reschedule(&lpn->timer, |
| K_MSEC(FRIEND_REQ_WAIT - (int32_t)lpn->adv_duration)); |
| lpn_set_state(BT_MESH_LPN_REQ_WAIT); |
| } else { |
| k_work_reschedule(&lpn->timer, K_MSEC(FRIEND_REQ_TIMEOUT)); |
| lpn_set_state(BT_MESH_LPN_WAIT_OFFER); |
| } |
| } |
| |
| static void friend_req_send_start(uint16_t duration, int err, void *user_data) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| lpn->adv_start_time = k_uptime_get_32(); |
| |
| if (err) { |
| friend_req_send_end(err, user_data); |
| } |
| } |
| |
| static const struct bt_mesh_send_cb friend_req_send_cb = { |
| .start = friend_req_send_start, |
| .end = friend_req_send_end, |
| }; |
| |
| 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 = { |
| .app_idx = BT_MESH_KEY_UNUSED, |
| .addr = BT_MESH_ADDR_FRIENDS, |
| .send_ttl = 0, |
| }; |
| struct bt_mesh_net_tx tx = { |
| .ctx = &ctx, |
| .src = bt_mesh_primary_addr(), |
| .xmit = POLL_XMIT, |
| }; |
| |
| lpn->lpn_counter++; |
| |
| 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->lpn_counter), |
| }; |
| |
| LOG_DBG(""); |
| |
| lpn->sub = bt_mesh_subnet_next(NULL); |
| if (!lpn->sub) { |
| LOG_ERR("No subnets, can't start LPN mode"); |
| return -ENOENT; |
| } |
| |
| ctx.net_idx = lpn->sub->net_idx; |
| tx.sub = lpn->sub; |
| |
| return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_REQ, &req, |
| sizeof(req), &friend_req_send_cb, NULL); |
| } |
| |
| static void req_send_end(int err, void *user_data) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| bool retry; |
| |
| if (lpn->state == BT_MESH_LPN_DISABLED) { |
| return; |
| } |
| |
| lpn->adv_duration = k_uptime_get_32() - lpn->adv_start_time; |
| |
| #if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) |
| LOG_DBG("req 0x%02x duration %u err %d state %s", lpn->sent_req, lpn->adv_duration, err, |
| state2str(lpn->state)); |
| #endif |
| |
| if (err) { |
| LOG_ERR("Sending request failed (err %d)", err); |
| lpn->sent_req = 0U; |
| group_zero(lpn->pending); |
| return; |
| } |
| |
| retry = (lpn->req_attempts > 0); |
| |
| lpn->req_attempts++; |
| |
| 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 eliminate risk of missing |
| * response data due to HCI and other latencies. |
| */ |
| k_work_reschedule(&lpn->timer, |
| K_MSEC(LPN_RECV_DELAY - SCAN_LATENCY - RX_DELAY_CORRECTION(lpn))); |
| } else { |
| lpn_set_state(BT_MESH_LPN_WAIT_UPDATE); |
| k_work_reschedule(&lpn->timer, K_MSEC(LPN_RECV_DELAY + lpn->recv_win)); |
| } |
| |
| STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) { |
| if (cb->polled) { |
| cb->polled(lpn->sub->net_idx, lpn->frnd, retry); |
| } |
| } |
| } |
| |
| static void req_send_start(uint16_t duration, int err, void *user_data) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| lpn->adv_start_time = k_uptime_get_32(); |
| |
| if (err) { |
| req_send_end(err, user_data); |
| } |
| } |
| |
| static const struct bt_mesh_send_cb req_send_cb = { |
| .start = req_send_start, |
| .end = req_send_end, |
| }; |
| |
| static int send_friend_poll(void) |
| { |
| struct bt_mesh_msg_ctx ctx = { |
| .net_idx = bt_mesh.lpn.sub->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.lpn.sub, |
| .ctx = &ctx, |
| .src = bt_mesh_primary_addr(), |
| .xmit = POLL_XMIT, |
| .friend_cred = true, |
| }; |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| uint8_t fsn = lpn->fsn; |
| int err; |
| |
| LOG_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, |
| &req_send_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 == 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) { |
| /* If this fails, the work handler will just exit on the |
| * next timeout. |
| */ |
| (void)k_work_cancel_delayable(&lpn->timer); |
| lpn_set_state(BT_MESH_LPN_DISABLED); |
| } else { |
| bt_mesh_lpn_disable(false); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void bt_mesh_lpn_friendship_end(void) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| if (!lpn->established) { |
| return; |
| } |
| |
| clear_friendship(true, false); |
| } |
| |
| static void friend_response_received(struct bt_mesh_lpn *lpn) |
| { |
| LOG_DBG("lpn->sent_req 0x%02x", lpn->sent_req); |
| |
| if (lpn->sent_req == TRANS_CTL_OP_FRIEND_POLL) { |
| lpn->fsn++; |
| } |
| |
| lpn_set_state(BT_MESH_LPN_ESTABLISHED); |
| lpn->req_attempts = 0U; |
| lpn->sent_req = 0U; |
| |
| /* Schedule the next poll. This may be overridden by additional |
| * processing of the received response. |
| */ |
| int32_t timeout = poll_timeout(lpn); |
| |
| k_work_reschedule(&lpn->timer, K_MSEC(timeout)); |
| bt_mesh_scan_disable(); |
| } |
| |
| 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) { |
| LOG_DBG("Restarting establishment timer"); |
| k_work_reschedule(&lpn->timer, K_MSEC(LPN_AUTO_TIMEOUT)); |
| return; |
| } |
| |
| /* If the message was a Friend control message, it's possible that a |
| * Poll was already queued for sending. In this case, we're already in |
| * a different state. |
| */ |
| if (lpn->state != BT_MESH_LPN_WAIT_UPDATE) { |
| return; |
| } |
| |
| if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { |
| LOG_WRN("Unexpected message without a preceding Poll"); |
| return; |
| } |
| |
| friend_response_received(lpn); |
| |
| LOG_DBG("Requesting more messages from Friend"); |
| |
| send_friend_poll(); |
| } |
| |
| static int friend_cred_create(struct bt_mesh_net_cred *cred, const struct bt_mesh_key *key) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| return bt_mesh_friend_cred_create(cred, bt_mesh_primary_addr(), |
| lpn->frnd, lpn->lpn_counter, |
| lpn->frnd_counter, key); |
| } |
| |
| 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; |
| uint16_t frnd_counter; |
| int err; |
| |
| if (buf->len < sizeof(*msg)) { |
| LOG_WRN("Too short Friend Offer"); |
| return -EBADMSG; |
| } |
| |
| if (lpn->state != BT_MESH_LPN_WAIT_OFFER) { |
| LOG_WRN("Ignoring unexpected Friend Offer"); |
| return 0; |
| } |
| |
| if (!msg->recv_win) { |
| LOG_WRN("Prohibited ReceiveWindow value"); |
| return -EBADMSG; |
| } |
| |
| frnd_counter = sys_be16_to_cpu(msg->frnd_counter); |
| |
| LOG_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_counter = frnd_counter; |
| lpn->frnd = rx->ctx.addr; |
| |
| /* Create friend credentials for each of the valid keys in the |
| * friendship subnet: |
| */ |
| for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) { |
| if (!lpn->sub->keys[i].valid) { |
| continue; |
| } |
| |
| err = friend_cred_create(&lpn->cred[i], &lpn->sub->keys[i].net); |
| if (err) { |
| lpn->frnd = BT_MESH_ADDR_UNASSIGNED; |
| return err; |
| } |
| } |
| |
| /* TODO: Add offer acceptance criteria check */ |
| |
| lpn->recv_win = msg->recv_win; |
| lpn->queue_size = msg->queue_size; |
| |
| err = send_friend_poll(); |
| if (err) { |
| LOG_WRN("LPN didn't succeed poll sending (err %d)", err); |
| for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) { |
| if (lpn->sub->keys[i].valid) { |
| bt_mesh_friend_cred_destroy(&lpn->cred[i]); |
| } |
| } |
| |
| lpn->sub = NULL; |
| lpn->frnd = BT_MESH_ADDR_UNASSIGNED; |
| lpn->recv_win = 0U; |
| lpn->queue_size = 0U; |
| } |
| |
| 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; |
| uint16_t addr, counter; |
| |
| if (buf->len < sizeof(*msg)) { |
| LOG_WRN("Too short Friend Clear Confirm"); |
| return -EBADMSG; |
| } |
| |
| if (lpn->state != BT_MESH_LPN_CLEAR) { |
| LOG_WRN("Ignoring unexpected Friend Clear Confirm"); |
| return 0; |
| } |
| |
| addr = sys_be16_to_cpu(msg->lpn_addr); |
| counter = sys_be16_to_cpu(msg->lpn_counter); |
| |
| LOG_DBG("LPNAddress 0x%04x LPNCounter 0x%04x", addr, counter); |
| |
| if (addr != bt_mesh_primary_addr() || counter != lpn->lpn_counter) { |
| LOG_WRN("Invalid parameters in Friend Clear Confirm"); |
| return 0; |
| } |
| |
| lpn->clear_success = 1U; |
| clear_friendship(false, lpn->disable); |
| |
| return 0; |
| } |
| |
| static void lpn_group_add(uint16_t group) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| uint16_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) { |
| LOG_WRN("Friend Subscription List exceeded!"); |
| return; |
| } |
| |
| *free_slot = group; |
| lpn->groups_changed = 1U; |
| } |
| |
| static void lpn_group_del(uint16_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 = 1U; |
| } 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(uint8_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 = lpn->sub->net_idx, |
| .app_idx = BT_MESH_KEY_UNUSED, |
| .addr = lpn->frnd, |
| .send_ttl = 0, |
| }; |
| struct bt_mesh_net_tx tx = { |
| .sub = lpn->sub, |
| .ctx = &ctx, |
| .src = bt_mesh_primary_addr(), |
| .xmit = POLL_XMIT, |
| .friend_cred = true, |
| }; |
| struct bt_mesh_ctl_friend_sub req; |
| size_t i, g; |
| |
| LOG_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) { |
| LOG_WRN("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, |
| &req_send_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) { |
| LOG_WRN("No response from Friend during ReceiveWindow"); |
| lpn_set_state(BT_MESH_LPN_ESTABLISHED); |
| k_work_reschedule(&lpn->timer, K_MSEC(POLL_RETRY_TIMEOUT)); |
| bt_mesh_scan_disable(); |
| } else { |
| if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { |
| bt_mesh_scan_disable(); |
| } |
| |
| if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) { |
| LOG_WRN("Retrying first Friend Poll"); |
| lpn->sent_req = 0U; |
| if (send_friend_poll() == 0) { |
| return; |
| } |
| } |
| |
| LOG_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_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) |
| LOG_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: |
| LOG_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(); |
| } |
| __fallthrough; |
| case BT_MESH_LPN_ENABLED: |
| send_friend_req(lpn); |
| break; |
| case BT_MESH_LPN_REQ_WAIT: |
| k_work_reschedule(&lpn->timer, K_MSEC(lpn->adv_duration + FRIEND_REQ_SCAN)); |
| lpn_set_state(BT_MESH_LPN_WAIT_OFFER); |
| bt_mesh_scan_enable(); |
| break; |
| case BT_MESH_LPN_WAIT_OFFER: |
| LOG_WRN("No acceptable Friend Offers received"); |
| lpn_set_state(BT_MESH_LPN_ENABLED); |
| lpn->sent_req = 0U; |
| k_work_reschedule(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); |
| |
| if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) { |
| bt_mesh_scan_disable(); |
| } |
| break; |
| case BT_MESH_LPN_ESTABLISHED: |
| if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) { |
| uint8_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; |
| } |
| |
| LOG_ERR("No response from Friend after %u retries", lpn->req_attempts); |
| lpn->req_attempts = 0U; |
| clear_friendship(true, false); |
| break; |
| case BT_MESH_LPN_RECV_DELAY: |
| k_work_reschedule(&lpn->timer, |
| K_MSEC(SCAN_LATENCY + lpn->recv_win + RX_DELAY_CORRECTION(lpn))); |
| lpn_set_state(BT_MESH_LPN_WAIT_UPDATE); |
| bt_mesh_scan_enable(); |
| break; |
| case BT_MESH_LPN_WAIT_UPDATE: |
| update_timeout(lpn); |
| break; |
| default: |
| __ASSERT(0, "Unhandled LPN state"); |
| break; |
| } |
| } |
| |
| void bt_mesh_lpn_group_add(uint16_t group) |
| { |
| LOG_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(const uint16_t *groups, size_t group_count) |
| { |
| int i; |
| |
| for (i = 0; i < group_count; i++) { |
| if (groups[i] != BT_MESH_ADDR_UNASSIGNED) { |
| LOG_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); |
| } |
| |
| 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)) { |
| LOG_WRN("Too short Friend Subscription Confirm"); |
| return -EBADMSG; |
| } |
| |
| LOG_DBG("xact 0x%02x", msg->xact); |
| |
| if (!lpn->sent_req) { |
| LOG_WRN("No pending subscription list message"); |
| return 0; |
| } |
| |
| if (msg->xact != lpn->xact_pending) { |
| LOG_WRN("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 { |
| LOG_WRN("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(); |
| } |
| |
| 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; |
| uint32_t iv_index; |
| bool established = false; |
| |
| if (buf->len < sizeof(*msg)) { |
| LOG_WRN("Too short Friend Update"); |
| return -EBADMSG; |
| } |
| |
| if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) { |
| LOG_WRN("Unexpected friend update"); |
| return 0; |
| } |
| |
| if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !rx->new_key) { |
| LOG_WRN("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) { |
| /* This is normally checked on the transport layer, however |
| * in this state we're also still accepting flooding |
| * credentials so we need to ensure the right ones (Friend |
| * Credentials) were used for this message. |
| */ |
| if (!rx->friend_cred) { |
| LOG_WRN("Friend Update with wrong credentials"); |
| return -EINVAL; |
| } |
| |
| lpn->established = 1U; |
| |
| LOG_INF("Friendship established with 0x%04x", lpn->frnd); |
| |
| bt_mesh_hb_feature_changed(BT_MESH_FEAT_LOW_POWER); |
| |
| /* Set initial poll timeout */ |
| lpn->poll_timeout = MIN(POLL_TIMEOUT_MAX(lpn), |
| POLL_TIMEOUT_INIT); |
| |
| established = true; |
| } |
| |
| friend_response_received(lpn); |
| |
| iv_index = sys_be32_to_cpu(msg->iv_index); |
| |
| LOG_DBG("flags 0x%02x iv_index 0x%08x md %u", msg->flags, iv_index, msg->md); |
| |
| bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(msg->flags), rx->new_key); |
| 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 = 0U; |
| } |
| } |
| |
| if (msg->md) { |
| LOG_DBG("Requesting for more messages"); |
| send_friend_poll(); |
| } |
| |
| if (established) { |
| STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) { |
| if (cb->established) { |
| cb->established(lpn->sub->net_idx, lpn->frnd, lpn->queue_size, |
| lpn->recv_win); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int bt_mesh_lpn_poll(void) |
| { |
| if (!bt_mesh.lpn.established) { |
| return -EAGAIN; |
| } |
| |
| LOG_DBG("Requesting more messages"); |
| |
| return send_friend_poll(); |
| } |
| |
| static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) |
| { |
| switch (evt) { |
| case BT_MESH_KEY_DELETED: |
| if (sub == bt_mesh.lpn.sub) { |
| LOG_DBG("NetKey deleted"); |
| clear_friendship(true, false); |
| } |
| break; |
| case BT_MESH_KEY_UPDATED: |
| LOG_DBG("NetKey updated"); |
| friend_cred_create(&bt_mesh.lpn.cred[1], &sub->keys[1].net); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| BT_MESH_SUBNET_CB_DEFINE(lpn) = { |
| .evt_handler = subnet_evt, |
| }; |
| |
| int bt_mesh_lpn_init(void) |
| { |
| struct bt_mesh_lpn *lpn = &bt_mesh.lpn; |
| |
| LOG_DBG(""); |
| |
| k_work_init_delayable(&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)) { |
| LOG_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT); |
| lpn_set_state(BT_MESH_LPN_TIMER); |
| k_work_reschedule(&lpn->timer, |
| K_MSEC(LPN_AUTO_TIMEOUT)); |
| } |
| } |
| |
| return 0; |
| } |