/* * Copyright (c) 2020 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include "access.h" #include "prov.h" #include "crypto.h" #include "rpr.h" #include "net.h" #include "mesh.h" #define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_mesh_rpr_srv); #define LINK_OPEN_TIMEOUT_DEFAULT 10 #define LINK_CTX(_cli, _send_rel) \ { \ .net_idx = (_cli)->net_idx, .app_idx = BT_MESH_KEY_DEV_LOCAL, \ .addr = (_cli)->addr, .send_ttl = (_cli)->ttl, \ .send_rel = (_send_rel) \ } enum { SCANNING, SCAN_REPORT_PENDING, SCAN_EXT_HAS_ADDR, NODE_REFRESH, URI_MATCHED, URI_REQUESTED, RPR_SRV_NUM_FLAGS, }; /** Remote provisioning server instance. */ static struct { const struct bt_mesh_model *mod; ATOMIC_DEFINE(flags, RPR_SRV_NUM_FLAGS); struct { struct bt_mesh_rpr_unprov devs[CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX]; uint8_t max_devs; enum bt_mesh_rpr_scan state; struct k_work_delayable report; struct k_work_delayable timeout; /* Extended scanning */ bt_addr_le_t addr; uint8_t ad[CONFIG_BT_MESH_RPR_AD_TYPES_MAX]; uint8_t ad_count; /* Time to do regular scanning after extended scanning ends: */ uint32_t additional_time; struct net_buf_simple *adv_data; struct bt_mesh_rpr_node cli; struct bt_mesh_rpr_unprov *dev; } scan; struct { struct k_work report; enum bt_mesh_rpr_link_state state; enum bt_mesh_rpr_status status; uint8_t close_reason; uint8_t tx_pdu; uint8_t rx_pdu; struct bt_mesh_rpr_node cli; struct bt_mesh_rpr_unprov *dev; } link; struct { const struct prov_bearer_cb *cb; enum bt_mesh_rpr_node_refresh procedure; void *cb_data; struct { prov_bearer_send_complete_t cb; void *cb_data; } tx; } refresh; } srv = { .scan = { .adv_data = NET_BUF_SIMPLE(CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX) } }; enum bt_mesh_rpr_node_refresh bt_mesh_node_refresh_get(void) { return srv.refresh.procedure; } static struct bt_mesh_rpr_unprov *unprov_get(const uint8_t uuid[16]) { int i; for (i = 0; i < srv.scan.max_devs; ++i) { if (uuid) { if ((srv.scan.devs[i].flags & BT_MESH_RPR_UNPROV_ACTIVE) && !memcmp(srv.scan.devs[i].uuid, uuid, 16)) { return &srv.scan.devs[i]; } } else if (!(srv.scan.devs[i].flags & BT_MESH_RPR_UNPROV_ACTIVE)) { return &srv.scan.devs[i]; } } return NULL; } static uint8_t *get_ad_type(uint8_t *list, size_t count, uint8_t ad) { int i; for (i = 0; i < count; ++i) { if (ad == list[i] || (ad == BT_DATA_NAME_SHORTENED && list[i] == BT_DATA_NAME_COMPLETE)) { return &list[i]; } } return NULL; } static void cli_scan_clear(void) { srv.scan.cli.addr = BT_MESH_ADDR_UNASSIGNED; srv.scan.cli.net_idx = BT_MESH_KEY_UNUSED; } static void cli_link_clear(void) { srv.link.cli.addr = BT_MESH_ADDR_UNASSIGNED; srv.link.cli.net_idx = BT_MESH_KEY_UNUSED; } static void scan_status_send(struct bt_mesh_msg_ctx *ctx, enum bt_mesh_rpr_status status) { uint8_t timeout = 0; if (atomic_test_bit(srv.flags, SCANNING)) { timeout = k_ticks_to_ms_floor32( k_work_delayable_remaining_get(&srv.scan.timeout)) / MSEC_PER_SEC; } BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_SCAN_STATUS, 4); bt_mesh_model_msg_init(&rsp, RPR_OP_SCAN_STATUS); net_buf_simple_add_u8(&rsp, status); net_buf_simple_add_u8(&rsp, srv.scan.state); net_buf_simple_add_u8(&rsp, srv.scan.max_devs); net_buf_simple_add_u8(&rsp, timeout); bt_mesh_model_send(srv.mod, ctx, &rsp, NULL, NULL); } static void link_status_send(struct bt_mesh_msg_ctx *ctx, enum bt_mesh_rpr_status status) { BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_STATUS, 2); bt_mesh_model_msg_init(&buf, RPR_OP_LINK_STATUS); net_buf_simple_add_u8(&buf, status); net_buf_simple_add_u8(&buf, srv.link.state); bt_mesh_model_send(srv.mod, ctx, &buf, NULL, NULL); } static void link_report_send(void) { struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_REPORT, 3); bt_mesh_model_msg_init(&buf, RPR_OP_LINK_REPORT); net_buf_simple_add_u8(&buf, srv.link.status); net_buf_simple_add_u8(&buf, srv.link.state); if (srv.link.status == BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER || srv.link.status == BT_MESH_RPR_ERR_LINK_CLOSED_BY_DEVICE) { net_buf_simple_add_u8(&buf, srv.link.close_reason); } LOG_DBG("%u %u", srv.link.status, srv.link.state); bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); } static void scan_report_schedule(void) { uint32_t delay = 0; if (k_work_delayable_remaining_get(&srv.scan.report) || atomic_test_bit(srv.flags, SCAN_REPORT_PENDING)) { return; } (void)bt_rand(&delay, sizeof(uint32_t)); delay = (delay % 480) + 20; k_work_reschedule(&srv.scan.report, K_MSEC(delay)); } static void scan_report_sent(int err, void *cb_data) { atomic_clear_bit(srv.flags, SCAN_REPORT_PENDING); k_work_reschedule(&srv.scan.report, K_NO_WAIT); } static const struct bt_mesh_send_cb report_cb = { .end = scan_report_sent, }; static void scan_report_send(void) { struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.scan.cli, true); int i, err; if (atomic_test_bit(srv.flags, SCAN_REPORT_PENDING)) { return; } for (i = 0; i < srv.scan.max_devs; ++i) { struct bt_mesh_rpr_unprov *dev = &srv.scan.devs[i]; if (!(dev->flags & BT_MESH_RPR_UNPROV_FOUND) || (dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) { continue; } BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_REPORT, 23); bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_REPORT); net_buf_simple_add_u8(&buf, dev->rssi); net_buf_simple_add_mem(&buf, dev->uuid, 16); net_buf_simple_add_le16(&buf, dev->oob); if (dev->flags & BT_MESH_RPR_UNPROV_HASH) { net_buf_simple_add_mem(&buf, &dev->hash, 4); } atomic_set_bit(srv.flags, SCAN_REPORT_PENDING); err = bt_mesh_model_send(srv.mod, &ctx, &buf, &report_cb, NULL); if (err) { atomic_clear_bit(srv.flags, SCAN_REPORT_PENDING); LOG_DBG("tx failed: %d", err); break; } LOG_DBG("Reported unprov #%u", i); dev->flags |= BT_MESH_RPR_UNPROV_REPORTED; break; } } static void scan_ext_report_send(void) { struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.scan.cli, true); int err; BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_EXTENDED_SCAN_REPORT, 19 + CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX); bt_mesh_model_msg_init(&buf, RPR_OP_EXTENDED_SCAN_REPORT); net_buf_simple_add_u8(&buf, BT_MESH_RPR_SUCCESS); net_buf_simple_add_mem(&buf, srv.scan.dev->uuid, 16); if (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_FOUND) { net_buf_simple_add_le16(&buf, srv.scan.dev->oob); } else { LOG_DBG("not found"); goto send; } if (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_EXT_ADV_RXD) { net_buf_simple_add_mem(&buf, srv.scan.adv_data->data, srv.scan.adv_data->len); LOG_DBG("adv data: %s", bt_hex(srv.scan.adv_data->data, srv.scan.adv_data->len)); } srv.scan.dev->flags &= ~BT_MESH_RPR_UNPROV_EXT_ADV_RXD; send: err = bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); if (!err) { srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_REPORTED; } } static void scan_stop(void) { LOG_DBG(""); k_work_cancel_delayable(&srv.scan.report); k_work_cancel_delayable(&srv.scan.timeout); srv.scan.state = BT_MESH_RPR_SCAN_IDLE; cli_scan_clear(); atomic_clear_bit(srv.flags, SCANNING); } static void scan_report_timeout(struct k_work *work) { scan_report_send(); } static void scan_ext_stop(uint32_t remaining_time) { atomic_clear_bit(srv.flags, URI_MATCHED); atomic_clear_bit(srv.flags, URI_REQUESTED); if ((remaining_time + srv.scan.additional_time) && srv.scan.state != BT_MESH_RPR_SCAN_IDLE) { k_work_reschedule( &srv.scan.timeout, K_MSEC(remaining_time + srv.scan.additional_time)); } else if (srv.scan.state == BT_MESH_RPR_SCAN_MULTI) { /* Extended scan might have finished early */ scan_ext_report_send(); } else if (srv.scan.state != BT_MESH_RPR_SCAN_IDLE) { scan_report_send(); scan_stop(); } else { atomic_clear_bit(srv.flags, SCANNING); } if (!(srv.scan.dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) { scan_ext_report_send(); } bt_mesh_scan_active_set(false); srv.scan.dev = NULL; } static void adv_handle_ext_scan(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf); static void scan_timeout(struct k_work *work) { LOG_DBG("%s", (srv.scan.dev ? "Extended scanning" : "Normal scanning")); if (srv.scan.dev) { scan_ext_stop(0); } else { scan_report_send(); scan_stop(); } } static void link_close(enum bt_mesh_rpr_status status, enum prov_bearer_link_status reason) { srv.link.status = status; srv.link.close_reason = reason; srv.link.state = BT_MESH_RPR_LINK_CLOSING; LOG_DBG("status: %u reason: %u", status, reason); if (atomic_test_and_clear_bit(srv.flags, NODE_REFRESH)) { /* Link closing is an atomic operation: */ srv.link.state = BT_MESH_RPR_LINK_IDLE; link_report_send(); srv.refresh.cb->link_closed(&pb_remote_srv, srv.refresh.cb_data, srv.link.close_reason); cli_link_clear(); } else { bt_mesh_pb_adv.link_close(reason); } } static void outbound_pdu_report_send(void) { struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_PDU_OUTBOUND_REPORT, 1); bt_mesh_model_msg_init(&buf, RPR_OP_PDU_OUTBOUND_REPORT); net_buf_simple_add_u8(&buf, srv.link.tx_pdu); LOG_DBG("%u", srv.link.tx_pdu); bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); } static void pdu_send_complete(int err, void *cb_data) { if (err) { link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, PROV_BEARER_LINK_STATUS_FAIL); } else if (srv.link.state == BT_MESH_RPR_LINK_SENDING) { srv.link.state = BT_MESH_RPR_LINK_ACTIVE; srv.link.tx_pdu++; outbound_pdu_report_send(); } } static int inbound_pdu_send(struct net_buf_simple *buf, const struct bt_mesh_send_cb *cb) { struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); BT_MESH_MODEL_BUF_DEFINE(msg, RPR_OP_PDU_REPORT, 66); bt_mesh_model_msg_init(&msg, RPR_OP_PDU_REPORT); net_buf_simple_add_u8(&msg, srv.link.rx_pdu); net_buf_simple_add_mem(&msg, buf->data, buf->len); return bt_mesh_model_send(srv.mod, &ctx, &msg, cb, NULL); } static void subnet_evt_handler(struct bt_mesh_subnet *subnet, enum bt_mesh_key_evt evt) { if (!srv.mod || evt != BT_MESH_KEY_DELETED) { return; } LOG_DBG("Subnet deleted"); if (srv.link.state != BT_MESH_RPR_LINK_IDLE && subnet->net_idx == srv.link.cli.net_idx) { link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER, PROV_BEARER_LINK_STATUS_FAIL); /* Skip the link closing stage, as specified in the Bluetooth * MshPRTv1.1: 4.4.5.4. */ srv.link.state = BT_MESH_RPR_LINK_IDLE; } else if (atomic_test_bit(srv.flags, SCANNING) && subnet->net_idx == srv.scan.cli.net_idx) { scan_stop(); } } BT_MESH_SUBNET_CB_DEFINE(rpr_srv) = { .evt_handler = subnet_evt_handler }; /******************************************************************************* * Prov bearer interface ******************************************************************************/ static void pb_link_opened(const struct prov_bearer *bearer, void *cb_data) { LOG_DBG(""); srv.link.state = BT_MESH_RPR_LINK_ACTIVE; srv.link.status = BT_MESH_RPR_SUCCESS; link_report_send(); } static void link_report_send_and_clear(struct k_work *work) { link_report_send(); cli_link_clear(); } static void pb_link_closed(const struct prov_bearer *bearer, void *cb_data, enum prov_bearer_link_status reason) { if (srv.link.state == BT_MESH_RPR_LINK_IDLE) { return; } LOG_DBG("%u", reason); if (srv.link.state == BT_MESH_RPR_LINK_OPENING) { srv.link.status = BT_MESH_RPR_ERR_LINK_OPEN_FAILED; } else if (reason == PROV_BEARER_LINK_STATUS_TIMEOUT) { if (srv.link.state == BT_MESH_RPR_LINK_SENDING) { srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU; } else { srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER; } } else if (reason == PROV_BEARER_LINK_STATUS_FAIL && srv.link.status != BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT && srv.link.status != BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER) { srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_BY_DEVICE; } if (reason == PROV_BEARER_LINK_STATUS_SUCCESS) { srv.link.close_reason = PROV_BEARER_LINK_STATUS_SUCCESS; } else { srv.link.close_reason = PROV_BEARER_LINK_STATUS_FAIL; } srv.link.state = BT_MESH_RPR_LINK_IDLE; k_work_submit(&srv.link.report); } static void pb_error(const struct prov_bearer *bearer, void *cb_data, uint8_t err) { if (srv.link.state == BT_MESH_RPR_LINK_IDLE) { return; } LOG_DBG("%d", err); srv.link.close_reason = err; srv.link.state = BT_MESH_RPR_LINK_IDLE; srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_RECEIVE_PDU; link_report_send(); cli_link_clear(); } static void pb_rx(const struct prov_bearer *bearer, void *cb_data, struct net_buf_simple *buf) { int err; if (srv.link.state != BT_MESH_RPR_LINK_ACTIVE && srv.link.state != BT_MESH_RPR_LINK_SENDING) { return; } srv.link.rx_pdu++; LOG_DBG(""); err = inbound_pdu_send(buf, NULL); if (err) { LOG_ERR("PDU send fail: %d", err); link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, PROV_BEARER_LINK_STATUS_FAIL); bt_mesh_pb_adv.link_close(PROV_ERR_RESOURCES); } } static const struct prov_bearer_cb prov_bearer_cb = { .link_opened = pb_link_opened, .link_closed = pb_link_closed, .error = pb_error, .recv = pb_rx, }; /******************************************************************************* * Message handlers ******************************************************************************/ static int handle_scan_caps_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_SCAN_CAPS_STATUS, 2); bt_mesh_model_msg_init(&rsp, RPR_OP_SCAN_CAPS_STATUS); net_buf_simple_add_u8(&rsp, CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX); net_buf_simple_add_u8(&rsp, true); bt_mesh_model_send(srv.mod, ctx, &rsp, NULL, NULL); return 0; } static int handle_scan_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { scan_status_send(ctx, BT_MESH_RPR_SUCCESS); return 0; } static int handle_scan_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_rpr_node cli = RPR_NODE(ctx); enum bt_mesh_rpr_status status; const uint8_t *uuid = NULL; uint8_t max_devs; uint8_t timeout; int i; max_devs = net_buf_simple_pull_u8(buf); timeout = net_buf_simple_pull_u8(buf); if (!timeout) { return -EINVAL; } if (buf->len == 16) { uuid = net_buf_simple_pull_mem(buf, 16); } else if (buf->len) { return -EINVAL; } LOG_DBG("max %u devs, %u s %s", max_devs, timeout, uuid ? bt_hex(uuid, 16) : ""); if (max_devs > CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX) { status = BT_MESH_RPR_ERR_SCANNING_CANNOT_START; goto rsp; } if (srv.scan.state != BT_MESH_RPR_SCAN_IDLE && !rpr_node_equal(&cli, &srv.scan.cli)) { status = BT_MESH_RPR_ERR_INVALID_STATE; goto rsp; } for (i = 0; i < ARRAY_SIZE(srv.scan.devs); ++i) { srv.scan.devs[i].flags = 0; } if (uuid) { srv.scan.state = BT_MESH_RPR_SCAN_SINGLE; srv.scan.devs[0].flags = BT_MESH_RPR_UNPROV_ACTIVE; memcpy(srv.scan.devs[0].uuid, uuid, 16); } else { srv.scan.state = BT_MESH_RPR_SCAN_MULTI; } srv.scan.max_devs = (max_devs ? max_devs : CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX); srv.scan.cli = cli; status = BT_MESH_RPR_SUCCESS; atomic_set_bit(srv.flags, SCANNING); k_work_reschedule(&srv.scan.timeout, K_SECONDS(timeout)); rsp: scan_status_send(ctx, status); return 0; } static int handle_extended_scan_start(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_EXTENDED_SCAN_REPORT, 19 + CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX); struct bt_mesh_rpr_node cli = RPR_NODE(ctx); enum bt_mesh_rpr_status status; const uint8_t *uuid; uint8_t *ad = NULL; uint8_t ad_count; uint8_t timeout; int i; /* According to MshPRTv1.1: 4.4.5.5.1.7, scan reports shall be * sent as segmented messages. */ ctx->send_rel = true; ad_count = net_buf_simple_pull_u8(buf); if (buf->len < ad_count || ad_count == 0 || ad_count > 0x10) { /* Prohibited */ return -EINVAL; } ad = net_buf_simple_pull_mem(buf, ad_count); for (i = 0; i < ad_count; ++i) { if (ad[i] == BT_DATA_NAME_SHORTENED || ad[i] == BT_DATA_UUID16_SOME || ad[i] == BT_DATA_UUID32_SOME || ad[i] == BT_DATA_UUID128_SOME) { return -EINVAL; } for (int j = 0; j < i; j++) { if (ad[i] == ad[j]) { /* Duplicate entry */ return -EINVAL; } } } ad_count = MIN(ad_count, CONFIG_BT_MESH_RPR_AD_TYPES_MAX); if (!buf->len) { const struct bt_mesh_prov *prov = bt_mesh_prov_get(); LOG_DBG("Self scan"); /* Want our local info. Could also include additional adv data, * but there's no functionality for this in the mesh stack at * the moment, so we'll only include the URI (if requested) */ bt_mesh_model_msg_init(&rsp, RPR_OP_EXTENDED_SCAN_REPORT); net_buf_simple_add_u8(&rsp, BT_MESH_RPR_SUCCESS); net_buf_simple_add_mem(&rsp, prov->uuid, 16); net_buf_simple_add_le16(&rsp, prov->oob_info); if (prov->uri && get_ad_type(ad, ad_count, BT_DATA_URI)) { uint8_t uri_len = strlen(prov->uri); if (uri_len < CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX - 2) { net_buf_simple_add_u8(&rsp, uri_len + 1); net_buf_simple_add_u8(&rsp, BT_DATA_URI); net_buf_simple_add_mem(&rsp, prov->uri, uri_len); LOG_DBG("URI added: %s", prov->uri); } else { LOG_WRN("URI data won't fit in scan report"); } } bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); return 0; } if (buf->len != 17) { return -EINVAL; } uuid = net_buf_simple_pull_mem(buf, 16); timeout = net_buf_simple_pull_u8(buf); if (IS_ENABLED(CONFIG_BT_MESH_MODEL_LOG_LEVEL_DBG)) { struct bt_uuid_128 uuid_repr = { .uuid = { BT_UUID_TYPE_128 } }; memcpy(uuid_repr.val, uuid, 16); LOG_DBG("%s AD types: %s", bt_uuid_str(&uuid_repr.uuid), bt_hex(ad, ad_count)); } if (timeout < BT_MESH_RPR_EXT_SCAN_TIME_MIN || timeout > BT_MESH_RPR_EXT_SCAN_TIME_MAX) { LOG_ERR("Invalid extended scan timeout %u", timeout); return -EINVAL; } if (srv.link.state != BT_MESH_RPR_LINK_IDLE) { status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; goto rsp; } if (srv.scan.dev && (memcmp(srv.scan.dev->uuid, uuid, 16) || !rpr_node_equal(&srv.scan.cli, &cli))) { LOG_WRN("Extended scan fail: Busy"); status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; goto rsp; } if (srv.scan.state == BT_MESH_RPR_SCAN_IDLE) { srv.scan.max_devs = 1; srv.scan.devs[0].flags = 0; } srv.scan.dev = unprov_get(uuid); if (!srv.scan.dev) { srv.scan.dev = unprov_get(NULL); if (!srv.scan.dev) { LOG_WRN("Extended scan fail: No memory"); status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; goto rsp; } memcpy(srv.scan.dev->uuid, uuid, 16); srv.scan.dev->oob = 0; srv.scan.dev->flags = 0; } memcpy(srv.scan.ad, ad, ad_count); srv.scan.ad_count = ad_count; net_buf_simple_reset(srv.scan.adv_data); atomic_set_bit(srv.flags, SCANNING); atomic_clear_bit(srv.flags, SCAN_EXT_HAS_ADDR); srv.scan.dev->flags &= ~BT_MESH_RPR_UNPROV_REPORTED; srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_ACTIVE | BT_MESH_RPR_UNPROV_EXT; if (srv.scan.state == BT_MESH_RPR_SCAN_IDLE) { srv.scan.additional_time = 0; srv.scan.cli = cli; } else if (k_ticks_to_ms_floor32( k_work_delayable_remaining_get(&srv.scan.timeout)) < (timeout * MSEC_PER_SEC)) { srv.scan.additional_time = 0; } else { srv.scan.additional_time = k_ticks_to_ms_floor32(k_work_delayable_remaining_get(&srv.scan.timeout)) - (timeout * MSEC_PER_SEC); } bt_mesh_scan_active_set(true); k_work_reschedule(&srv.scan.timeout, K_SECONDS(timeout)); return 0; rsp: bt_mesh_model_msg_init(&rsp, RPR_OP_EXTENDED_SCAN_REPORT); net_buf_simple_add_u8(&rsp, status); net_buf_simple_add_mem(&rsp, uuid, 16); bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); return 0; } static int handle_scan_stop(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { if (atomic_test_bit(srv.flags, SCANNING)) { scan_report_send(); scan_stop(); } scan_status_send(ctx, BT_MESH_RPR_SUCCESS); return 0; } static int handle_link_get(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { LOG_DBG(""); link_status_send(ctx, BT_MESH_RPR_SUCCESS); return 0; } static int handle_link_open(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { bool is_refresh_procedure = (buf->len == 1); struct bt_mesh_rpr_node cli = RPR_NODE(ctx); int8_t timeout = LINK_OPEN_TIMEOUT_DEFAULT; enum bt_mesh_rpr_status status; const uint8_t *uuid; uint8_t refresh; int err; if (buf->len != 1 && buf->len != 16 && buf->len != 17) { return -EINVAL; } if (srv.link.state == BT_MESH_RPR_LINK_CLOSING || srv.link.state == BT_MESH_RPR_LINK_SENDING) { status = BT_MESH_RPR_ERR_INVALID_STATE; LOG_ERR("Invalid state: %u", srv.link.state); goto rsp; } if (srv.link.state == BT_MESH_RPR_LINK_OPENING || srv.link.state == BT_MESH_RPR_LINK_ACTIVE) { if (!rpr_node_equal(&cli, &srv.link.cli)) { status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; goto rsp; } if (is_refresh_procedure) { refresh = net_buf_simple_pull_u8(buf); if (!atomic_test_bit(srv.flags, NODE_REFRESH) || srv.refresh.procedure != refresh) { status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; } else { status = BT_MESH_RPR_SUCCESS; } goto rsp; } if (atomic_test_bit(srv.flags, NODE_REFRESH)) { status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; goto rsp; } uuid = net_buf_simple_pull_mem(buf, 16); if (memcmp(uuid, srv.link.dev->uuid, 16)) { status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; } else { status = BT_MESH_RPR_SUCCESS; } goto rsp; } /* Link state is IDLE */ if (is_refresh_procedure) { refresh = net_buf_simple_pull_u8(buf); if (refresh > BT_MESH_RPR_NODE_REFRESH_COMPOSITION) { LOG_ERR("Invalid refresh: %u", refresh); return -EINVAL; } if (refresh == BT_MESH_RPR_NODE_REFRESH_COMPOSITION && !atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY)) { LOG_WRN("Composition data page 128 is equal to page 0"); status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; goto rsp; } LOG_DBG("Node Refresh: %u", refresh); atomic_set_bit(srv.flags, NODE_REFRESH); srv.refresh.procedure = refresh; srv.link.cli = cli; srv.link.rx_pdu = 0; srv.link.tx_pdu = 0; srv.link.state = BT_MESH_RPR_LINK_ACTIVE; srv.link.status = BT_MESH_RPR_SUCCESS; srv.refresh.cb->link_opened(&pb_remote_srv, &srv); status = BT_MESH_RPR_SUCCESS; link_report_send(); goto rsp; } uuid = net_buf_simple_pull_mem(buf, 16); if (buf->len) { timeout = net_buf_simple_pull_u8(buf); if (!timeout || timeout > 0x3c) { LOG_ERR("Invalid timeout: %u", timeout); return -EINVAL; } } LOG_DBG("0x%04x: %s", cli.addr, bt_hex(uuid, 16)); /* Attempt to reuse the scanned unprovisioned device, to preserve as * much information as possible, but fall back to hijacking the first * slot if none was found. */ srv.link.dev = unprov_get(uuid); if (!srv.link.dev) { srv.link.dev = &srv.scan.devs[0]; memcpy(srv.link.dev->uuid, uuid, 16); srv.link.dev->flags = 0; } err = bt_mesh_pb_adv.link_open(uuid, timeout, &prov_bearer_cb, &srv); if (err) { status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; goto rsp; } srv.link.cli = cli; srv.link.rx_pdu = 0; srv.link.tx_pdu = 0; srv.link.state = BT_MESH_RPR_LINK_OPENING; srv.link.status = BT_MESH_RPR_SUCCESS; srv.link.dev->flags |= BT_MESH_RPR_UNPROV_HAS_LINK; status = BT_MESH_RPR_SUCCESS; rsp: link_status_send(ctx, status); return 0; } static int handle_link_close(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_rpr_node cli = RPR_NODE(ctx); enum prov_bearer_link_status reason; reason = net_buf_simple_pull_u8(buf); if (reason != PROV_BEARER_LINK_STATUS_SUCCESS && reason != PROV_BEARER_LINK_STATUS_FAIL) { return -EINVAL; } LOG_DBG(""); if (srv.link.state == BT_MESH_RPR_LINK_IDLE || srv.link.state == BT_MESH_RPR_LINK_CLOSING) { link_status_send(ctx, BT_MESH_RPR_SUCCESS); return 0; } if (!rpr_node_equal(&cli, &srv.link.cli)) { link_status_send(ctx, BT_MESH_RPR_ERR_INVALID_STATE); return 0; } srv.link.state = BT_MESH_RPR_LINK_CLOSING; /* Note: The response status isn't the same as the link status state, * which will be used in the link report when the link is fully closed. */ /* Disable randomization for the Remote Provisioning Link Status message to avoid reordering * of it with the Remote Provisioning Link Report message that shall be sent in a sequence * when closing an active link (see section 4.4.5.5.3.3 of MshPRTv1.1). */ ctx->rnd_delay = false; link_status_send(ctx, BT_MESH_RPR_SUCCESS); link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT, reason); return 0; } static int handle_pdu_send(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_rpr_node cli = RPR_NODE(ctx); uint8_t pdu_num; int err; pdu_num = net_buf_simple_pull_u8(buf); if (srv.link.state != BT_MESH_RPR_LINK_ACTIVE) { LOG_WRN("Sending PDU while busy (state %u)", srv.link.state); return 0; } if (!rpr_node_equal(&cli, &srv.link.cli)) { LOG_WRN("Unknown client 0x%04x", cli.addr); return 0; } if (pdu_num != srv.link.tx_pdu + 1) { LOG_WRN("Invalid pdu number: %u, expected %u", pdu_num, srv.link.tx_pdu + 1); outbound_pdu_report_send(); return 0; } LOG_DBG("0x%02x", buf->data[0]); if (atomic_test_bit(srv.flags, NODE_REFRESH)) { srv.link.tx_pdu++; outbound_pdu_report_send(); srv.refresh.cb->recv(&pb_remote_srv, srv.refresh.cb_data, buf); } else { srv.link.state = BT_MESH_RPR_LINK_SENDING; err = bt_mesh_pb_adv.send(buf, pdu_send_complete, &srv); if (err) { link_close( BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, PROV_BEARER_LINK_STATUS_FAIL); } } return 0; } const struct bt_mesh_model_op _bt_mesh_rpr_srv_op[] = { { RPR_OP_SCAN_CAPS_GET, BT_MESH_LEN_EXACT(0), handle_scan_caps_get }, { RPR_OP_SCAN_GET, BT_MESH_LEN_EXACT(0), handle_scan_get }, { RPR_OP_SCAN_START, BT_MESH_LEN_MIN(2), handle_scan_start }, { RPR_OP_EXTENDED_SCAN_START, BT_MESH_LEN_MIN(1), handle_extended_scan_start }, { RPR_OP_SCAN_STOP, BT_MESH_LEN_EXACT(0), handle_scan_stop }, { RPR_OP_LINK_GET, BT_MESH_LEN_EXACT(0), handle_link_get }, { RPR_OP_LINK_OPEN, BT_MESH_LEN_MIN(1), handle_link_open }, { RPR_OP_LINK_CLOSE, BT_MESH_LEN_EXACT(1), handle_link_close }, { RPR_OP_PDU_SEND, BT_MESH_LEN_MIN(1), handle_pdu_send }, BT_MESH_MODEL_OP_END, }; static struct bt_mesh_rpr_unprov * adv_handle_beacon(const struct bt_le_scan_recv_info *info, struct bt_data *ad) { struct bt_uuid_128 uuid_repr = { .uuid = { BT_UUID_TYPE_128 } }; struct bt_mesh_rpr_unprov *dev = NULL; const uint8_t *uuid; if (ad->data[0] != 0x00 || (ad->data_len != 19 && ad->data_len != 23)) { return NULL; } uuid = &ad->data[1]; dev = unprov_get(uuid); if (!dev) { if (srv.scan.state != BT_MESH_RPR_SCAN_MULTI) { return NULL; } dev = unprov_get(NULL); if (!dev) { return NULL; } memcpy(dev->uuid, uuid, 16); dev->flags = BT_MESH_RPR_UNPROV_ACTIVE; } else if (dev->flags & BT_MESH_RPR_UNPROV_FOUND) { return dev; } dev->oob = sys_get_be16(&ad->data[17]); dev->rssi = info->rssi; if (ad->data_len == 23) { memcpy(&dev->hash, &ad->data[19], 4); dev->flags |= BT_MESH_RPR_UNPROV_HASH; } dev->flags |= BT_MESH_RPR_UNPROV_FOUND; memcpy(uuid_repr.val, uuid, 16); LOG_DBG("Unprov #%u: %s OOB: 0x%04x %s", dev - &srv.scan.devs[0], bt_uuid_str(&uuid_repr.uuid), dev->oob, (dev->flags & BT_MESH_RPR_UNPROV_HASH) ? bt_hex(&dev->hash, 4) : "(no hash)"); if (dev != srv.scan.dev && !(dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) { scan_report_schedule(); } return dev; } static bool pull_ad_data(struct net_buf_simple *buf, struct bt_data *ad) { uint8_t len; if (!buf->len) { return false; } len = net_buf_simple_pull_u8(buf); if (!len || len > buf->len) { return false; } ad->type = net_buf_simple_pull_u8(buf); ad->data_len = len - sizeof(ad->type); ad->data = net_buf_simple_pull_mem(buf, ad->data_len); return true; } static void adv_handle_ext_scan(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { struct bt_mesh_rpr_unprov *dev = NULL; struct net_buf_simple_state initial; struct bt_data ad; bool uri_match = false; bool uri_present = false; bool is_beacon = false; if (atomic_test_bit(srv.flags, SCAN_EXT_HAS_ADDR) && !bt_addr_le_cmp(&srv.scan.addr, info->addr)) { dev = srv.scan.dev; } /* Do AD data walk in two rounds: First to figure out which * unprovisioned device this is (if any), and the second to copy out * relevant AD data to the extended scan report. */ net_buf_simple_save(buf, &initial); while (pull_ad_data(buf, &ad)) { if (ad.type == BT_DATA_URI) { uri_present = true; } if (ad.type == BT_DATA_MESH_BEACON && !dev) { dev = adv_handle_beacon(info, &ad); is_beacon = true; } else if (ad.type == BT_DATA_URI && (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_HASH)) { uint8_t hash[16]; if (bt_mesh_s1(ad.data, ad.data_len, hash) || memcmp(hash, &srv.scan.dev->hash, 4)) { continue; } LOG_DBG("Found matching URI"); uri_match = true; dev = srv.scan.dev; srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_EXT_ADV_RXD; } } if (uri_match) { atomic_set_bit(srv.flags, URI_MATCHED); } if (!dev) { return; } /* Do not process advertisement if it was not identified by URI hash from beacon */ if (!(dev->flags & BT_MESH_RPR_UNPROV_EXT_ADV_RXD)) { return; } srv.scan.addr = *info->addr; atomic_set_bit(srv.flags, SCAN_EXT_HAS_ADDR); if (IS_ENABLED(CONFIG_BT_MESH_MODEL_LOG_LEVEL_DBG)) { struct bt_uuid_128 uuid_repr = { .uuid = { BT_UUID_TYPE_128 } }; memcpy(uuid_repr.val, dev->uuid, 16); LOG_DBG("Is %s", bt_uuid_str(&uuid_repr.uuid)); } net_buf_simple_restore(buf, &initial); /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start message * contains only the URI AD Type, and the URI Hash is not available for the device * with the Device UUID that was requested in the Remote Provisioning Extended Scan * Start message. */ if (srv.scan.ad_count == 1 && get_ad_type(srv.scan.ad, 1, BT_DATA_URI) && !uri_match) { goto complete; } while (srv.scan.ad_count && pull_ad_data(buf, &ad)) { uint8_t *ad_entry; ad_entry = get_ad_type(srv.scan.ad, srv.scan.ad_count, ad.type); if (!ad_entry || (ad.type == BT_DATA_URI && !uri_match)) { continue; } LOG_DBG("AD type 0x%02x", ad.type); if (ad.type == BT_DATA_URI) { atomic_set_bit(srv.flags, URI_REQUESTED); } if (ad.data_len + 2 > net_buf_simple_tailroom(srv.scan.adv_data)) { LOG_WRN("Can't fit AD 0x%02x in scan report", ad.type); continue; } net_buf_simple_add_u8(srv.scan.adv_data, ad.data_len + 1); net_buf_simple_add_u8(srv.scan.adv_data, ad.type); net_buf_simple_add_mem(srv.scan.adv_data, ad.data, ad.data_len); *ad_entry = srv.scan.ad[--srv.scan.ad_count]; } /* The Remote Provisioning Server collects AD structures corresponding to all * AD Types specified in the ADTypeFilter field of the Remote Provisioning Extended * Scan Start message. The timeout specified in the Timeout field of the Remote * Provisioning Extended Scan Start message expires. * OR * The ADTypeFilter field of the Remote Provisioning Extended Scan Start message * contains only the URI AD Type, and the Remote Provisioning Server has received * an advertising report or scan response with the URI corresponding to the URI Hash * of the device with the Device UUID that was requested in the Remote Provisioning * Extended Scan Start message. */ if (!srv.scan.ad_count) { goto complete; } /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start message does * not contain the URI AD Type, and the Remote Provisioning Server receives and processes * the scan response data from the device with Device UUID requested in the Remote * Provisioning Extended Scan Start message. */ if (!is_beacon && !uri_present && info->adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) { goto complete; } /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start message contains * the URI AD Type and at least one different AD Type in the ADTypeFilter field, and the * Remote Provisioning Server has received an advertising report or scan response with the * URI corresponding to the URI Hash of the device with the Device UUID that was requested * in the Remote Provisioning Extended Scan Start message, and the Remote Provisioning * Server received the scan response from the same device. * OR * The ADTypeFilter field of the Remote Provisioning Extended Scan Start message contains * the URI AD Type and at least one different AD Type in the ADTypeFilter field, and the * URI Hash is not available for the device with the Device UUID that was requested in the * Remote Provisioning Extended Scan Start message, and the Remote Provisioning Server * received the scan response from the same device. */ if (atomic_get(srv.flags) & URI_REQUESTED && (atomic_get(srv.flags) & URI_MATCHED || (dev->flags & ~BT_MESH_RPR_UNPROV_HASH)) && info->adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) { goto complete; } return; complete: srv.scan.additional_time = 0; if (srv.scan.state != BT_MESH_RPR_SCAN_MULTI) { k_work_cancel_delayable(&srv.scan.timeout); } scan_ext_stop(0); } static void adv_handle_scan(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { struct bt_data ad; if (info->adv_type != BT_HCI_ADV_NONCONN_IND) { return; } while (pull_ad_data(buf, &ad)) { if (ad.type == BT_DATA_MESH_BEACON) { adv_handle_beacon(info, &ad); return; } } } static void scan_packet_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf) { if (!atomic_test_bit(srv.flags, SCANNING)) { return; } if (srv.scan.dev) { adv_handle_ext_scan(info, buf); } else { adv_handle_scan(info, buf); } } static struct bt_le_scan_cb scan_cb = { .recv = scan_packet_recv, }; static int rpr_srv_init(const struct bt_mesh_model *mod) { if (mod->rt->elem_idx || srv.mod) { LOG_ERR("Remote provisioning server must be initialized " "on first element"); return -EINVAL; } srv.mod = mod; net_buf_simple_init(srv.scan.adv_data, 0); k_work_init_delayable(&srv.scan.timeout, scan_timeout); k_work_init_delayable(&srv.scan.report, scan_report_timeout); k_work_init(&srv.link.report, link_report_send_and_clear); bt_le_scan_cb_register(&scan_cb); mod->keys[0] = BT_MESH_KEY_DEV_LOCAL; mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; return 0; } static void rpr_srv_reset(const struct bt_mesh_model *mod) { cli_link_clear(); cli_scan_clear(); srv.scan.state = BT_MESH_RPR_SCAN_IDLE; srv.link.state = BT_MESH_RPR_LINK_IDLE; k_work_cancel_delayable(&srv.scan.timeout); k_work_cancel_delayable(&srv.scan.report); net_buf_simple_init(srv.scan.adv_data, 0); atomic_clear(srv.flags); srv.link.dev = NULL; srv.scan.dev = NULL; } const struct bt_mesh_model_cb _bt_mesh_rpr_srv_cb = { .init = rpr_srv_init, .reset = rpr_srv_reset, }; static int node_refresh_link_accept(const struct prov_bearer_cb *cb, void *cb_data) { srv.refresh.cb = cb; srv.refresh.cb_data = cb_data; return 0; } static void node_refresh_tx_complete(int err, void *cb_data) { if (err) { link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, PROV_BEARER_LINK_STATUS_FAIL); return; } if (srv.refresh.tx.cb) { srv.refresh.tx.cb(err, srv.refresh.tx.cb_data); } } static int node_refresh_buf_send(struct net_buf_simple *buf, prov_bearer_send_complete_t cb, void *cb_data) { static const struct bt_mesh_send_cb send_cb = { .end = node_refresh_tx_complete, }; int err; if (!atomic_test_bit(srv.flags, NODE_REFRESH)) { return -EBUSY; } srv.refresh.tx.cb = cb; srv.refresh.tx.cb_data = cb_data; srv.link.rx_pdu++; LOG_DBG("%u", srv.link.rx_pdu); err = inbound_pdu_send(buf, &send_cb); if (err) { link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER, PROV_BEARER_LINK_STATUS_FAIL); } return err; } static void node_refresh_clear_tx(void) { /* Nothing can be done */ } const struct prov_bearer pb_remote_srv = { .type = BT_MESH_PROV_REMOTE, .link_accept = node_refresh_link_accept, .send = node_refresh_buf_send, .clear_tx = node_refresh_clear_tx, };