| /* |
| * 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 "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) { |
| 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) { |
| LOG_DBG("Not soliciting"); |
| 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_DBG("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) { |
| LOG_DBG("No solicitation 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) { |
| LOG_DBG("Invalid length (%u) Solicitation PDU", buf->len); |
| return; |
| } |
| |
| net_buf_simple_pull_mem(buf, reported_len - 3); |
| } |
| |
| if (!svc_data_found) { |
| LOG_DBG("No solicitation service data found"); |
| return; |
| } |
| |
| type = net_buf_simple_pull_u8(buf); |
| if (type != 0) { |
| LOG_DBG("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 |
| } |