Bluetooth: Mesh: add implementation for Proxy Solicitation

This is initial implementation of Proxy solicitation procedure.
This includes:
- support for sending and receiving Solicitation PDUs
- On-Demand Private Proxy functionality (Server and Client) controlling
  behaviour of node after receiving Solicitation PDU
- Solicitation PDU RPL Configuration (Server and Client), which manages
  Replay Protection List for Solicitation PDUs.

Proxy Solicitation allows to enable advertising of Proxy service on node
by sending Solicitation PDUs. These PDUs are not part of Mesh messages;
instead, these are non-connectable, undirected advertising PDUs with
their own format, containing Proxy Solicitation UUID.

Signed-off-by: Krzysztof Kopyściński <krzysztof.kopyscinski@codecoup.pl>
This commit is contained in:
Krzysztof Kopyściński 2022-10-19 09:42:02 +02:00 committed by Carles Cufí
commit d0995541fb
40 changed files with 1851 additions and 75 deletions

View file

@ -0,0 +1,542 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/mesh.h>
#include <zephyr/sys/byteorder.h>
#include <stdlib.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include "access.h"
#include "adv.h"
#include "cfg.h"
#include "crypto.h"
#include "mesh.h"
#include "net.h"
#include "proxy.h"
#include "settings.h"
#include "common/bt_str.h"
#include "host/hci_core.h"
#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_solicitation);
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
static struct srpl_entry {
uint32_t sseq;
uint16_t ssrc;
} sol_pdu_rpl[CONFIG_BT_MESH_PROXY_SRPL_SIZE];
static ATOMIC_DEFINE(store, CONFIG_BT_MESH_PROXY_SRPL_SIZE);
static atomic_t clear;
#endif
#if CONFIG_BT_MESH_PROXY_SOLICITATION
static uint32_t sseq_out;
#endif
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
static struct srpl_entry *srpl_find_by_addr(uint16_t ssrc)
{
int i;
for (i = 0; i < ARRAY_SIZE(sol_pdu_rpl); i++) {
if (sol_pdu_rpl[i].ssrc == ssrc) {
return &sol_pdu_rpl[i];
}
}
return NULL;
}
static int srpl_entry_save(struct bt_mesh_subnet *sub, uint32_t sseq, uint16_t ssrc)
{
struct srpl_entry *entry;
if (!BT_MESH_ADDR_IS_UNICAST(ssrc)) {
LOG_DBG("Addr not in unicast range");
return -EINVAL;
}
entry = srpl_find_by_addr(ssrc);
if (entry) {
if (entry->sseq >= sseq && sseq != 0) {
LOG_WRN("Higher or equal SSEQ already saved for this SSRC");
return -EALREADY;
}
} else {
entry = srpl_find_by_addr(BT_MESH_ADDR_UNASSIGNED);
if (!entry) {
/* No space to save new PDU in RPL for this SSRC
* and this PDU is first for this SSRC
*/
return -ENOMEM;
}
}
entry->sseq = sseq;
entry->ssrc = ssrc;
LOG_DBG("Added: SSRC %d SSEQ %d to SRPL", entry->ssrc, entry->sseq);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
atomic_set_bit(store, entry - &sol_pdu_rpl[0]);
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SRPL_PENDING);
}
return 0;
}
#endif
void bt_mesh_sseq_pending_store(void)
{
#if CONFIG_BT_MESH_PROXY_SOLICITATION
char *path = "bt/mesh/SSeq";
int err;
if (sseq_out) {
err = settings_save_one(path, &sseq_out, sizeof(sseq_out));
} else {
err = settings_delete(path);
}
if (err) {
LOG_ERR("Failed to %s SSeq %s value", (sseq_out == 0 ? "delete" : "store"), path);
} else {
LOG_DBG("%s %s value", (sseq_out == 0 ? "Deleted" : "Stored"), path);
}
#endif
}
#if CONFIG_BT_MESH_PROXY_SOLICITATION
static int sseq_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
int err;
err = bt_mesh_settings_set(read_cb, cb_arg, &sseq_out, sizeof(sseq_out));
if (err) {
LOG_ERR("Failed to set \'sseq\'");
return err;
}
LOG_DBG("Restored SSeq value 0x%06x", sseq_out);
return 0;
}
BT_MESH_SETTINGS_DEFINE(sseq, "SSeq", sseq_set);
#endif
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
static bool sol_pdu_decrypt(struct bt_mesh_subnet *sub, void *data)
{
struct net_buf_simple *in = data;
struct net_buf_simple *out = NET_BUF_SIMPLE(17);
int err, i;
uint32_t sseq;
uint16_t ssrc;
for (i = 0; i < ARRAY_SIZE(sub->keys); i++) {
if (!sub->keys[i].valid) {
LOG_ERR("invalid keys %d", i);
continue;
}
net_buf_simple_init(out, 0);
net_buf_simple_add_mem(out, in->data, in->len);
err = bt_mesh_net_obfuscate(out->data, 0, sub->keys[i].msg.privacy);
if (err) {
LOG_DBG("obfuscation err %d", err);
continue;
}
err = bt_mesh_net_decrypt(sub->keys[i].msg.enc, out,
0, BT_MESH_NONCE_SOLICITATION);
if (!err) {
LOG_DBG("Decrypted PDU %s", bt_hex(out->data, out->len));
memcpy(&sseq, &out->data[2], 3);
memcpy(&ssrc, &out->data[5], 2);
err = srpl_entry_save(sub,
sys_be24_to_cpu(sseq),
sys_be16_to_cpu(ssrc));
return err ? false : true;
}
LOG_DBG("decrypt err %d", err);
}
return false;
}
#endif
void bt_mesh_sol_recv(struct net_buf_simple *buf, uint8_t uuid_list_len)
{
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
uint8_t type;
struct bt_mesh_subnet *sub;
uint16_t uuid;
uint8_t reported_len;
uint8_t svc_data_type;
bool sol_uuid_found = false;
bool svc_data_found = false;
if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED ||
bt_mesh_priv_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED ||
bt_mesh_od_priv_proxy_get() == 0) {
return;
}
/* Get rid of ad_type that was checked in bt_mesh_scan_cb */
type = net_buf_simple_pull_u8(buf);
if (type != BT_DATA_UUID16_SOME && type != BT_DATA_UUID16_ALL) {
LOG_ERR("Invalid type 0x%x, expected 0x%x or 0x%x",
type, BT_DATA_UUID16_SOME, BT_DATA_UUID16_ALL);
return;
}
if (buf->len < 24) {
LOG_DBG("Invalid length (%u) Solicitation PDU", buf->len);
return;
}
while (uuid_list_len >= 2) {
uuid = net_buf_simple_pull_le16(buf);
if (uuid == BT_UUID_MESH_PROXY_SOLICITATION_VAL) {
sol_uuid_found = true;
}
uuid_list_len -= 2;
}
if (!sol_uuid_found) {
return;
}
while (buf->len >= 22) {
reported_len = net_buf_simple_pull_u8(buf);
svc_data_type = net_buf_simple_pull_u8(buf);
uuid = net_buf_simple_pull_le16(buf);
if (reported_len == 21 && svc_data_type == BT_DATA_SVC_DATA16 &&
uuid == BT_UUID_MESH_PROXY_SOLICITATION_VAL) {
svc_data_found = true;
break;
}
if (buf->len <= reported_len - 3) {
return;
}
net_buf_simple_pull_mem(buf, reported_len - 3);
}
if (!svc_data_found) {
return;
}
type = net_buf_simple_pull_u8(buf);
if (type != 0) {
LOG_ERR("Invalid type %d, expected 0x00", type);
return;
}
sub = bt_mesh_subnet_find(sol_pdu_decrypt, (void *)buf);
if (!sub) {
LOG_DBG("Unable to find subnetwork for received solicitation PDU");
return;
}
LOG_DBG("Decrypted solicitation PDU for existing subnet");
sub->solicited = true;
bt_mesh_adv_gatt_update();
#endif
}
int bt_mesh_proxy_solicit(uint16_t net_idx)
{
#if CONFIG_BT_MESH_PROXY_SOLICITATION
struct bt_mesh_subnet *sub;
sub = bt_mesh_subnet_get(net_idx);
if (!sub) {
LOG_ERR("No subnet with net_idx %d", net_idx);
return -EINVAL;
}
if (sub->sol_tx == true) {
LOG_ERR("Solicitation already scheduled for this subnet");
return -EALREADY;
}
/* SSeq reached its maximum value */
if (sseq_out > 0xFFFFFF) {
LOG_ERR("SSeq out of range");
return -EOVERFLOW;
}
sub->sol_tx = true;
bt_mesh_adv_gatt_update();
return 0;
#else
return -ENOTSUP;
#endif
}
#if CONFIG_BT_MESH_PROXY_SOLICITATION
static int sol_pdu_create(struct bt_mesh_subnet *sub, struct net_buf_simple *pdu)
{
int err;
net_buf_simple_add_u8(pdu, sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.nid);
/* CTL = 1, TTL = 0 */
net_buf_simple_add_u8(pdu, 0x80);
net_buf_simple_add_le24(pdu, sys_cpu_to_be24(sseq_out));
net_buf_simple_add_le16(pdu, sys_cpu_to_be16(bt_mesh_primary_addr()));
/* DST = 0x0000 */
net_buf_simple_add_le16(pdu, 0x0000);
err = bt_mesh_net_encrypt(sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.enc,
pdu, 0, BT_MESH_NONCE_SOLICITATION);
if (err) {
LOG_ERR("Encryption failed, err=%d", err);
return err;
}
err = bt_mesh_net_obfuscate(pdu->data, 0,
sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.privacy);
if (err) {
LOG_ERR("Obfuscation failed, err=%d", err);
return err;
}
net_buf_simple_push_u8(pdu, 0);
net_buf_simple_push_le16(pdu, BT_UUID_MESH_PROXY_SOLICITATION_VAL);
return 0;
}
#endif
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
static int srpl_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct srpl_entry *entry;
int err;
uint16_t ssrc;
uint32_t sseq;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -ENOENT;
}
ssrc = strtol(name, NULL, 16);
entry = srpl_find_by_addr(ssrc);
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", ssrc);
}
return 0;
}
if (!entry) {
entry = srpl_find_by_addr(BT_MESH_ADDR_UNASSIGNED);
if (!entry) {
LOG_ERR("Unable to allocate SRPL entry for 0x%04x", ssrc);
return -ENOMEM;
}
}
err = bt_mesh_settings_set(read_cb, cb_arg, &sseq, sizeof(sseq));
if (err) {
LOG_ERR("Failed to set \'sseq\'");
return err;
}
entry->ssrc = ssrc;
entry->sseq = sseq;
LOG_DBG("SRPL entry for 0x%04x: Seq 0x%06x", entry->ssrc,
entry->sseq);
return 0;
}
BT_MESH_SETTINGS_DEFINE(srpl, "SRPL", srpl_set);
#endif
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
static void srpl_entry_clear(int i)
{
uint16_t addr = sol_pdu_rpl[i].ssrc;
LOG_DBG("Removing entry SSRC: %d, SSEQ: %d from RPL",
sol_pdu_rpl[i].ssrc,
sol_pdu_rpl[i].sseq);
sol_pdu_rpl[i].ssrc = 0;
sol_pdu_rpl[i].sseq = 0;
atomic_clear_bit(store, i);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
char path[18];
snprintk(path, sizeof(path), "bt/mesh/SRPL/%x", addr);
settings_delete(path);
}
}
static void srpl_store(struct srpl_entry *entry)
{
char path[18];
int err;
LOG_DBG("src 0x%04x seq 0x%06x", entry->ssrc, entry->sseq);
snprintk(path, sizeof(path), "bt/mesh/SRPL/%x", entry->ssrc);
err = settings_save_one(path, &entry->sseq, sizeof(entry->sseq));
if (err) {
LOG_ERR("Failed to store RPL %s value", path);
} else {
LOG_DBG("Stored RPL %s value", path);
}
}
#endif
void bt_mesh_srpl_pending_store(void)
{
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
bool clr;
clr = atomic_cas(&clear, 1, 0);
for (int i = 0; i < ARRAY_SIZE(sol_pdu_rpl); i++) {
LOG_DBG("src 0x%04x seq 0x%06x", sol_pdu_rpl[i].ssrc, sol_pdu_rpl[i].sseq);
if (clr) {
srpl_entry_clear(i);
} else if (atomic_test_and_clear_bit(store, i)) {
srpl_store(&sol_pdu_rpl[i]);
}
}
#endif
}
void bt_mesh_srpl_entry_clear(uint16_t addr)
{
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
struct srpl_entry *entry;
if (!BT_MESH_ADDR_IS_UNICAST(addr)) {
LOG_DBG("Addr not in unicast range");
return;
}
entry = srpl_find_by_addr(addr);
if (!entry) {
return;
}
srpl_entry_clear(entry - &sol_pdu_rpl[0]);
#endif
}
void bt_mesh_sol_reset(void)
{
#if CONFIG_BT_MESH_PROXY_SOLICITATION
sseq_out = 0;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING);
#endif
#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV
(void)atomic_cas(&clear, 0, 1);
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SRPL_PENDING);
#endif
}
#if CONFIG_BT_MESH_PROXY_SOLICITATION
static bool sol_subnet_find(struct bt_mesh_subnet *sub, void *cb_data)
{
return sub->sol_tx;
}
#endif
int bt_mesh_sol_send(void)
{
#if CONFIG_BT_MESH_PROXY_SOLICITATION
uint16_t adv_int;
struct bt_mesh_subnet *sub;
int err;
NET_BUF_SIMPLE_DEFINE(pdu, 20);
sub = bt_mesh_subnet_find(sol_subnet_find, NULL);
if (!sub) {
return -ENOENT;
}
/* SSeq reached its maximum value */
if (sseq_out > 0xFFFFFF) {
LOG_ERR("SSeq out of range");
sub->sol_tx = false;
return -EOVERFLOW;
}
net_buf_simple_init(&pdu, 3);
adv_int = BT_MESH_TRANSMIT_INT(CONFIG_BT_MESH_SOL_ADV_XMIT);
err = sol_pdu_create(sub, &pdu);
if (err) {
LOG_ERR("Failed to create Solicitation PDU, err=%d", err);
return err;
}
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS,
(BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL,
BT_UUID_16_ENCODE(
BT_UUID_MESH_PROXY_SOLICITATION_VAL)),
BT_DATA(BT_DATA_SVC_DATA16, pdu.data, pdu.size),
};
err = bt_mesh_adv_bt_data_send(CONFIG_BT_MESH_SOL_ADV_XMIT,
adv_int, ad, 3);
if (err) {
LOG_ERR("Failed to advertise Solicitation PDU, err=%d", err);
sub->sol_tx = false;
return err;
}
sub->sol_tx = false;
sseq_out++;
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING);
return 0;
#else
return -ENOTSUP;
#endif
}