/*
|
* Copyright (c) 2017 Intel Corporation
|
* Copyright (c) 2021 Lingao Meng
|
*
|
* SPDX-License-Identifier: Apache-2.0
|
*/
|
|
#define MESH_LOG_MODULE BLE_MESH_PROV_LOG
|
|
#include "mesh_priv.h"
|
#include "adv.h"
|
#include "net.h"
|
#include "rpl.h"
|
#include "transport.h"
|
#include "prov.h"
|
#include "beacon.h"
|
#include "foundation.h"
|
#include "access.h"
|
#include "proxy.h"
|
#include "proxy_msg.h"
|
#include "pb_gatt_srv.h"
|
#include "nimble_syscfg.h"
|
#include "services/gatt/ble_svc_gatt.h"
|
#include "../../host/src/ble_hs_priv.h"
|
|
#if defined(CONFIG_BT_MESH_PB_GATT_USE_DEVICE_NAME)
|
#define ADV_OPT_USE_NAME BT_LE_ADV_OPT_USE_NAME
|
#else
|
#define ADV_OPT_USE_NAME 0
|
#endif
|
|
#define ADV_OPT_PROV \
|
.conn_mode = (BLE_GAP_CONN_MODE_UND), \
|
.disc_mode = (BLE_GAP_DISC_MODE_GEN),
|
|
#if MYNEWT_VAL(BLE_MESH_PB_GATT)
|
/** @def BT_UUID_MESH_PROV
|
* @brief Mesh Provisioning Service
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROV = BLE_UUID16_INIT(0x1827);
|
#define BT_UUID_MESH_PROV_VAL 0x1827
|
/** @def BT_UUID_MESH_PROXY
|
* @brief Mesh Proxy Service
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROXY = BLE_UUID16_INIT(0x1828);
|
#define BT_UUID_MESH_PROXY_VAL 0x1828
|
/** @def BT_UUID_GATT_CCC
|
* @brief GATT Client Characteristic Configuration
|
*/
|
ble_uuid16_t BT_UUID_GATT_CCC = BLE_UUID16_INIT(0x2902);
|
#define BT_UUID_GATT_CCC_VAL 0x2902
|
/** @def BT_UUID_MESH_PROV_DATA_IN
|
* @brief Mesh Provisioning Data In
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROV_DATA_IN = BLE_UUID16_INIT(0x2adb);
|
#define BT_UUID_MESH_PROV_DATA_IN_VAL 0x2adb
|
/** @def BT_UUID_MESH_PROV_DATA_OUT
|
* @brief Mesh Provisioning Data Out
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROV_DATA_OUT = BLE_UUID16_INIT(0x2adc);
|
#define BT_UUID_MESH_PROV_DATA_OUT_VAL 0x2adc
|
/** @def BT_UUID_MESH_PROXY_DATA_IN
|
* @brief Mesh Proxy Data In
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROXY_DATA_IN = BLE_UUID16_INIT(0x2add);
|
#define BT_UUID_MESH_PROXY_DATA_IN_VAL 0x2add
|
/** @def BT_UUID_MESH_PROXY_DATA_OUT
|
* @brief Mesh Proxy Data Out
|
*/
|
ble_uuid16_t BT_UUID_MESH_PROXY_DATA_OUT = BLE_UUID16_INIT(0x2ade);
|
#define BT_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ade
|
#define BT_UUID_16_ENCODE(w16) \
|
(((w16) >> 0) & 0xFF), \
|
(((w16) >> 8) & 0xFF)
|
|
static bool prov_fast_adv;
|
|
struct svc_handles svc_handles;
|
static atomic_t pending_notifications;
|
|
static int gatt_send(uint16_t conn_handle,
|
const void *data, uint16_t len);
|
|
static struct bt_mesh_proxy_role *cli;
|
|
static void proxy_msg_recv(struct bt_mesh_proxy_role *role)
|
{
|
switch (role->msg_type) {
|
case BT_MESH_PROXY_PROV:
|
BT_DBG("Mesh Provisioning PDU");
|
bt_mesh_pb_gatt_recv(role->conn_handle, role->buf);
|
break;
|
default:
|
BT_WARN("Unhandled Message Type 0x%02x", role->msg_type);
|
break;
|
}
|
}
|
|
static bool service_registered;
|
|
static int gatt_recv_proxy(uint16_t conn_handle, uint16_t attr_handle,
|
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
{
|
const uint8_t *data = ctxt->om->om_data;
|
uint16_t len = ctxt->om->om_len;
|
struct bt_mesh_proxy_client *client = find_client(conn_handle);
|
|
if (len < 1) {
|
BT_WARN("Too small Proxy PDU");
|
return -EINVAL;
|
}
|
|
if (PDU_TYPE(data) == BT_MESH_PROXY_PROV) {
|
BT_WARN("Proxy PDU type doesn't match GATT service");
|
return -EINVAL;
|
}
|
|
return bt_mesh_proxy_msg_recv(client->cli, data, len);
|
}
|
|
static int gatt_recv_prov(uint16_t conn_handle, uint16_t attr_handle,
|
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
{
|
const uint8_t *data = ctxt->om->om_data;
|
uint16_t len = ctxt->om->om_len;
|
|
if (conn_handle != cli->conn_handle) {
|
BT_WARN("conn_handle != cli->conn_handle");
|
return -ENOTCONN;
|
}
|
|
if (len < 1) {
|
BT_WARN("Too small Proxy PDU");
|
return -EINVAL;
|
}
|
|
if (PDU_TYPE(data) != BT_MESH_PROXY_PROV) {
|
BT_WARN("Proxy PDU type doesn't match GATT service");
|
return -EINVAL;
|
}
|
|
return bt_mesh_proxy_msg_recv(cli, data, len);
|
}
|
|
void gatt_connected_pb_gatt(uint16_t conn_handle, uint8_t err)
|
{
|
struct ble_gap_conn_desc info;
|
struct ble_hs_conn *conn;
|
|
conn = ble_hs_conn_find(conn_handle);
|
bt_conn_get_info(conn, &info);
|
if (info.role != BLE_GAP_ROLE_SLAVE ||
|
!service_registered || bt_mesh_is_provisioned()) {
|
return;
|
}
|
|
cli = bt_mesh_proxy_role_setup(conn_handle, gatt_send, proxy_msg_recv);
|
|
BT_DBG("conn %p err 0x%02x", (void *)conn, err);
|
}
|
|
void gatt_disconnected_pb_gatt(struct ble_gap_conn_desc conn, uint8_t reason)
|
{
|
if (conn.role != BLE_GAP_ROLE_SLAVE ||
|
!service_registered) {
|
return;
|
}
|
|
if (cli) {
|
bt_mesh_proxy_role_cleanup(cli);
|
cli = NULL;
|
}
|
|
BT_DBG("conn_handle %d reason 0x%02x", conn.conn_handle, reason);
|
|
bt_mesh_pb_gatt_close(conn.conn_handle);
|
|
if (bt_mesh_is_provisioned()) {
|
(void)bt_mesh_pb_gatt_disable();
|
}
|
}
|
|
int prov_ccc_write(uint16_t conn_handle, uint8_t type)
|
{
|
if (cli->conn_handle != conn_handle) {
|
BT_ERR("No PB-GATT Client found");
|
return -ENOTCONN;
|
}
|
|
if (type != BLE_GAP_EVENT_SUBSCRIBE) {
|
BT_WARN("Client wrote instead enabling notify");
|
return BT_GATT_ERR(EINVAL);
|
}
|
|
bt_mesh_pb_gatt_open(conn_handle);
|
|
return 0;
|
}
|
|
/* Mesh Provisioning Service Declaration */
|
|
static int
|
dummy_access_cb(uint16_t conn_handle, uint16_t attr_handle,
|
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
{
|
/*
|
* We should never never enter this callback - it's attached to notify-only
|
* characteristic which are notified directly from mbuf. And we can't pass
|
* NULL as access_cb because gatts will assert on init...
|
*/
|
BLE_HS_DBG_ASSERT(0);
|
return 0;
|
}
|
|
static const struct ble_gatt_svc_def svc_defs [] = {
|
{
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
|
.characteristics = (struct ble_gatt_chr_def[]) { {
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_IN_VAL),
|
.access_cb = gatt_recv_proxy,
|
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
}, {
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
|
.access_cb = dummy_access_cb,
|
.flags = BLE_GATT_CHR_F_NOTIFY,
|
}, {
|
0, /* No more characteristics in this service. */
|
} },
|
}, {
|
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
|
.characteristics = (struct ble_gatt_chr_def[]) { {
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
|
.access_cb = gatt_recv_prov,
|
.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
|
}, {
|
.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
|
.access_cb = dummy_access_cb,
|
.flags = BLE_GATT_CHR_F_NOTIFY,
|
}, {
|
0, /* No more characteristics in this service. */
|
} },
|
}, {
|
0, /* No more services. */
|
},
|
};
|
|
void resolve_svc_handles(void)
|
{
|
int rc;
|
|
/* Either all handles are already resolved, or none of them */
|
if (svc_handles.prov_data_out_h) {
|
return;
|
}
|
|
/*
|
* We assert if attribute is not found since at this stage all attributes
|
* shall be already registered and thus shall be found.
|
*/
|
|
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
|
&svc_handles.proxy_h);
|
assert(rc == 0);
|
|
rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
|
BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
|
NULL, &svc_handles.proxy_data_out_h);
|
assert(rc == 0);
|
|
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
|
&svc_handles.prov_h);
|
assert(rc == 0);
|
|
rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
|
BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
|
NULL, &svc_handles.prov_data_in_h);
|
assert(rc == 0);
|
|
rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
|
BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
|
NULL, &svc_handles.prov_data_out_h);
|
assert(rc == 0);
|
}
|
|
|
int bt_mesh_proxy_svcs_register(void)
|
{
|
int rc;
|
|
rc = ble_gatts_count_cfg(svc_defs);
|
assert(rc == 0);
|
|
rc = ble_gatts_add_svcs(svc_defs);
|
assert(rc == 0);
|
|
return 0;
|
}
|
|
int bt_mesh_pb_gatt_enable(void)
|
{
|
int rc;
|
uint16_t handle;
|
BT_DBG("");
|
|
if (bt_mesh_is_provisioned()) {
|
return -ENOTSUP;
|
}
|
|
if (service_registered) {
|
return -EBUSY;
|
}
|
|
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
|
assert(rc == 0);
|
ble_gatts_svc_set_visibility(handle, 1);
|
/* FIXME: figure out end handle */
|
ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
|
|
service_registered = true;
|
prov_fast_adv = true;
|
|
return 0;
|
}
|
|
int bt_mesh_pb_gatt_disable(void)
|
{
|
uint16_t handle;
|
int rc;
|
|
BT_DBG("");
|
|
if (!service_registered) {
|
return -EALREADY;
|
}
|
|
rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
|
assert(rc == 0);
|
ble_gatts_svc_set_visibility(handle, 0);
|
/* FIXME: figure out end handle */
|
ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
|
service_registered = false;
|
|
bt_mesh_adv_update();
|
|
return 0;
|
}
|
|
static uint8_t prov_svc_data[20] = {
|
BT_UUID_16_ENCODE(BT_UUID_MESH_PROV_VAL),
|
};
|
|
static const struct bt_data prov_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_PROV_VAL)),
|
BT_DATA(BT_DATA_SVC_DATA16, prov_svc_data, sizeof(prov_svc_data)),
|
};
|
|
int bt_mesh_pb_gatt_send(uint16_t conn_handle, struct os_mbuf *buf)
|
{
|
if (!cli || cli->conn_handle != conn_handle) {
|
BT_ERR("No PB-GATT Client found");
|
return -ENOTCONN;
|
}
|
|
return bt_mesh_proxy_msg_send(cli, BT_MESH_PROXY_PROV, buf);
|
}
|
|
static size_t gatt_prov_adv_create(struct bt_data prov_sd[1])
|
{
|
const struct bt_mesh_prov *prov = bt_mesh_prov_get();
|
size_t uri_len;
|
|
memcpy(prov_svc_data + 2, prov->uuid, 16);
|
sys_put_be16(prov->oob_info, prov_svc_data + 18);
|
|
if (!prov->uri) {
|
return 0;
|
}
|
|
uri_len = strlen(prov->uri);
|
if (uri_len > 29) {
|
/* There's no way to shorten an URI */
|
BT_WARN("Too long URI to fit advertising packet");
|
return 0;
|
}
|
|
prov_sd[0].type = BT_DATA_URI;
|
prov_sd[0].data_len = uri_len;
|
prov_sd[0].data = (const uint8_t *)prov->uri;
|
|
return 1;
|
}
|
|
static int gatt_send(uint16_t conn_handle,
|
const void *data, uint16_t len)
|
{
|
struct os_mbuf *om;
|
int err = 0;
|
|
BT_DBG("%u bytes: %s", len, bt_hex(data, len));
|
om = ble_hs_mbuf_from_flat(data, len);
|
assert(om);
|
err = ble_gatts_notify_custom(conn_handle, svc_handles.prov_data_out_h, om);
|
notify_complete();
|
|
if (!err) {
|
atomic_inc(&pending_notifications);
|
}
|
|
return err;
|
}
|
|
int bt_mesh_pb_gatt_adv_start(void)
|
{
|
BT_DBG("");
|
|
if (!service_registered || bt_mesh_is_provisioned()) {
|
return -ENOTSUP;
|
}
|
|
struct ble_gap_adv_params fast_adv_param = {
|
ADV_OPT_PROV
|
ADV_FAST_INT
|
};
|
#if ADV_OPT_USE_NAME
|
const char *name = CONFIG_BT_DEVICE_NAME;
|
size_t name_len = strlen(name);
|
struct bt_data prov_sd = {
|
.type = BT_DATA_NAME_COMPLETE,
|
.data_len = name_len,
|
.data = (void *)name
|
};
|
#else
|
struct bt_data *prov_sd = NULL;
|
#endif
|
|
size_t prov_sd_len;
|
int err;
|
|
prov_sd_len = gatt_prov_adv_create(prov_sd);
|
|
if (!prov_fast_adv) {
|
struct ble_gap_adv_params slow_adv_param = {
|
ADV_OPT_PROV
|
ADV_SLOW_INT
|
};
|
|
return bt_mesh_adv_start(&slow_adv_param, K_FOREVER, prov_ad,
|
ARRAY_SIZE(prov_ad), prov_sd, prov_sd_len);
|
}
|
|
/* Advertise 60 seconds using fast interval */
|
err = bt_mesh_adv_start(&fast_adv_param, (60 * MSEC_PER_SEC),
|
prov_ad, ARRAY_SIZE(prov_ad),
|
prov_sd, prov_sd_len);
|
if (!err) {
|
prov_fast_adv = false;
|
}
|
|
return err;
|
}
|
#endif
|