| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <string.h> |
| #include <zephyr/bluetooth/mesh.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <common/bt_str.h> |
| #include "access.h" |
| #include "prov.h" |
| #include "rpr.h" |
| |
| #define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_mesh_rpr_cli); |
| |
| #define LINK_TIMEOUT_SECONDS_DEFAULT 10 |
| |
| BUILD_ASSERT(BT_MESH_MODEL_OP_LEN(RPR_OP_PDU_SEND) == 2, "Assumes PDU send is a 2 byte opcode"); |
| |
| #define LINK_CTX(_srv, _send_rel) \ |
| { \ |
| .net_idx = (_srv)->net_idx, .app_idx = BT_MESH_KEY_DEV_REMOTE, \ |
| .addr = (_srv)->addr, .send_ttl = (_srv)->ttl, \ |
| .send_rel = (_send_rel), \ |
| } |
| |
| enum { |
| BEARER_LINK_IDLE, |
| BEARER_LINK_OPENING, |
| BEARER_LINK_OPENED, |
| }; |
| |
| static struct { |
| int link; |
| const struct prov_bearer_cb *cb; |
| struct bt_mesh_rpr_cli *cli; |
| struct { |
| prov_bearer_send_complete_t cb; |
| } tx; |
| } bearer; |
| |
| static int32_t tx_timeout = (2 * MSEC_PER_SEC); |
| |
| static void link_reset(struct bt_mesh_rpr_cli *cli); |
| static void link_closed(struct bt_mesh_rpr_cli *cli, |
| enum bt_mesh_rpr_status status); |
| |
| static void link_report(struct bt_mesh_rpr_cli *cli, |
| struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_link *link) |
| { |
| struct pb_remote_ctx ctx = { cli, srv }; |
| |
| if (link->state == BT_MESH_RPR_LINK_ACTIVE && |
| bearer.link == BEARER_LINK_OPENING) { |
| bearer.link = BEARER_LINK_OPENED; |
| LOG_DBG("Opened"); |
| bearer.cb->link_opened(&pb_remote_cli, &ctx); |
| |
| /* PB-Remote Open Link procedure timeout is configurable, but the provisioning |
| * protocol timeout is not. Use default provisioning protocol timeout. |
| */ |
| cli->link.time = PROTOCOL_TIMEOUT_SEC; |
| return; |
| } |
| |
| if (link->state == BT_MESH_RPR_LINK_IDLE && |
| bearer.link != BEARER_LINK_IDLE) { |
| bearer.link = BEARER_LINK_IDLE; |
| |
| LOG_DBG("Closed (%u)", link->status); |
| bearer.cb->link_closed(&pb_remote_cli, &ctx, |
| ((link->status == BT_MESH_RPR_SUCCESS) ? |
| PROV_BEARER_LINK_STATUS_SUCCESS : |
| PROV_BEARER_LINK_STATUS_FAIL)); |
| } |
| } |
| |
| static void tx_complete(struct bt_mesh_rpr_cli *cli, int err, void *cb_data) |
| { |
| LOG_DBG("%d", err); |
| |
| cli->link.tx_pdu++; |
| bt_mesh_msg_ack_ctx_clear(&cli->prov_ack_ctx); |
| |
| if (bearer.tx.cb) { |
| bearer.tx.cb(err, cb_data); |
| } |
| } |
| |
| static int handle_extended_scan_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_unprov dev = { 0 }; |
| enum bt_mesh_rpr_status status; |
| bool found_dev = false; |
| |
| status = net_buf_simple_pull_u8(buf); |
| if (status != BT_MESH_RPR_SUCCESS) { |
| LOG_WRN("scan report fail (%u)", status); |
| return 0; |
| } |
| |
| memcpy(dev.uuid, net_buf_simple_pull_mem(buf, 16), 16); |
| if (buf->len >= 2) { |
| dev.oob = net_buf_simple_pull_le16(buf); |
| found_dev = true; |
| LOG_DBG("0x%04x: %s oob: 0x%04x adv data: %s", srv.addr, |
| bt_hex(dev.uuid, 16), dev.oob, |
| bt_hex(buf->data, buf->len)); |
| } else { |
| LOG_DBG("0x%04x: %s not found.", srv.addr, bt_hex(dev.uuid, 16)); |
| } |
| |
| if (cli->scan_report && found_dev) { |
| cli->scan_report(cli, &srv, &dev, buf); |
| } |
| |
| return 0; |
| } |
| |
| static int handle_link_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_link link; |
| uint8_t reason = PROV_BEARER_LINK_STATUS_SUCCESS; |
| |
| link.status = net_buf_simple_pull_u8(buf); |
| link.state = net_buf_simple_pull_u8(buf); |
| if (buf->len == 1) { |
| reason = net_buf_simple_pull_u8(buf); |
| } else if (buf->len) { |
| LOG_WRN("Invalid link report len"); |
| return -EINVAL; |
| } |
| |
| if (cli->link.srv.addr != srv.addr) { |
| LOG_DBG("Link report from unknown server 0x%04x", srv.addr); |
| return 0; |
| } |
| |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| |
| cli->link.state = link.state; |
| |
| LOG_DBG("0x%04x: status: %u state: %u reason: %u", srv.addr, link.status, link.state, |
| reason); |
| |
| if (link.state == BT_MESH_RPR_LINK_IDLE) { |
| link_reset(cli); |
| } |
| |
| link_report(cli, &cli->link.srv, &link); |
| |
| return 0; |
| } |
| |
| static int handle_link_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct bt_mesh_rpr_link *rsp; |
| struct bt_mesh_rpr_link link; |
| |
| link.status = net_buf_simple_pull_u8(buf); |
| link.state = net_buf_simple_pull_u8(buf); |
| |
| LOG_DBG("0x%04x: status: %u state: %u", srv.addr, link.status, |
| link.state); |
| |
| if (bt_mesh_msg_ack_ctx_match(&cli->prov_ack_ctx, RPR_OP_LINK_STATUS, |
| srv.addr, (void **)&rsp)) { |
| *rsp = link; |
| bt_mesh_msg_ack_ctx_rx(&cli->prov_ack_ctx); |
| } |
| |
| if (cli->link.srv.addr == srv.addr) { |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| |
| cli->link.state = link.state; |
| if (link.state == BT_MESH_RPR_LINK_IDLE) { |
| cli->link.srv.addr = BT_MESH_ADDR_UNASSIGNED; |
| } |
| |
| link_report(cli, &cli->link.srv, &link); |
| } |
| |
| return 0; |
| } |
| |
| static int handle_pdu_outbound_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| void *cb_data; |
| uint8_t num; |
| |
| if (srv.addr != cli->link.srv.addr) { |
| LOG_WRN("Outbound report from unknown server 0x%04x", srv.addr); |
| return 0; |
| } |
| |
| num = net_buf_simple_pull_u8(buf); |
| |
| LOG_DBG("0x%04x: %u", srv.addr, num); |
| |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| |
| if (!bt_mesh_msg_ack_ctx_match(&cli->prov_ack_ctx, RPR_OP_PDU_OUTBOUND_REPORT, |
| srv.addr, &cb_data) || |
| num != cli->link.tx_pdu) { |
| LOG_WRN("Non-matching PDU report (%u)", num); |
| return 0; |
| } |
| |
| tx_complete(cli, 0, cb_data); |
| |
| return 0; |
| } |
| |
| static int handle_pdu_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct pb_remote_ctx cb_ctx = { |
| cli, |
| &cli->link.srv, |
| }; |
| uint8_t pdu; |
| |
| if (cli->link.srv.addr != srv.addr) { |
| LOG_WRN("PDU report from unknown server 0x%04x", srv.addr); |
| return 0; |
| } |
| |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| |
| pdu = net_buf_simple_pull_u8(buf); |
| if (pdu <= cli->link.rx_pdu) { |
| LOG_WRN("Duplicate rx %u", pdu); |
| return 0; |
| } |
| |
| LOG_DBG("0x%04x: %u (%u bytes)", srv.addr, pdu, buf->len); |
| |
| bearer.cb->recv(&pb_remote_cli, &cb_ctx, buf); |
| |
| return 0; |
| } |
| |
| static int handle_scan_caps_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct bt_mesh_rpr_caps *caps; |
| |
| if (!bt_mesh_msg_ack_ctx_match(&cli->scan_ack_ctx, RPR_OP_SCAN_CAPS_STATUS, |
| srv.addr, (void **)&caps)) { |
| LOG_WRN("Unexpected scan caps rsp from 0x%04x", srv.addr); |
| return 0; |
| } |
| |
| caps->max_devs = net_buf_simple_pull_u8(buf); |
| caps->active_scan = net_buf_simple_pull_u8(buf); |
| |
| LOG_DBG("max devs: %u active scan: %u", caps->max_devs, |
| caps->active_scan); |
| |
| bt_mesh_msg_ack_ctx_rx(&cli->scan_ack_ctx); |
| |
| return 0; |
| } |
| |
| static int handle_scan_report(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| struct bt_mesh_rpr_unprov dev = { 0 }; |
| |
| dev.rssi = net_buf_simple_pull_u8(buf); |
| memcpy(dev.uuid, net_buf_simple_pull_mem(buf, 16), 16); |
| dev.oob = net_buf_simple_pull_le16(buf); |
| if (buf->len == 4) { |
| memcpy(&dev.hash, net_buf_simple_pull_mem(buf, 4), 4); |
| dev.flags = BT_MESH_RPR_UNPROV_HASH; |
| } else if (buf->len) { |
| return -EINVAL; |
| } |
| |
| 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("0x%04x: %s oob: 0x%04x %ddBm", srv.addr, |
| bt_uuid_str(&uuid_repr.uuid), dev.oob, dev.rssi); |
| } |
| |
| if (cli->scan_report) { |
| cli->scan_report(cli, &srv, &dev, NULL); |
| } |
| |
| return 0; |
| } |
| |
| static int handle_scan_status(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| struct bt_mesh_rpr_scan_status *status; |
| struct bt_mesh_rpr_node srv = RPR_NODE(ctx); |
| |
| if (!bt_mesh_msg_ack_ctx_match(&cli->scan_ack_ctx, RPR_OP_SCAN_STATUS, |
| srv.addr, (void **)&status)) { |
| LOG_WRN("Unexpected scan status from 0x%04x", srv.addr); |
| return 0; |
| } |
| |
| status->status = net_buf_simple_pull_u8(buf); |
| status->scan = net_buf_simple_pull_u8(buf); |
| status->max_devs = net_buf_simple_pull_u8(buf); |
| status->timeout = net_buf_simple_pull_u8(buf); |
| |
| LOG_DBG("status: %u state: %u max devs: %u timeout: %u seconds", |
| status->status, status->scan, status->max_devs, status->timeout); |
| bt_mesh_msg_ack_ctx_rx(&cli->scan_ack_ctx); |
| |
| return 0; |
| } |
| |
| const struct bt_mesh_model_op _bt_mesh_rpr_cli_op[] = { |
| { RPR_OP_EXTENDED_SCAN_REPORT, BT_MESH_LEN_MIN(17), handle_extended_scan_report }, |
| { RPR_OP_LINK_REPORT, BT_MESH_LEN_MIN(2), handle_link_report }, |
| { RPR_OP_LINK_STATUS, BT_MESH_LEN_EXACT(2), handle_link_status }, |
| { RPR_OP_PDU_OUTBOUND_REPORT, BT_MESH_LEN_EXACT(1), handle_pdu_outbound_report }, |
| { RPR_OP_PDU_REPORT, BT_MESH_LEN_MIN(2), handle_pdu_report }, |
| { RPR_OP_SCAN_CAPS_STATUS, BT_MESH_LEN_EXACT(2), handle_scan_caps_status }, |
| { RPR_OP_SCAN_REPORT, BT_MESH_LEN_MIN(19), handle_scan_report }, |
| { RPR_OP_SCAN_STATUS, BT_MESH_LEN_EXACT(4), handle_scan_status }, |
| BT_MESH_MODEL_OP_END, |
| }; |
| |
| static void link_timeout(struct k_work *work) |
| { |
| struct bt_mesh_rpr_cli *cli = CONTAINER_OF(k_work_delayable_from_work(work), |
| struct bt_mesh_rpr_cli, link.timeout); |
| |
| if (bearer.link != BEARER_LINK_IDLE) { |
| LOG_DBG(""); |
| link_closed(cli, BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT); |
| } |
| } |
| |
| static int rpr_cli_init(const struct bt_mesh_model *mod) |
| { |
| if (mod->rt->elem_idx) { |
| LOG_ERR("Remote provisioning client must be initialized " |
| "on first element"); |
| return -EINVAL; |
| } |
| |
| struct bt_mesh_rpr_cli *cli = mod->rt->user_data; |
| |
| cli->mod = mod; |
| cli->link.time = LINK_TIMEOUT_SECONDS_DEFAULT; |
| |
| bt_mesh_msg_ack_ctx_init(&cli->scan_ack_ctx); |
| bt_mesh_msg_ack_ctx_init(&cli->prov_ack_ctx); |
| k_work_init_delayable(&cli->link.timeout, link_timeout); |
| mod->keys[0] = BT_MESH_KEY_DEV_ANY; |
| mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; |
| |
| return 0; |
| } |
| |
| const struct bt_mesh_model_cb _bt_mesh_rpr_cli_cb = { |
| .init = rpr_cli_init, |
| }; |
| |
| static void pdu_send_start(uint16_t duration, int err, void *cb_data) |
| { |
| struct bt_mesh_rpr_cli *cli = cb_data; |
| |
| if (err) { |
| LOG_ERR("PDU Send failed: %d", err); |
| |
| link_closed(cli, |
| BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); |
| } |
| } |
| |
| static void pdu_send_end(int err, void *cb_data) |
| { |
| struct bt_mesh_rpr_cli *cli = cb_data; |
| |
| if (err) { |
| LOG_ERR("PDU Send failed: %d", err); |
| |
| link_closed(cli, |
| BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); |
| return; |
| } |
| |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| } |
| |
| static const struct bt_mesh_send_cb pdu_send_cb = { |
| .start = pdu_send_start, |
| .end = pdu_send_end, |
| }; |
| |
| static int tx_wait(struct bt_mesh_rpr_cli *cli, |
| struct bt_mesh_msg_ack_ctx *ack_ctx, const struct bt_mesh_rpr_node *srv, |
| struct net_buf_simple *buf, uint32_t rsp, void *rsp_ctx) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); |
| int err; |
| |
| err = bt_mesh_msg_ack_ctx_prepare(ack_ctx, rsp, srv->addr, rsp_ctx); |
| if (err) { |
| return err; |
| } |
| |
| err = bt_mesh_model_send(cli->mod, &ctx, buf, NULL, NULL); |
| if (err) { |
| bt_mesh_msg_ack_ctx_clear(ack_ctx); |
| LOG_WRN("TX fail"); |
| return err; |
| } |
| |
| err = bt_mesh_msg_ack_ctx_wait(ack_ctx, K_MSEC(tx_timeout)); |
| |
| bt_mesh_msg_ack_ctx_clear(ack_ctx); |
| return err; |
| } |
| |
| static void link_init(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv) |
| { |
| cli->link.srv = *srv; |
| cli->link.state = BT_MESH_RPR_LINK_IDLE; |
| cli->link.rx_pdu = 0; |
| cli->link.tx_pdu = 1; |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| } |
| |
| static void link_reset(struct bt_mesh_rpr_cli *cli) |
| { |
| k_work_cancel_delayable(&cli->link.timeout); |
| cli->link.srv.addr = BT_MESH_ADDR_UNASSIGNED; |
| cli->link.state = BT_MESH_RPR_LINK_IDLE; |
| bt_mesh_msg_ack_ctx_clear(&cli->prov_ack_ctx); |
| } |
| |
| static void link_closed(struct bt_mesh_rpr_cli *cli, |
| enum bt_mesh_rpr_status status) |
| { |
| struct bt_mesh_rpr_node srv = cli->link.srv; |
| struct bt_mesh_rpr_link link = { |
| .status = status, |
| .state = BT_MESH_RPR_LINK_IDLE, |
| }; |
| |
| LOG_DBG("0x%04x: status: %u state: %u rx: %u tx: %u", srv.addr, link.status, |
| cli->link.state, cli->link.rx_pdu, cli->link.tx_pdu); |
| |
| link_reset(cli); |
| |
| link_report(cli, &srv, &link); |
| } |
| |
| int bt_mesh_rpr_scan_caps_get(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_caps *caps) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_CAPS_GET, 0); |
| bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_CAPS_GET); |
| |
| return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_CAPS_STATUS, caps); |
| } |
| |
| int bt_mesh_rpr_scan_get(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_scan_status *status) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_GET, 0); |
| bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_GET); |
| |
| return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, status); |
| } |
| |
| int bt_mesh_rpr_scan_start(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| const uint8_t uuid[16], uint8_t timeout, |
| uint8_t max_devs, |
| struct bt_mesh_rpr_scan_status *status) |
| { |
| if (!timeout) { |
| return -EINVAL; |
| } |
| |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_START, 18); |
| bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_START); |
| |
| net_buf_simple_add_u8(&buf, max_devs); |
| net_buf_simple_add_u8(&buf, timeout); |
| |
| if (uuid) { |
| net_buf_simple_add_mem(&buf, uuid, 16); |
| } |
| |
| return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, status); |
| } |
| |
| int bt_mesh_rpr_scan_start_ext(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| const uint8_t uuid[16], uint8_t timeout, |
| const uint8_t *ad_types, size_t ad_count) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); |
| |
| if ((uuid && (timeout < BT_MESH_RPR_EXT_SCAN_TIME_MIN || |
| timeout > BT_MESH_RPR_EXT_SCAN_TIME_MAX)) || |
| !ad_count || ad_count > CONFIG_BT_MESH_RPR_AD_TYPES_MAX) { |
| return -EINVAL; |
| } |
| |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_EXTENDED_SCAN_START, |
| 18 + CONFIG_BT_MESH_RPR_AD_TYPES_MAX); |
| bt_mesh_model_msg_init(&buf, RPR_OP_EXTENDED_SCAN_START); |
| |
| net_buf_simple_add_u8(&buf, ad_count); |
| net_buf_simple_add_mem(&buf, ad_types, ad_count); |
| if (uuid) { |
| net_buf_simple_add_mem(&buf, uuid, 16); |
| net_buf_simple_add_u8(&buf, timeout); |
| } |
| |
| return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); |
| } |
| |
| int bt_mesh_rpr_scan_stop(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_scan_status *status) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_STOP, 0); |
| bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_STOP); |
| |
| return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, status); |
| } |
| |
| int bt_mesh_rpr_link_get(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_link *rsp) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_GET, 0); |
| bt_mesh_model_msg_init(&buf, RPR_OP_LINK_GET); |
| |
| return tx_wait(cli, &cli->prov_ack_ctx, srv, &buf, RPR_OP_LINK_STATUS, rsp); |
| } |
| |
| int bt_mesh_rpr_link_close(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| struct bt_mesh_rpr_link *rsp) |
| { |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_CLOSE, 1); |
| bt_mesh_model_msg_init(&buf, RPR_OP_LINK_CLOSE); |
| net_buf_simple_add_u8(&buf, PROV_BEARER_LINK_STATUS_FAIL); |
| |
| return tx_wait(cli, &cli->prov_ack_ctx, srv, &buf, RPR_OP_LINK_STATUS, rsp); |
| } |
| |
| static int link_open_prov(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, const uint8_t uuid[16], |
| uint8_t timeout) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); |
| |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_OPEN, 17); |
| bt_mesh_model_msg_init(&buf, RPR_OP_LINK_OPEN); |
| |
| net_buf_simple_add_mem(&buf, uuid, 16); |
| |
| if (cli->link.time != LINK_TIMEOUT_SECONDS_DEFAULT) { |
| net_buf_simple_add_u8(&buf, cli->link.time); |
| } |
| |
| return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); |
| } |
| |
| static int link_open_node(struct bt_mesh_rpr_cli *cli, |
| const struct bt_mesh_rpr_node *srv, |
| enum bt_mesh_rpr_node_refresh type) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); |
| |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_OPEN, 1); |
| bt_mesh_model_msg_init(&buf, RPR_OP_LINK_OPEN); |
| |
| net_buf_simple_add_u8(&buf, type); |
| |
| return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); |
| } |
| |
| static int link_close(struct bt_mesh_rpr_cli *cli, |
| enum prov_bearer_link_status status) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(&cli->link.srv, false); |
| int err; |
| |
| if (cli->link.srv.addr == BT_MESH_ADDR_UNASSIGNED) { |
| return -EALREADY; |
| } |
| |
| BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_CLOSE, 1); |
| bt_mesh_model_msg_init(&buf, RPR_OP_LINK_CLOSE); |
| |
| net_buf_simple_add_u8(&buf, status); |
| |
| err = bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); |
| if (err) { |
| link_reset(cli); |
| } |
| |
| k_work_reschedule(&cli->link.timeout, K_SECONDS(cli->link.time)); |
| return err; |
| } |
| |
| static int send(struct bt_mesh_rpr_cli *cli, struct net_buf_simple *buf, |
| void *cb_data) |
| { |
| struct bt_mesh_msg_ctx ctx = LINK_CTX(&cli->link.srv, true); |
| int err; |
| |
| if (cli->link.srv.addr == BT_MESH_ADDR_UNASSIGNED) { |
| LOG_ERR("No server"); |
| return -ESHUTDOWN; |
| } |
| |
| if (net_buf_simple_headroom(buf) < 3) { |
| LOG_ERR("Invalid buffer"); |
| return -EINVAL; |
| } |
| |
| err = bt_mesh_msg_ack_ctx_prepare(&cli->prov_ack_ctx, |
| RPR_OP_PDU_OUTBOUND_REPORT, |
| cli->link.srv.addr, cb_data); |
| if (err) { |
| LOG_ERR("Busy"); |
| return err; |
| } |
| |
| LOG_DBG("0x%02x", buf->data[0]); |
| |
| net_buf_simple_push_u8(buf, cli->link.tx_pdu); |
| /* Assumes opcode is 2 bytes. Build assert at top of file ensures this. |
| */ |
| net_buf_simple_push_be16(buf, RPR_OP_PDU_SEND); |
| |
| err = bt_mesh_model_send(cli->mod, &ctx, buf, &pdu_send_cb, cli); |
| if (err) { |
| link_closed(cli, |
| BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); |
| } |
| |
| return err; |
| } |
| |
| int32_t bt_mesh_rpr_cli_timeout_get(void) |
| { |
| return tx_timeout; |
| } |
| |
| void bt_mesh_rpr_cli_timeout_set(int32_t timeout) |
| { |
| tx_timeout = timeout; |
| } |
| |
| /******************************************************************************* |
| * Prov bearer interface |
| ******************************************************************************/ |
| |
| static int pb_send(struct net_buf_simple *buf, prov_bearer_send_complete_t cb, |
| void *cb_data) |
| { |
| bearer.tx.cb = cb; |
| return send(bearer.cli, buf, cb_data); |
| } |
| |
| static void pb_clear_tx(void) |
| { |
| /* Nothing can be done */ |
| } |
| |
| static int pb_link_open(const uint8_t uuid[16], uint8_t timeout, |
| const struct prov_bearer_cb *cb, void *cb_data) |
| { |
| struct pb_remote_ctx *ctx = cb_data; |
| struct bt_mesh_rpr_cli *cli = ctx->cli; |
| const struct bt_mesh_rpr_node *srv = ctx->srv; |
| int err; |
| |
| if (cli->link.srv.addr != BT_MESH_ADDR_UNASSIGNED) { |
| return -EBUSY; |
| } |
| |
| bearer.cli = ctx->cli; |
| bearer.cb = cb; |
| |
| cli->link.time = timeout ? timeout : LINK_TIMEOUT_SECONDS_DEFAULT; |
| |
| LOG_DBG("timeout: %d", cli->link.time); |
| |
| link_init(cli, srv); |
| |
| if (uuid) { |
| err = link_open_prov(cli, srv, uuid, timeout); |
| } else { |
| err = link_open_node(cli, srv, ctx->refresh); |
| } |
| |
| if (err) { |
| link_reset(cli); |
| return err; |
| } |
| |
| bearer.link = BEARER_LINK_OPENING; |
| |
| return 0; |
| } |
| |
| static void pb_link_close(enum prov_bearer_link_status status) |
| { |
| int err; |
| |
| err = link_close(bearer.cli, status); |
| if (err) { |
| LOG_ERR("Link close failed (%d)", err); |
| } |
| } |
| |
| const struct prov_bearer pb_remote_cli = { |
| .type = BT_MESH_PROV_REMOTE, |
| .send = pb_send, |
| .clear_tx = pb_clear_tx, |
| .link_open = pb_link_open, |
| .link_close = pb_link_close, |
| }; |