blob: ba177bf19816f05560ff44054e61f4e3f2c39a2a [file] [log] [blame]
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2020 Lingao Meng
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/mesh.h>
#include <zephyr/bluetooth/uuid.h>
#include "crypto.h"
#include "mesh.h"
#include "net.h"
#include "access.h"
#include "foundation.h"
#include "prov.h"
#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_prov);
struct bt_mesh_prov_link bt_mesh_prov_link;
const struct bt_mesh_prov *bt_mesh_prov;
/* Verify specification defined length: */
BUILD_ASSERT(sizeof(bt_mesh_prov_link.conf_inputs) == 145,
"Confirmation inputs shall be 145 bytes");
int bt_mesh_prov_reset_state(void)
{
int err;
const size_t offset = offsetof(struct bt_mesh_prov_link, addr);
atomic_clear(bt_mesh_prov_link.flags);
(void)memset((uint8_t *)&bt_mesh_prov_link + offset, 0,
sizeof(bt_mesh_prov_link) - offset);
err = bt_mesh_pub_key_gen();
if (err) {
LOG_ERR("Failed to generate public key (%d)", err);
return err;
}
return 0;
}
static bt_mesh_output_action_t output_action(uint8_t action)
{
switch (action) {
case OUTPUT_OOB_BLINK:
return BT_MESH_BLINK;
case OUTPUT_OOB_BEEP:
return BT_MESH_BEEP;
case OUTPUT_OOB_VIBRATE:
return BT_MESH_VIBRATE;
case OUTPUT_OOB_NUMBER:
return BT_MESH_DISPLAY_NUMBER;
case OUTPUT_OOB_STRING:
return BT_MESH_DISPLAY_STRING;
default:
return BT_MESH_NO_OUTPUT;
}
}
static bt_mesh_input_action_t input_action(uint8_t action)
{
switch (action) {
case INPUT_OOB_PUSH:
return BT_MESH_PUSH;
case INPUT_OOB_TWIST:
return BT_MESH_TWIST;
case INPUT_OOB_NUMBER:
return BT_MESH_ENTER_NUMBER;
case INPUT_OOB_STRING:
return BT_MESH_ENTER_STRING;
default:
return BT_MESH_NO_INPUT;
}
}
static int check_output_auth(bt_mesh_output_action_t output, uint8_t size)
{
if (!output) {
return -EINVAL;
}
if (!(bt_mesh_prov->output_actions & output)) {
return -EINVAL;
}
if (size > bt_mesh_prov->output_size) {
return -EINVAL;
}
return 0;
}
static int check_input_auth(bt_mesh_input_action_t input, uint8_t size)
{
if (!input) {
return -EINVAL;
}
if (!(bt_mesh_prov->input_actions & input)) {
return -EINVAL;
}
if (size > bt_mesh_prov->input_size) {
return -EINVAL;
}
return 0;
}
static void get_auth_string(char *str, uint8_t size)
{
uint64_t value;
bt_rand(&value, sizeof(value));
static const char characters[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int i = 0; i < size; i++) {
/* pull base-36 digits: */
int idx = value % 36;
value = value / 36;
str[i] = characters[idx];
}
str[size] = '\0';
memcpy(bt_mesh_prov_link.auth, str, size);
memset(bt_mesh_prov_link.auth + size, 0,
sizeof(bt_mesh_prov_link.auth) - size);
}
static uint32_t get_auth_number(bt_mesh_output_action_t output,
bt_mesh_input_action_t input, uint8_t size)
{
const uint32_t divider[PROV_IO_OOB_SIZE_MAX] = { 10, 100, 1000, 10000,
100000, 1000000, 10000000, 100000000 };
uint8_t auth_size = bt_mesh_prov_auth_size_get();
uint32_t num = 0;
bt_rand(&num, sizeof(num));
if (output == BT_MESH_BLINK || output == BT_MESH_BEEP || output == BT_MESH_VIBRATE ||
input == BT_MESH_PUSH || input == BT_MESH_TWIST) {
/* According to MshPRTv1.1: 5.4.2.4, blink, beep vibrate, push and twist should be
* a random integer between 0 and 10^size, *exclusive*:
*/
num = (num % (divider[size - 1] - 1)) + 1;
} else {
num %= divider[size - 1];
}
sys_put_be32(num, &bt_mesh_prov_link.auth[auth_size - sizeof(num)]);
memset(bt_mesh_prov_link.auth, 0, auth_size - sizeof(num));
return num;
}
int bt_mesh_prov_auth(bool is_provisioner, uint8_t method, uint8_t action, uint8_t size)
{
bt_mesh_output_action_t output;
bt_mesh_input_action_t input;
uint8_t auth_size = bt_mesh_prov_auth_size_get();
int err;
switch (method) {
case AUTH_METHOD_NO_OOB:
if (action || size) {
return -EINVAL;
}
(void)memset(bt_mesh_prov_link.auth, 0, auth_size);
return 0;
case AUTH_METHOD_STATIC:
if (action || size) {
return -EINVAL;
}
atomic_set_bit(bt_mesh_prov_link.flags, OOB_STATIC_KEY);
return 0;
case AUTH_METHOD_OUTPUT:
output = output_action(action);
if (is_provisioner) {
if (output == BT_MESH_DISPLAY_STRING) {
input = BT_MESH_ENTER_STRING;
atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING);
} else {
input = BT_MESH_ENTER_NUMBER;
atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER);
}
return bt_mesh_prov->input(input, size);
}
err = check_output_auth(output, size);
if (err) {
return err;
}
if (output == BT_MESH_DISPLAY_STRING) {
char str[9];
atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE);
get_auth_string(str, size);
return bt_mesh_prov->output_string(str);
}
atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE);
return bt_mesh_prov->output_number(output,
get_auth_number(output, BT_MESH_NO_INPUT, size));
case AUTH_METHOD_INPUT:
input = input_action(action);
if (!is_provisioner) {
err = check_input_auth(input, size);
if (err) {
return err;
}
if (input == BT_MESH_ENTER_STRING) {
atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING);
} else {
atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER);
}
return bt_mesh_prov->input(input, size);
}
if (input == BT_MESH_ENTER_STRING) {
char str[9];
atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE);
get_auth_string(str, size);
return bt_mesh_prov->output_string(str);
}
atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE);
output = BT_MESH_DISPLAY_NUMBER;
return bt_mesh_prov->output_number(output,
get_auth_number(BT_MESH_NO_OUTPUT, input, size));
default:
return -EINVAL;
}
}
int bt_mesh_input_number(uint32_t num)
{
uint8_t auth_size = bt_mesh_prov_auth_size_get();
LOG_DBG("%u", num);
if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_NUMBER)) {
return -EINVAL;
}
sys_put_be32(num, &bt_mesh_prov_link.auth[auth_size - sizeof(num)]);
bt_mesh_prov_link.role->input_complete();
return 0;
}
int bt_mesh_input_string(const char *str)
{
LOG_DBG("%s", str);
if (strlen(str) > PROV_IO_OOB_SIZE_MAX ||
strlen(str) > bt_mesh_prov_link.oob_size) {
return -ENOTSUP;
}
if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_STRING)) {
return -EINVAL;
}
memcpy(bt_mesh_prov_link.auth, str, strlen(str));
bt_mesh_prov_link.role->input_complete();
return 0;
}
const struct bt_mesh_prov *bt_mesh_prov_get(void)
{
return bt_mesh_prov;
}
bool bt_mesh_prov_active(void)
{
return atomic_test_bit(bt_mesh_prov_link.flags, LINK_ACTIVE);
}
static void prov_recv(const struct prov_bearer *bearer, void *cb_data,
struct net_buf_simple *buf)
{
static const uint8_t op_len[10] = {
[PROV_INVITE] = PDU_LEN_INVITE,
[PROV_CAPABILITIES] = PDU_LEN_CAPABILITIES,
[PROV_START] = PDU_LEN_START,
[PROV_PUB_KEY] = PDU_LEN_PUB_KEY,
[PROV_INPUT_COMPLETE] = PDU_LEN_INPUT_COMPLETE,
[PROV_CONFIRM] = PDU_LEN_CONFIRM,
[PROV_RANDOM] = PDU_LEN_RANDOM,
[PROV_DATA] = PDU_LEN_DATA,
[PROV_COMPLETE] = PDU_LEN_COMPLETE,
[PROV_FAILED] = PDU_LEN_FAILED,
};
uint8_t type = buf->data[0];
LOG_DBG("type 0x%02x len %u", type, buf->len);
if (type >= ARRAY_SIZE(bt_mesh_prov_link.role->op)) {
LOG_ERR("Unknown provisioning PDU type 0x%02x", type);
bt_mesh_prov_link.role->error(PROV_ERR_NVAL_PDU);
return;
}
if ((type != PROV_FAILED && type != bt_mesh_prov_link.expect) ||
!bt_mesh_prov_link.role->op[type]) {
LOG_WRN("Unexpected msg 0x%02x != 0x%02x", type, bt_mesh_prov_link.expect);
bt_mesh_prov_link.role->error(PROV_ERR_UNEXP_PDU);
return;
}
uint8_t expected = 1 + op_len[type];
if (type == PROV_CONFIRM || type == PROV_RANDOM) {
/* Expected length depends on Auth size */
expected = 1 + bt_mesh_prov_auth_size_get();
}
if (buf->len != expected) {
LOG_ERR("Invalid length %u for type 0x%02x", buf->len, type);
bt_mesh_prov_link.role->error(PROV_ERR_NVAL_FMT);
return;
}
bt_mesh_prov_link.role->op[type](&buf->data[1]);
}
static void prov_link_opened(const struct prov_bearer *bearer, void *cb_data)
{
atomic_set_bit(bt_mesh_prov_link.flags, LINK_ACTIVE);
if (bt_mesh_prov->link_open) {
bt_mesh_prov->link_open(bearer->type);
}
bt_mesh_prov_link.bearer = bearer;
if (bt_mesh_prov_link.role->link_opened) {
bt_mesh_prov_link.role->link_opened();
}
}
static void prov_link_closed(const struct prov_bearer *bearer, void *cb_data,
enum prov_bearer_link_status reason)
{
LOG_DBG("%u", reason);
if (bt_mesh_prov_link.role->link_closed) {
bt_mesh_prov_link.role->link_closed(reason);
}
if (bt_mesh_prov->link_close) {
bt_mesh_prov->link_close(bearer->type);
}
}
static void prov_bearer_error(const struct prov_bearer *bearer, void *cb_data,
uint8_t err)
{
if (bt_mesh_prov_link.role->error) {
bt_mesh_prov_link.role->error(err);
}
}
static const struct prov_bearer_cb prov_bearer_cb = {
.link_opened = prov_link_opened,
.link_closed = prov_link_closed,
.error = prov_bearer_error,
.recv = prov_recv,
};
const struct prov_bearer_cb *bt_mesh_prov_bearer_cb_get(void)
{
return &prov_bearer_cb;
}
void bt_mesh_prov_complete(uint16_t net_idx, uint16_t addr)
{
if (bt_mesh_prov->complete) {
bt_mesh_prov->complete(net_idx, addr);
}
}
void bt_mesh_prov_reset(void)
{
if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
bt_mesh_pb_adv_reset();
}
if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) {
bt_mesh_pb_gatt_reset();
}
bt_mesh_prov_reset_state();
if (bt_mesh_prov->reset) {
bt_mesh_prov->reset();
}
}
int bt_mesh_prov_init(const struct bt_mesh_prov *prov_info)
{
if (!prov_info) {
LOG_ERR("No provisioning context provided");
return -EINVAL;
}
bt_mesh_prov = prov_info;
if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
bt_mesh_pb_adv_init();
}
if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) {
bt_mesh_pb_gatt_init();
}
return bt_mesh_prov_reset_state();
}