520 lines
11 KiB
C
520 lines
11 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2017 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.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <sys/uio.h>
|
|
#include <wordexp.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "src/shared/io.h"
|
|
#include "src/shared/shell.h"
|
|
#include "gdbus/gdbus.h"
|
|
#include "lib/bluetooth.h"
|
|
#include "lib/uuid.h"
|
|
#include "mesh/node.h"
|
|
#include "mesh/util.h"
|
|
#include "mesh/gatt.h"
|
|
#include "mesh/prov.h"
|
|
#include "mesh/net.h"
|
|
|
|
#define MESH_PROV_DATA_OUT_UUID_STR "00002adc-0000-1000-8000-00805f9b34fb"
|
|
#define MESH_PROXY_DATA_OUT_UUID_STR "00002ade-0000-1000-8000-00805f9b34fb"
|
|
|
|
static struct io *write_io;
|
|
static uint16_t write_mtu;
|
|
|
|
static struct io *notify_io;
|
|
static uint16_t notify_mtu;
|
|
|
|
struct write_data {
|
|
GDBusProxy *proxy;
|
|
void *user_data;
|
|
struct iovec iov;
|
|
GDBusReturnFunction cb;
|
|
uint8_t *gatt_data;
|
|
uint8_t gatt_len;
|
|
};
|
|
|
|
struct notify_data {
|
|
GDBusProxy *proxy;
|
|
bool enable;
|
|
GDBusReturnFunction cb;
|
|
void *user_data;
|
|
};
|
|
|
|
bool mesh_gatt_is_child(GDBusProxy *proxy, GDBusProxy *parent,
|
|
const char *name)
|
|
{
|
|
DBusMessageIter iter;
|
|
const char *parent_path;
|
|
|
|
if (!parent)
|
|
return FALSE;
|
|
|
|
if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE)
|
|
return FALSE;
|
|
|
|
dbus_message_iter_get_basic(&iter, &parent_path);
|
|
|
|
if (!strcmp(parent_path, g_dbus_proxy_get_path(parent)))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* Refactor this once actual MTU is available */
|
|
#define GATT_MTU 23
|
|
|
|
static void write_data_free(void *user_data)
|
|
{
|
|
struct write_data *data = user_data;
|
|
|
|
g_free(data->gatt_data);
|
|
free(data);
|
|
}
|
|
|
|
uint16_t mesh_gatt_sar(uint8_t **pkt, uint16_t size)
|
|
{
|
|
const uint8_t *data = *pkt;
|
|
uint8_t gatt_hdr = *data++;
|
|
uint8_t type = gatt_hdr & GATT_TYPE_MASK;
|
|
static uint8_t gatt_size;
|
|
static uint8_t gatt_pkt[67];
|
|
|
|
print_byte_array("GATT-RX:\t", *pkt, size);
|
|
if (size < 1) {
|
|
gatt_pkt[0] = GATT_TYPE_INVALID;
|
|
/* TODO: Disconnect GATT per last paragraph sec 6.6 */
|
|
return 0;
|
|
}
|
|
|
|
size--;
|
|
|
|
switch (gatt_hdr & GATT_SAR_MASK) {
|
|
case GATT_SAR_FIRST:
|
|
gatt_size = 1;
|
|
gatt_pkt[0] = type;
|
|
/* TODO: Start Proxy Timeout */
|
|
/* fall through */
|
|
|
|
case GATT_SAR_CONTINUE:
|
|
if (gatt_pkt[0] != type ||
|
|
gatt_size + size > MAX_GATT_SIZE) {
|
|
|
|
/* Invalidate packet and return failure */
|
|
gatt_pkt[0] = GATT_TYPE_INVALID;
|
|
/* TODO: Disconnect GATT per last paragraph sec 6.6 */
|
|
return 0;
|
|
}
|
|
|
|
memcpy(gatt_pkt + gatt_size, data, size);
|
|
gatt_size += size;
|
|
|
|
/* We are good to this point, but incomplete */
|
|
return 0;
|
|
|
|
default:
|
|
case GATT_SAR_COMPLETE:
|
|
gatt_size = 1;
|
|
gatt_pkt[0] = type;
|
|
|
|
/* fall through */
|
|
|
|
case GATT_SAR_LAST:
|
|
if (gatt_pkt[0] != type ||
|
|
gatt_size + size > MAX_GATT_SIZE) {
|
|
|
|
/* Invalidate packet and return failure */
|
|
gatt_pkt[0] = GATT_TYPE_INVALID;
|
|
/* Disconnect GATT per last paragraph sec 6.6 */
|
|
return 0;
|
|
}
|
|
|
|
memcpy(gatt_pkt + gatt_size, data, size);
|
|
gatt_size += size;
|
|
*pkt = gatt_pkt;
|
|
return gatt_size;
|
|
}
|
|
}
|
|
|
|
static bool pipe_write(struct io *io, void *user_data)
|
|
{
|
|
struct write_data *data = user_data;
|
|
struct iovec iov[2];
|
|
uint8_t sar;
|
|
uint8_t max_len;
|
|
|
|
if (data == NULL)
|
|
return true;
|
|
|
|
max_len = write_mtu ? write_mtu - 3 - 1 : GATT_MTU - 3 - 1;
|
|
print_byte_array("GATT-TX:\t", data->gatt_data, data->gatt_len);
|
|
|
|
sar = data->gatt_data[0];
|
|
|
|
data->iov.iov_base = data->gatt_data + 1;
|
|
data->iov.iov_len--;
|
|
|
|
sar = data->gatt_data[0] & GATT_TYPE_MASK;
|
|
data->gatt_len--;
|
|
|
|
if (data->gatt_len > max_len) {
|
|
sar |= GATT_SAR_FIRST;
|
|
data->iov.iov_len = max_len;
|
|
}
|
|
|
|
iov[0].iov_base = &sar;
|
|
iov[0].iov_len = sizeof(sar);
|
|
|
|
while (1) {
|
|
int err;
|
|
|
|
iov[1] = data->iov;
|
|
|
|
err = io_send(io, iov, 2);
|
|
if (err < 0) {
|
|
bt_shell_printf("Failed to write: %s\n", strerror(-err));
|
|
write_data_free(data);
|
|
return false;
|
|
}
|
|
|
|
switch (sar & GATT_SAR_MASK) {
|
|
case GATT_SAR_FIRST:
|
|
case GATT_SAR_CONTINUE:
|
|
data->gatt_len -= max_len;
|
|
data->iov.iov_base = data->iov.iov_base + max_len;
|
|
|
|
sar &= GATT_TYPE_MASK;
|
|
if (max_len < data->gatt_len) {
|
|
data->iov.iov_len = max_len;
|
|
sar |= GATT_SAR_CONTINUE;
|
|
} else {
|
|
data->iov.iov_len = data->gatt_len;
|
|
sar |= GATT_SAR_LAST;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
if(data->cb)
|
|
data->cb(NULL, data->user_data);
|
|
write_data_free(data);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void write_io_destroy(void)
|
|
{
|
|
io_destroy(write_io);
|
|
write_io = NULL;
|
|
write_mtu = 0;
|
|
}
|
|
|
|
static void notify_io_destroy(void)
|
|
{
|
|
io_destroy(notify_io);
|
|
notify_io = NULL;
|
|
notify_mtu = 0;
|
|
}
|
|
|
|
static bool pipe_hup(struct io *io, void *user_data)
|
|
{
|
|
bt_shell_printf("%s closed\n", io == notify_io ? "Notify" : "Write");
|
|
|
|
if (io == notify_io)
|
|
notify_io_destroy();
|
|
else
|
|
write_io_destroy();
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct io *pipe_io_new(int fd)
|
|
{
|
|
struct io *io;
|
|
|
|
io = io_new(fd);
|
|
|
|
io_set_close_on_destroy(io, true);
|
|
|
|
io_set_disconnect_handler(io, pipe_hup, NULL, NULL);
|
|
|
|
return io;
|
|
}
|
|
|
|
static void acquire_write_reply(DBusMessage *message, void *user_data)
|
|
{
|
|
struct write_data *data = user_data;
|
|
DBusError error;
|
|
int fd;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
if (dbus_set_error_from_message(&error, message) == TRUE) {
|
|
dbus_error_free(&error);
|
|
bt_shell_printf("Failed to write\n");
|
|
write_data_free(data);
|
|
return;
|
|
}
|
|
|
|
if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd,
|
|
DBUS_TYPE_UINT16, &write_mtu,
|
|
DBUS_TYPE_INVALID) == false)) {
|
|
bt_shell_printf("Invalid AcquireWrite response\n");
|
|
return;
|
|
}
|
|
|
|
bt_shell_printf("AcquireWrite success: fd %d MTU %u\n", fd, write_mtu);
|
|
|
|
write_io = pipe_io_new(fd);
|
|
|
|
pipe_write(write_io, data);
|
|
}
|
|
|
|
static void acquire_setup(DBusMessageIter *iter, void *user_data)
|
|
{
|
|
DBusMessageIter dict;
|
|
|
|
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
DBUS_TYPE_STRING_AS_STRING
|
|
DBUS_TYPE_VARIANT_AS_STRING
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
|
|
&dict);
|
|
|
|
dbus_message_iter_close_container(iter, &dict);
|
|
}
|
|
|
|
bool mesh_gatt_write(GDBusProxy *proxy, uint8_t *buf, uint16_t len,
|
|
GDBusReturnFunction cb, void *user_data)
|
|
{
|
|
struct write_data *data;
|
|
|
|
if (!buf || !len)
|
|
return false;
|
|
|
|
if (len > 69)
|
|
return false;
|
|
|
|
data = g_new0(struct write_data, 1);
|
|
if (!data)
|
|
return false;
|
|
|
|
/* TODO: should keep in queue in case we need to cancel write? */
|
|
|
|
data->gatt_len = len;
|
|
data->gatt_data = g_memdup(buf, len);
|
|
data->gatt_data[0] &= GATT_TYPE_MASK;
|
|
data->iov.iov_base = data->gatt_data;
|
|
data->iov.iov_len = len;
|
|
data->proxy = proxy;
|
|
data->user_data = user_data;
|
|
data->cb = cb;
|
|
|
|
if (write_io)
|
|
return pipe_write(write_io, data);
|
|
|
|
if (g_dbus_proxy_method_call(proxy, "AcquireWrite",
|
|
acquire_setup, acquire_write_reply,
|
|
data, NULL) == FALSE) {
|
|
bt_shell_printf("Failed to AcquireWrite\n");
|
|
write_data_free(data);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void notify_reply(DBusMessage *message, void *user_data)
|
|
{
|
|
struct notify_data *data = user_data;
|
|
DBusError error;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
if (dbus_set_error_from_message(&error, message) == TRUE) {
|
|
bt_shell_printf("Failed to %s notify: %s\n",
|
|
data->enable ? "start" : "stop", error.name);
|
|
dbus_error_free(&error);
|
|
goto done;
|
|
}
|
|
|
|
bt_shell_printf("Notify %s\n", data->enable ? "started" : "stopped");
|
|
|
|
done:
|
|
if (data->cb)
|
|
data->cb(message, data->user_data);
|
|
|
|
g_free(data);
|
|
}
|
|
|
|
static bool pipe_read(struct io *io, bool prov, void *user_data)
|
|
{
|
|
struct mesh_node *node = user_data;
|
|
uint8_t buf[512];
|
|
uint8_t *res;
|
|
int fd = io_get_fd(io);
|
|
ssize_t len;
|
|
|
|
if (io != notify_io)
|
|
return true;
|
|
|
|
while ((len = read(fd, buf, sizeof(buf)))) {
|
|
if (len <= 0)
|
|
break;
|
|
|
|
res = buf;
|
|
mesh_gatt_sar(&res, len);
|
|
|
|
if (prov)
|
|
prov_data_ready(node, res, len);
|
|
else
|
|
net_data_ready(res, len);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool pipe_read_prov(struct io *io, void *user_data)
|
|
{
|
|
return pipe_read(io, true, user_data);
|
|
}
|
|
|
|
static bool pipe_read_proxy(struct io *io, void *user_data)
|
|
{
|
|
return pipe_read(io, false, user_data);
|
|
}
|
|
|
|
static void acquire_notify_reply(DBusMessage *message, void *user_data)
|
|
{
|
|
struct notify_data *data = user_data;
|
|
DBusMessageIter iter;
|
|
DBusError error;
|
|
int fd;
|
|
const char *uuid;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
if (dbus_set_error_from_message(&error, message) == TRUE) {
|
|
dbus_error_free(&error);
|
|
if (g_dbus_proxy_method_call(data->proxy, "StartNotify", NULL,
|
|
notify_reply, data, NULL) == FALSE) {
|
|
bt_shell_printf("Failed to StartNotify\n");
|
|
g_free(data);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (notify_io) {
|
|
io_destroy(notify_io);
|
|
notify_io = NULL;
|
|
}
|
|
|
|
notify_mtu = 0;
|
|
|
|
if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd,
|
|
DBUS_TYPE_UINT16, ¬ify_mtu,
|
|
DBUS_TYPE_INVALID) == false)) {
|
|
if (g_dbus_proxy_method_call(data->proxy, "StartNotify", NULL,
|
|
notify_reply, data, NULL) == FALSE) {
|
|
bt_shell_printf("Failed to StartNotify\n");
|
|
g_free(data);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bt_shell_printf("AcquireNotify success: fd %d MTU %u\n", fd, notify_mtu);
|
|
|
|
if (g_dbus_proxy_get_property(data->proxy, "UUID", &iter) == FALSE)
|
|
goto done;
|
|
|
|
notify_io = pipe_io_new(fd);
|
|
|
|
dbus_message_iter_get_basic(&iter, &uuid);
|
|
|
|
if (!bt_uuid_strcmp(uuid, MESH_PROV_DATA_OUT_UUID_STR))
|
|
io_set_read_handler(notify_io, pipe_read_prov, data->user_data,
|
|
NULL);
|
|
else if (!bt_uuid_strcmp(uuid, MESH_PROXY_DATA_OUT_UUID_STR))
|
|
io_set_read_handler(notify_io, pipe_read_proxy, data->user_data,
|
|
NULL);
|
|
|
|
done:
|
|
if (data->cb)
|
|
data->cb(message, data->user_data);
|
|
|
|
g_free(data);
|
|
}
|
|
|
|
bool mesh_gatt_notify(GDBusProxy *proxy, bool enable, GDBusReturnFunction cb,
|
|
void *user_data)
|
|
{
|
|
struct notify_data *data;
|
|
DBusMessageIter iter;
|
|
const char *method;
|
|
GDBusSetupFunction setup = NULL;
|
|
|
|
data = g_new0(struct notify_data, 1);
|
|
data->proxy = proxy;
|
|
data->enable = enable;
|
|
data->cb = cb;
|
|
data->user_data = user_data;
|
|
|
|
if (enable == TRUE) {
|
|
if (g_dbus_proxy_get_property(proxy, "NotifyAcquired", &iter)) {
|
|
method = "AcquireNotify";
|
|
cb = acquire_notify_reply;
|
|
setup = acquire_setup;
|
|
} else {
|
|
method = "StartNotify";
|
|
cb = notify_reply;
|
|
}
|
|
} else {
|
|
if (notify_io) {
|
|
notify_io_destroy();
|
|
if (cb)
|
|
cb(NULL, user_data);
|
|
return true;
|
|
} else {
|
|
method = "StopNotify";
|
|
cb = notify_reply;
|
|
}
|
|
}
|
|
|
|
if (g_dbus_proxy_method_call(proxy, method, setup, cb,
|
|
data, NULL) == FALSE) {
|
|
bt_shell_printf("Failed to %s\n", method);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|