/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #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 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; if (IS_ENABLED(CONFIG_BT_SETTINGS)) { bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING); } #endif #if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV if (IS_ENABLED(CONFIG_BT_SETTINGS)) { (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++; if (IS_ENABLED(CONFIG_BT_SETTINGS)) { bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING); } return 0; #else return -ENOTSUP; #endif }