blob: 693577a3a0a337796152cdaff9306d084f0570c8 [file] [log] [blame]
/*
*
* 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, len_sar;
if (io != notify_io)
return true;
while ((len = read(fd, buf, sizeof(buf)))) {
if (len <= 0)
break;
res = buf;
len_sar = mesh_gatt_sar(&res, len);
if (len_sar) {
if (prov)
prov_data_ready(node, res, len_sar);
else
net_data_ready(res, len_sar);
}
}
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, &notify_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;
}