bluez/mesh/prov.c
2018-08-20 12:48:18 -07:00

723 lines
16 KiB
C

/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/time.h>
#include <ell/ell.h>
#include "mesh/mesh-defs.h"
#include "mesh/mesh-io.h"
#include "mesh/display.h"
#include "mesh/crypto.h"
#include "mesh/net.h"
#include "mesh/prov.h"
#define PB_ADV_MTU 24
#define DEFAULT_CONN_ID 0x00000000
#define DEFAULT_PROV_MSG_NUM 0x00
#define DEFAULT_DEV_MSG_NUM 0x80
#define TX_TIMEOUT 30
struct mesh_prov *mesh_prov_new(struct mesh_net *net, uint16_t remote)
{
struct mesh_prov *prov;
prov = l_new(struct mesh_prov, 1);
prov->remote = remote;
prov->net = net;
return mesh_prov_ref(prov);
}
struct mesh_prov *mesh_prov_ref(struct mesh_prov *prov)
{
if (!prov)
return NULL;
__sync_fetch_and_add(&prov->ref_count, 1);
return prov;
}
void mesh_prov_unref(struct mesh_prov *prov)
{
struct mesh_io *io;
uint8_t type;
if (!prov)
return;
if (__sync_sub_and_fetch(&prov->ref_count, 1))
return;
io = mesh_net_get_io(prov->net);
type = MESH_AD_TYPE_BEACON;
mesh_io_send_cancel(io, &type, 1);
type = MESH_AD_TYPE_PROVISION;
mesh_io_send_cancel(io, &type, 1);
mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_PROV);
mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_BEACON);
l_timeout_remove(prov->tx_timeout);
l_info("Freed Prov Data");
l_free(prov);
}
static void packet_received(struct mesh_prov *prov, const void *data,
uint16_t size, uint8_t fcs)
{
if (prov->receive_callback)
prov->receive_callback(data, size, prov);
}
static void send_open_req(struct mesh_prov *prov)
{
struct mesh_io *io;
uint8_t open_req[23] = { MESH_AD_TYPE_PROVISION };
struct mesh_io_send_info info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 50,
.u.gen.cnt = 1,
.u.gen.min_delay = 0,
.u.gen.max_delay = 0,
};
if (!prov)
return;
io = mesh_net_get_io(prov->net);
if (!io)
return;
l_put_be32(prov->conn_id, open_req + 1);
open_req[1 + 4] = prov->local_msg_num = 0;
open_req[1 + 4 + 1] = 0x03; /* OPEN_REQ */
memcpy(open_req + 1 + 4 + 1 + 1, prov->uuid, 16);
/* print_packet("PB-TX", open_req + 1, sizeof(open_req) - 1); */
mesh_io_send_cancel(io, open_req, 1);
mesh_io_send(io, &info, open_req, sizeof(open_req));
}
static void send_open_cfm(struct mesh_prov *prov)
{
struct mesh_io *io;
uint8_t open_cfm[7] = { MESH_AD_TYPE_PROVISION };
struct mesh_io_send_info info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 50,
.u.gen.cnt = 1,
.u.gen.min_delay = 0,
.u.gen.max_delay = 0,
};
if (!prov)
return;
io = mesh_net_get_io(prov->net);
if (!io)
return;
l_put_be32(prov->conn_id, open_cfm + 1);
open_cfm[1 + 4] = 0;
open_cfm[1 + 4 + 1] = 0x07; /* OPEN_CFM */
/* print_packet("PB-TX", open_cfm + 1, sizeof(open_cfm) - 1); */
mesh_io_send_cancel(io, open_cfm, 1);
mesh_io_send(io, &info, open_cfm, sizeof(open_cfm));
}
static void send_close_ind(struct mesh_prov *prov, uint8_t reason)
{
uint8_t buf[8] = { MESH_AD_TYPE_PROVISION };
struct mesh_io *io;
struct mesh_io_send_info info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 50,
.u.gen.cnt = 3,
.u.gen.min_delay = 0,
.u.gen.max_delay = 0,
};
if (!prov)
return;
if (prov->bearer == MESH_BEARER_ADV) {
io = mesh_net_get_io(prov->net);
if (!io)
return;
l_put_be32(prov->conn_id, buf + 1);
buf[5] = 0;
buf[6] = 0x0B; /* CLOSE_IND */
buf[7] = reason;
/* print_packet("PB-TX", buf + 1, sizeof(buf) - 1); */
mesh_io_send_cancel(io, buf, 1);
mesh_io_send(io, &info, buf, sizeof(buf));
}
prov->bearer = MESH_BEARER_IDLE;
}
static void tx_timeout(struct l_timeout *timeout, void *user_data)
{
struct mesh_prov *prov = user_data;
uint8_t cancel[] = { MESH_AD_TYPE_PROVISION };
struct mesh_io *io;
if (!prov)
return;
l_timeout_remove(prov->tx_timeout);
prov->tx_timeout = NULL;
io = mesh_net_get_io(prov->net);
if (!io)
return;
mesh_io_send_cancel(io, cancel, sizeof(cancel));
l_info("TX timeout");
mesh_prov_close(prov, 1);
}
static void send_adv_segs(struct mesh_prov *prov)
{
struct mesh_io *io = mesh_net_get_io(prov->net);
struct mesh_io_send_info info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 50,
.u.gen.cnt = MESH_IO_TX_COUNT_UNLIMITED,
.u.gen.min_delay = 0,
.u.gen.max_delay = 0,
};
const void *data = prov->packet_buf;
uint16_t size = prov->packet_len;
uint16_t init_size;
uint8_t buf[1 + PB_ADV_MTU + 5] = { MESH_AD_TYPE_PROVISION };
uint8_t max_seg;
uint8_t consumed;
int i;
if (!size)
return;
mesh_io_send_cancel(io, buf, 1);
l_put_be32(prov->conn_id, buf + 1);
buf[1 + 4] = prov->local_msg_num;
if (size > PB_ADV_MTU - 4) {
max_seg = 1 +
(((size - (PB_ADV_MTU - 4)) - 1) / (PB_ADV_MTU - 1));
init_size = PB_ADV_MTU - 4;
} else {
max_seg = 0;
init_size = size;
}
/* print_packet("FULL-TX", data, size); */
l_debug("Sending %u fragments for %u octets", max_seg + 1, size);
buf[1 + 4 + 1] = max_seg << 2;
l_put_be16(size, buf + 1 + 4 + 1 + 1);
buf[9] = mesh_crypto_compute_fcs(data, size);
memcpy(buf + 1 + 4 + 1 + 1 + 2 + 1, data, init_size);
l_debug("max_seg: %2.2x", max_seg);
l_debug("size: %2.2x, CRC: %2.2x", size, buf[9]);
/* print_packet("PB-TX", buf + 1, init_size + 9); */
mesh_io_send(io, &info, buf, init_size + 10);
consumed = init_size;
for (i = 1; i <= max_seg; i++) {
uint8_t seg_size; /* Amount of payload data being sent */
if (size - consumed > PB_ADV_MTU - 1)
seg_size = PB_ADV_MTU - 1;
else
seg_size = size - consumed;
buf[6] = (i << 2) | 0x02;
memcpy(buf + 7, data + consumed, seg_size);
/* print_packet("PB-TX", buf + 1, seg_size + 6); */
mesh_io_send(io, &info, buf, seg_size + 7);
consumed += seg_size;
}
}
static void send_adv_msg(struct mesh_prov *prov, const void *data,
uint16_t size)
{
l_timeout_remove(prov->tx_timeout);
prov->tx_timeout = l_timeout_create(TX_TIMEOUT, tx_timeout, prov, NULL);
memcpy(prov->packet_buf, data, size);
prov->packet_len = size;
send_adv_segs(prov);
}
static void send_ack(struct mesh_prov *prov)
{
struct mesh_io *io = mesh_net_get_io(prov->net);
struct mesh_io_send_info info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 50,
.u.gen.cnt = 1,
.u.gen.min_delay = 0,
.u.gen.max_delay = 0,
};
uint8_t ack[7] = { MESH_AD_TYPE_PROVISION };
l_put_be32(prov->conn_id, ack + 1);
ack[1 + 4] = prov->last_peer_msg_num;
ack[1 + 4 + 1] = 0x01; /* ACK */
/* print_packet("ADV-ACK", ack + 1, sizeof(ack) - 1); */
mesh_io_send(io, &info, ack, sizeof(ack));
}
static void adv_data_pkt(uint8_t type, const void *pkt, uint8_t size,
void *user_data)
{
const uint8_t *data = pkt;
struct mesh_prov *prov = user_data;
uint16_t offset = 0;
if ((type & 0x03) == 0x00) {
uint8_t last_seg = type >> 2;
prov->expected_len = l_get_be16(data);
prov->expected_fcs = l_get_u8(data + 2);
/* print_packet("Pkt", pkt, size); */
data += 3;
size -= 3;
prov->trans = MESH_TRANS_RX;
if (prov->expected_len > sizeof(prov->peer_buf)) {
l_info("Incoming pkt exceeds storage %d > %ld",
prov->expected_len, sizeof(prov->peer_buf));
return;
} else if (last_seg == 0)
prov->trans = MESH_TRANS_IDLE;
prov->expected_segs = 0xff >> (7 - last_seg);
prov->got_segs |= 1;
memcpy(prov->peer_buf, data, size);
} else if ((type & 0x03) == 0x02) {
offset = (PB_ADV_MTU - 4) + ((type >> 2) - 1) *
(PB_ADV_MTU - 1);
if (offset + size > prov->expected_len) {
l_info("Incoming pkt exceeds agreed len %d + %d > %d",
offset, size, prov->expected_len);
return;
}
prov->trans = MESH_TRANS_RX;
l_debug("Processing fragment %u", type & 0x3f);
prov->got_segs |= 1 << (type >> 2);
memcpy(prov->peer_buf + offset, data, size);
} else if (type == 0x01) {
if (prov->send_callback) {
void *data = prov->send_data;
mesh_prov_send_func_t cb = prov->send_callback;
prov->trans = MESH_TRANS_IDLE;
prov->send_callback = NULL;
prov->send_data = NULL;
cb(true, data);
}
return;
} else
return;
if (prov->got_segs != prov->expected_segs)
return;
/* Validate RXed packet and pass up to Provisioning */
if (!mesh_crypto_check_fcs(prov->peer_buf,
prov->expected_len,
prov->expected_fcs)) {
l_debug("Invalid FCS");
return;
}
prov->last_peer_msg_num = prov->peer_msg_num;
send_ack(prov);
prov->trans = MESH_TRANS_IDLE;
packet_received(prov, prov->peer_buf,
prov->expected_len, prov->expected_fcs);
/* Reset Re-Assembly for next packet */
prov->expected_len = sizeof(prov->peer_buf);
prov->expected_fcs = 0;
prov->expected_segs = 0;
prov->got_segs = 0;
}
static void adv_bearer_packet(void *user_data, struct mesh_io_recv_info *info,
const uint8_t *pkt, uint16_t len)
{
struct mesh_prov *prov = user_data;
uint32_t conn_id;
uint8_t msg_num;
uint8_t type;
if (len < 6) {
l_info(" Too short packet");
return;
}
conn_id = l_get_be32(pkt + 1);
msg_num = l_get_u8(pkt + 1 + 4);
type = l_get_u8(pkt + 1 + 4 + 1);
/*if (prov->conn_id == conn_id) print_packet("ADV-RX", pkt, len); */
if (prov->conn_id != DEFAULT_CONN_ID) {
if (prov->conn_id != conn_id) {
l_debug("rxed unknown conn_id: %8.8x != %8.8x",
conn_id, prov->conn_id);
return;
}
} else if (type != 0x03)
return;
/* print_packet("PB-ADV-RX", pkt, len); */
/* Normalize pkt to start of PROV pkt payload */
pkt += 7;
len -= 7;
if (type == 0x07) { /* OPEN_CFM */
if (conn_id != prov->conn_id)
return;
if (msg_num != prov->local_msg_num)
return;
l_info("Link open confirmed");
prov->bearer = MESH_BEARER_ADV;
if (prov->open_callback)
prov->open_callback(prov->receive_data);
} else if (type == 0x01) {
if (conn_id != prov->conn_id)
return;
if (msg_num != prov->local_msg_num)
return;
l_debug("Got ACK %d", msg_num);
adv_data_pkt(type, pkt, len, user_data);
} else if (type == 0x03) {
/*
* Ignore if:
* 1. We are already provisioning
* 2. We are not advertising that we are unprovisioned
* 3. Open request not addressed to us
*/
if (prov->conn_id != DEFAULT_CONN_ID &&
prov->conn_id != conn_id)
return;
if (prov->local_msg_num != (DEFAULT_DEV_MSG_NUM - 1))
return;
if (memcmp(pkt, prov->uuid, 16))
return;
l_info("Link open request");
prov->last_peer_msg_num = 0xFF;
prov->bearer = MESH_BEARER_ADV;
if (prov->open_callback && prov->conn_id == DEFAULT_CONN_ID)
prov->open_callback(prov->receive_data);
prov->conn_id = conn_id;
prov->peer_msg_num = msg_num;
send_open_cfm(prov);
} else if (type == 0x0B) {
if (prov->conn_id != conn_id)
return;
prov->conn_id = DEFAULT_CONN_ID;
prov->local_msg_num = 0xFF;
prov->peer_msg_num = 0xFF;
prov->last_peer_msg_num = 0xFF;
l_timeout_remove(prov->tx_timeout);
prov->tx_timeout = NULL;
l_info("Link closed notification: %2.2x", pkt[0]);
if (prov->close_callback)
prov->close_callback(prov->receive_data, pkt[0]);
} else if ((type & 0x03) == 0x00) {
if (prov->conn_id != conn_id)
return;
if (msg_num == prov->last_peer_msg_num) {
send_ack(prov);
return;
}
prov->peer_msg_num = msg_num;
l_debug("Processing Data with %u fragments,%d octets",
type >> 2, l_get_be16(pkt));
adv_data_pkt(type, pkt, len, user_data);
} else if ((type & 0x03) == 0x02) {
if (prov->conn_id != conn_id)
return;
if (msg_num == prov->last_peer_msg_num) {
send_ack(prov);
return;
}
prov->peer_msg_num = msg_num;
l_debug("Processing fragment %u", type >> 2);
adv_data_pkt(type, pkt, len, user_data);
}
}
static void beacon_packet(void *user_data, struct mesh_io_recv_info *info,
const uint8_t *pkt, uint16_t len)
{
struct mesh_prov *prov = user_data;
struct mesh_io *io;
pkt++;
len--;
if (len < 19)
return;
if (!pkt[0])
print_packet("UnProv-BEACON-RX", pkt, len);
/* Ignore devices not matching UUID */
if (pkt[0] || memcmp(pkt + 1, prov->uuid, 16))
return;
io = mesh_net_get_io(prov->net);
mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_BEACON);
if ((prov->conn_id != DEFAULT_CONN_ID) ||
(prov->bearer != MESH_BEARER_IDLE)) {
l_info("PB-ADV: Already Provisioning");
return;
}
l_getrandom(&prov->conn_id, sizeof(prov->conn_id));
prov->bearer = MESH_BEARER_ADV;
send_open_req(prov);
}
static bool mesh_prov_enable(struct mesh_prov *prov, enum mesh_prov_mode mode,
uint8_t uuid[16])
{
const uint8_t pb_adv_data[] = { MESH_AD_TYPE_BEACON, 0 };
uint8_t adv_data[62];
uint8_t adv_len, type;
struct mesh_io *io;
struct mesh_io_send_info tx_info = {
.type = MESH_IO_TIMING_TYPE_GENERAL,
.u.gen.interval = 1000, /* ms */
.u.gen.cnt = 0, /* 0 == Infinite */
.u.gen.min_delay = 0, /* no delay */
.u.gen.max_delay = 0, /* no delay */
};
if (!prov || !prov->net)
return false;
prov->mode = mode;
memcpy(prov->uuid, uuid, 16);
prov->conn_id = DEFAULT_CONN_ID;
io = mesh_net_get_io(prov->net);
switch (mode) {
case MESH_PROV_MODE_NONE:
break;
case MESH_PROV_MODE_INITIATOR:
print_packet("Searching for uuid", uuid, 16);
prov->local_msg_num = DEFAULT_PROV_MSG_NUM;
prov->peer_msg_num = DEFAULT_DEV_MSG_NUM;
mesh_io_register_recv_cb(io, MESH_IO_FILTER_PROV,
adv_bearer_packet, prov);
mesh_io_register_recv_cb(io, MESH_IO_FILTER_BEACON,
beacon_packet, prov);
break;
case MESH_PROV_MODE_ADV_ACCEPTOR:
prov->local_msg_num = DEFAULT_DEV_MSG_NUM - 1;
prov->peer_msg_num = DEFAULT_PROV_MSG_NUM;
print_packet("Beaconing as unProvisioned uuid", uuid, 16);
adv_len = sizeof(pb_adv_data);
memcpy(adv_data, pb_adv_data, adv_len);
memcpy(adv_data + adv_len, uuid, 16);
adv_len += 16;
adv_len += 2;
mesh_io_register_recv_cb(io, MESH_IO_FILTER_PROV,
adv_bearer_packet, prov);
type = MESH_AD_TYPE_BEACON;
mesh_io_send_cancel(io, &type, 1);
mesh_io_send(io, &tx_info, adv_data, adv_len);
break;
case MESH_PROV_MODE_GATT_CLIENT:
case MESH_PROV_MODE_MESH_GATT_CLIENT:
case MESH_PROV_MODE_GATT_ACCEPTOR:
case MESH_PROV_MODE_MESH_SERVER:
case MESH_PROV_MODE_MESH_CLIENT:
default:
l_error("Unimplemented Prov Mode: %d", mode);
break;
}
return true;
}
bool mesh_prov_listen(struct mesh_net *net, uint8_t uuid[16], uint8_t caps[12],
mesh_prov_open_func_t open_callback,
mesh_prov_close_func_t close_callback,
mesh_prov_receive_func_t recv_callback,
void *user_data)
{
struct mesh_prov *prov = mesh_net_get_prov(net);
if (!prov) {
prov = mesh_prov_new(net, 0);
if (!prov)
return false;
mesh_net_set_prov(net, prov);
}
prov->open_callback = open_callback;
prov->close_callback = close_callback;
prov->receive_callback = recv_callback;
prov->receive_data = prov; /* TODO: retink the callback placement */
memcpy(prov->caps, caps, sizeof(prov->caps));
prov->trans = MESH_TRANS_IDLE;
return mesh_prov_enable(prov, MESH_PROV_MODE_ADV_ACCEPTOR, uuid);
}
unsigned int mesh_prov_send(struct mesh_prov *prov,
const void *ptr, uint16_t size,
mesh_prov_send_func_t send_callback,
void *user_data)
{
const uint8_t *data = ptr;
if (!prov)
return 0;
if (prov->trans != MESH_TRANS_IDLE)
return 0;
if (prov->remote) {
/* TODO -- PB-Remote */
} else {
prov->send_callback = send_callback;
prov->send_data = user_data;
prov->trans = MESH_TRANS_TX;
prov->local_msg_num++;
send_adv_msg(prov, data, size);
}
return 1;
}
bool mesh_prov_close(struct mesh_prov *prov, uint8_t reason)
{
if (!prov)
return false;
prov->local_msg_num = 0;
send_close_ind(prov, reason);
prov->conn_id = DEFAULT_CONN_ID;
prov->local_msg_num = 0xFF;
prov->peer_msg_num = 0xFF;
prov->last_peer_msg_num = 0xFF;
if (prov->tx_timeout) {
l_timeout_remove(prov->tx_timeout);
/* If timing out, give Close indication 1 second of
* provisioning timing to get final Close indication out
*/
prov->tx_timeout = l_timeout_create(1, tx_timeout, prov, NULL);
}
if (prov->close_callback)
prov->close_callback(prov->receive_data, reason);
return false;
}
void mesh_prov_set_addr(struct mesh_prov *prov, uint16_t addr)
{
prov->addr = addr;
}
uint16_t mesh_prov_get_idx(struct mesh_prov *prov)
{
return prov->net_idx;
}