OOB information should be in little endian in scan report messages. URI hash should be retrieved as it is from unprovisioned device beacon and encoded likewise into scan report messages like we do for UUID. Signed-off-by: Alperen Sener <alperen.sener@nordicsemi.no>
759 lines
20 KiB
C
759 lines
20 KiB
C
/*
|
|
* 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(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->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(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->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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_DEBUG)) {
|
|
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(struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
struct bt_mesh_rpr_cli *cli = mod->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(struct bt_mesh_model *mod)
|
|
{
|
|
if (mod->elem_idx) {
|
|
LOG_ERR("Remote provisioning client must be initialized "
|
|
"on first element");
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct bt_mesh_rpr_cli *cli = mod->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->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,
|
|
};
|