blob: 2170a94667182ea9bc3f10f78ff9105e2e200abd [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2020 Lingao Meng
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/mesh.h>
#include "mesh.h"
#include "net.h"
#include "rpl.h"
#include "settings.h"
#define LOG_LEVEL CONFIG_BT_MESH_RPL_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_rpl);
/* Replay Protection List information for persistent storage. */
struct rpl_val {
uint32_t seq:24,
old_iv:1;
};
static struct bt_mesh_rpl replay_list[CONFIG_BT_MESH_CRPL];
static ATOMIC_DEFINE(store, CONFIG_BT_MESH_CRPL);
enum {
PENDING_CLEAR,
PENDING_RESET,
RPL_FLAGS_COUNT,
};
static ATOMIC_DEFINE(rpl_flags, RPL_FLAGS_COUNT);
static inline int rpl_idx(const struct bt_mesh_rpl *rpl)
{
return rpl - &replay_list[0];
}
static void clear_rpl(struct bt_mesh_rpl *rpl)
{
int err;
char path[18];
if (!rpl->src) {
return;
}
atomic_clear_bit(store, rpl_idx(rpl));
snprintk(path, sizeof(path), "bt/mesh/RPL/%x", rpl->src);
err = settings_delete(path);
if (err) {
LOG_ERR("Failed to clear RPL");
} else {
LOG_DBG("Cleared RPL");
}
}
static void schedule_rpl_store(struct bt_mesh_rpl *entry, bool force)
{
atomic_set_bit(store, rpl_idx(entry));
if (force
#ifdef CONFIG_BT_MESH_RPL_STORE_TIMEOUT
|| CONFIG_BT_MESH_RPL_STORE_TIMEOUT >= 0
#endif
) {
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
}
void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl,
struct bt_mesh_net_rx *rx)
{
/* If this is the first message on the new IV index, we should reset it
* to zero to avoid invalid combinations of IV index and seg.
*/
if (rpl->old_iv && !rx->old_iv) {
rpl->seg = 0;
}
rpl->src = rx->ctx.addr;
rpl->seq = rx->seq;
rpl->old_iv = rx->old_iv;
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_store(rpl, false);
}
}
/* Check the Replay Protection List for a replay attempt. If non-NULL match
* parameter is given the RPL slot is returned, but it is not immediately
* updated. This is used to prevent storing data in RPL that has been rejected
* by upper logic (access, transport commands) and for receiving the segmented messages.
* If a NULL match is given the RPL is immediately updated (used for proxy configuration).
*/
bool bt_mesh_rpl_check(struct bt_mesh_net_rx *rx, struct bt_mesh_rpl **match)
{
struct bt_mesh_rpl *rpl;
int i;
/* Don't bother checking messages from ourselves */
if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
return false;
}
/* The RPL is used only for the local node */
if (!rx->local_match) {
return false;
}
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
rpl = &replay_list[i];
/* Empty slot */
if (!rpl->src) {
goto match;
}
/* Existing slot for given address */
if (rpl->src == rx->ctx.addr) {
if (!rpl->old_iv &&
atomic_test_bit(rpl_flags, PENDING_RESET) &&
!atomic_test_bit(store, i)) {
/* Until rpl reset is finished, entry with old_iv == false and
* without "store" bit set will be removed, therefore it can be
* reused. If such entry is reused, "store" bit will be set and
* the entry won't be removed.
*/
goto match;
}
if (rx->old_iv && !rpl->old_iv) {
return true;
}
if ((!rx->old_iv && rpl->old_iv) ||
rpl->seq < rx->seq) {
goto match;
} else {
return true;
}
}
}
LOG_ERR("RPL is full!");
return true;
match:
if (match) {
*match = rpl;
} else {
bt_mesh_rpl_update(rpl, rx);
}
return false;
}
void bt_mesh_rpl_clear(void)
{
LOG_DBG("");
if (!IS_ENABLED(CONFIG_BT_SETTINGS)) {
(void)memset(replay_list, 0, sizeof(replay_list));
return;
}
atomic_set_bit(rpl_flags, PENDING_CLEAR);
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
static struct bt_mesh_rpl *bt_mesh_rpl_find(uint16_t src)
{
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (replay_list[i].src == src) {
return &replay_list[i];
}
}
return NULL;
}
static struct bt_mesh_rpl *bt_mesh_rpl_alloc(uint16_t src)
{
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (!replay_list[i].src) {
replay_list[i].src = src;
return &replay_list[i];
}
}
return NULL;
}
void bt_mesh_rpl_reset(void)
{
/* Discard "old old" IV Index entries from RPL and flag
* any other ones (which are valid) as old.
*/
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
struct bt_mesh_rpl *rpl = &replay_list[i];
if (!rpl->src) {
continue;
}
/* Entries with "store" bit set will be stored, other entries will be
* removed.
*/
atomic_set_bit_to(store, i, !rpl->old_iv);
rpl->old_iv = !rpl->old_iv;
}
if (i != 0) {
atomic_set_bit(rpl_flags, PENDING_RESET);
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
} else {
int shift = 0;
int last = 0;
for (int i = 0; i < ARRAY_SIZE(replay_list); i++) {
struct bt_mesh_rpl *rpl = &replay_list[i];
if (rpl->src) {
if (rpl->old_iv) {
(void)memset(rpl, 0, sizeof(*rpl));
shift++;
} else {
rpl->old_iv = true;
if (shift > 0) {
replay_list[i - shift] = *rpl;
}
}
last = i;
}
}
(void)memset(&replay_list[last - shift + 1], 0, sizeof(struct bt_mesh_rpl) * shift);
}
}
static int rpl_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct bt_mesh_rpl *entry;
struct rpl_val rpl;
int err;
uint16_t src;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
src = strtol(name, NULL, 16);
entry = bt_mesh_rpl_find(src);
if (len_rd == 0) {
LOG_DBG("val (null)");
if (entry) {
(void)memset(entry, 0, sizeof(*entry));
} else {
LOG_WRN("Unable to find RPL entry for 0x%04x", src);
}
return 0;
}
if (!entry) {
entry = bt_mesh_rpl_alloc(src);
if (!entry) {
LOG_ERR("Unable to allocate RPL entry for 0x%04x", src);
return -ENOMEM;
}
}
err = bt_mesh_settings_set(read_cb, cb_arg, &rpl, sizeof(rpl));
if (err) {
LOG_ERR("Failed to set `net`");
return err;
}
entry->seq = rpl.seq;
entry->old_iv = rpl.old_iv;
LOG_DBG("RPL entry for 0x%04x: Seq 0x%06x old_iv %u", entry->src, entry->seq,
entry->old_iv);
return 0;
}
BT_MESH_SETTINGS_DEFINE(rpl, "RPL", rpl_set);
static void store_rpl(struct bt_mesh_rpl *entry)
{
struct rpl_val rpl = {0};
char path[18];
int err;
if (!entry->src) {
return;
}
LOG_DBG("src 0x%04x seq 0x%06x old_iv %u", entry->src, entry->seq, entry->old_iv);
rpl.seq = entry->seq;
rpl.old_iv = entry->old_iv;
snprintk(path, sizeof(path), "bt/mesh/RPL/%x", entry->src);
err = settings_save_one(path, &rpl, sizeof(rpl));
if (err) {
LOG_ERR("Failed to store RPL %s value", path);
} else {
LOG_DBG("Stored RPL %s value", path);
}
}
void bt_mesh_rpl_pending_store(uint16_t addr)
{
int shift = 0;
int last = 0;
bool clr;
bool rst;
if (!IS_ENABLED(CONFIG_BT_SETTINGS) ||
(!BT_MESH_ADDR_IS_UNICAST(addr) &&
addr != BT_MESH_ADDR_ALL_NODES)) {
return;
}
if (addr == BT_MESH_ADDR_ALL_NODES) {
bt_mesh_settings_store_cancel(BT_MESH_SETTINGS_RPL_PENDING);
}
clr = atomic_test_and_clear_bit(rpl_flags, PENDING_CLEAR);
rst = atomic_test_bit(rpl_flags, PENDING_RESET);
for (int i = 0; i < ARRAY_SIZE(replay_list); i++) {
struct bt_mesh_rpl *rpl = &replay_list[i];
if (addr != BT_MESH_ADDR_ALL_NODES && addr != rpl->src) {
continue;
}
if (clr) {
clear_rpl(rpl);
shift++;
} else if (atomic_test_and_clear_bit(store, i)) {
if (shift > 0) {
replay_list[i - shift] = *rpl;
}
store_rpl(&replay_list[i - shift]);
} else if (rst) {
clear_rpl(rpl);
/* Check if this entry was re-used during removal. If so, shift it as well.
* Otherwise, increment shift counter.
*/
if (atomic_test_and_clear_bit(store, i)) {
replay_list[i - shift] = *rpl;
atomic_set_bit(store, i - shift);
} else {
shift++;
}
}
last = i;
if (addr != BT_MESH_ADDR_ALL_NODES) {
break;
}
}
atomic_clear_bit(rpl_flags, PENDING_RESET);
if (addr == BT_MESH_ADDR_ALL_NODES) {
(void)memset(&replay_list[last - shift + 1], 0, sizeof(struct bt_mesh_rpl) * shift);
}
}