blob: f79901cd6765110c8a7a0a0075e23fe2c9d0061e [file] [log] [blame]
/*
*
* Dropcam BTLE integration for BlueZ
*
* Copyright (C) 2013 Dropcam Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/* This module implements the Dropcam BTLE profile for mobile setup.
*
* Notes:
* - General structure and "watcher" implementation is modeled after the
* cyclingspeed and thermometer profile implementations.
* - This module exposes a new DBus interface (com.dropcam.BTLEMessageDispatch1)
* which can be used to send and receive messages from a connected Dropcam BTLE setup
* device.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdbool.h>
#include <glib.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <gdbus/gdbus.h>
#include "src/dbus-common.h"
#include "src/adapter.h"
#include "lib/uuid.h"
#include "src/plugin.h"
#include "src/hcid.h"
#include "src/log.h"
#include "src/device.h"
#include "src/error.h"
#include "attrib/gattrib.h"
#include "attrib/gatt-service.h"
#include "attrib/att.h"
#include "attrib/gatt.h"
#include "attrib/att-database.h"
#include "dropcam_btle_profile_def.h"
#define DROPCAM_OBJECT_PATH "/com/dropcam"
#define DROPCAM_INTERFACE "com.dropcam.BTLEMessageDispatch1"
#define DROPCAM_WATCHER_INTERFACE "com.dropcam.BTLEDispatchWatcher1"
#define MTU_SIZE 20
struct pending_read {
int payload_len;
uint8_t payload[MTU_SIZE];
};
struct message {
uint8_t *bytes;
int len;
};
struct watcher {
guint id;
char *srv;
char *path;
};
struct dropcam_adapter {
struct btd_adapter *adapter;
uint8_t last_read_seqnum;
uint8_t last_write_seqnum;
int cur_message_pos;
uint8_t message_payload[0xffff];
GSList *pending_read_payloads;
};
static GSList *adapters = NULL;
static GSList *watchers = NULL;
static int adapter_cmp(gconstpointer a, gconstpointer b)
{
const struct dropcam_adapter *dc_adapter = a;
const struct btd_adapter *adapter = b;
return dc_adapter->adapter == adapter ? 0 : -1;
}
static struct dropcam_adapter *find_dropcam_adapter(struct btd_adapter *adapter) {
GSList *l = g_slist_find_custom(adapters, adapter, adapter_cmp);
return l ? l->data : NULL;
}
static void update_watcher(gpointer data, gpointer user_data) {
struct watcher *w = data;
struct dropcam_adapter *dc_adapter = user_data;
const char *path = DROPCAM_OBJECT_PATH;
DBusMessageIter iter;
DBusMessageIter array;
DBusMessage *msg;
msg = dbus_message_new_method_call(w->srv, w->path, DROPCAM_WATCHER_INTERFACE, "MessageReceived");
if (msg == NULL)
return;
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path);
uint8_t *payload_ptr = dc_adapter->message_payload;
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array);
dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
&payload_ptr, dc_adapter->cur_message_pos);
dbus_message_iter_close_container(&iter, &array);
dbus_message_set_no_reply(msg, TRUE);
DBG("Sending MessageReceived to: %s: %s", w->srv, w->path);
g_dbus_send_message(btd_get_dbus_connection(), msg);
}
static uint8_t dropcam_tx_read(struct attribute *a, struct btd_device *device, gpointer user_data) {
struct dropcam_adapter *dc_adapter = user_data;
if (dc_adapter->pending_read_payloads == NULL) {
/* nothing to send so increment the sequence number and return an empty message */
uint8_t seq_num = dc_adapter->last_read_seqnum + 1;
attrib_db_update(dc_adapter->adapter, a->handle, NULL, &seq_num, sizeof(seq_num), NULL);
dc_adapter->last_read_seqnum = seq_num;
} else {
/* return the next pending read */
struct pending_read* pr = dc_adapter->pending_read_payloads->data;
DBG("Sending %d byte payload", pr->payload_len);
attrib_db_update(dc_adapter->adapter, a->handle, NULL, pr->payload, pr->payload_len, NULL);
dc_adapter->pending_read_payloads = g_slist_remove(dc_adapter->pending_read_payloads, pr);
g_free(pr);
}
return 0;
}
static uint8_t dropcam_tx_write(struct attribute *a, struct btd_device *device, gpointer user_data) {
struct dropcam_adapter *dc_adapter = user_data;
if (a->len > 0) {
uint8_t expected_seq_num = dc_adapter->last_write_seqnum + 1;
struct DropcamBTLEPayloadHeader *payload_header = (struct DropcamBTLEPayloadHeader *)a->data;
uint8_t *payload_bytes = a->data + sizeof(struct DropcamBTLEPayloadHeader);
int payload_bytes_len = a->len - sizeof(struct DropcamBTLEPayloadHeader);
DBG("Received payload with sequence number: %d, length: %d", payload_header->seqnum, payload_bytes_len);
if (payload_header->seqnum != expected_seq_num) {
warn("Received out of order write! Expected: %d, received: %d", expected_seq_num, payload_header->seqnum);
}
if (a->len > 1) {
if (dc_adapter->cur_message_pos == -1) {
/* we received data so we're starting a new message */
dc_adapter->cur_message_pos = 0;
}
if (dc_adapter->cur_message_pos + payload_bytes_len >= sizeof(dc_adapter->message_payload)) {
DBG("Message too large!");
return;
}
uint8_t *cur_pos = dc_adapter->message_payload + dc_adapter->cur_message_pos;
memcpy(cur_pos, payload_bytes, payload_bytes_len);
dc_adapter->cur_message_pos += payload_bytes_len;
} else {
if (dc_adapter->cur_message_pos != -1) {
/* received an empty payload - pending message is complete */
DBG("Received message -- length: %d", dc_adapter->cur_message_pos);
/* send the message to the watchers and reset the current message */
g_slist_foreach(watchers, update_watcher, dc_adapter);
dc_adapter->cur_message_pos = -1;
}
}
dc_adapter->last_write_seqnum = payload_header->seqnum;
}
return 0;
}
static void queue_pending_read_packet(struct dropcam_adapter *dc_adapter, uint8_t *packet, int packet_len) {
uint8_t seq_num = dc_adapter->last_read_seqnum + 1;
struct pending_read *pr = g_new0(struct pending_read, 1);
pr->payload_len = packet_len + 1;
struct DropcamBTLEPayloadHeader *pkt_header = (struct DropcamBTLEPayloadHeader *)pr->payload;
pkt_header->seqnum = seq_num;
if (packet_len > 0) {
memcpy(pr->payload + 1, packet, packet_len);
}
DBG("Queuing pending read: %d, %d", seq_num, packet_len + 1);
dc_adapter->pending_read_payloads = g_slist_append(dc_adapter->pending_read_payloads, pr);
dc_adapter->last_read_seqnum = seq_num;
}
static void queue_pending_read_message(struct dropcam_adapter *dc_adapter, struct message *msg) {
uint8_t *packet = msg->bytes;
int bytes_remaining = msg->len;
/* Split the message into smaller packets */
while (bytes_remaining > 0) {
int next_packet_len = MIN(MTU_SIZE - sizeof(struct DropcamBTLEPayloadHeader), bytes_remaining);
queue_pending_read_packet(dc_adapter, packet, next_packet_len);
packet += next_packet_len;
bytes_remaining -= next_packet_len;
}
/* Queue a final empty packet to signal that the message is complete */
queue_pending_read_packet(dc_adapter, NULL, 0);
}
static gboolean register_dropcam_service(struct dropcam_adapter *dc_adapter) {
bt_uuid_t svc_uuid;
bt_string_to_uuid(&svc_uuid, DROPCAM_SVC_UUID);
bt_uuid_t tx_read_chr_uuid;
bt_string_to_uuid(&tx_read_chr_uuid, DROPCAM_TRANSFER_READ_CHR_UUID);
bt_uuid_t tx_write_chr_uuid;
bt_string_to_uuid(&tx_write_chr_uuid, DROPCAM_TRANSFER_WRITE_CHR_UUID);
/* Dropcam Service */
gboolean svc_added = gatt_service_add(
dc_adapter->adapter,
GATT_PRIM_SVC_UUID, &svc_uuid,
/* Dropcam Transfer Write */
GATT_OPT_CHR_UUID, &tx_write_chr_uuid,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_WRITE,
GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE,
dropcam_tx_write, dc_adapter,
/* Dropcam Transfer Read */
GATT_OPT_CHR_UUID, &tx_read_chr_uuid,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
dropcam_tx_read, dc_adapter,
GATT_OPT_INVALID);
}
static void remove_watcher(gpointer user_data) {
struct watcher *watcher = user_data;
g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id);
}
static void dropcam_destroy(gpointer user_data) {
g_slist_free_full(watchers, remove_watcher);
watchers = NULL;
}
static void watcher_exit_cb(DBusConnection *conn, void *user_data) {
struct watcher *watcher = user_data;
DBG("Dropcam watcher [%s] disconnected", watcher->path);
watchers = g_slist_remove(watchers, watcher);
g_dbus_remove_watch(conn, watcher->id);
}
static int cmp_watcher(gconstpointer a, gconstpointer b) {
const struct watcher *watcher = a;
const struct watcher *match = b;
int ret;
ret = g_strcmp0(watcher->srv, match->srv);
if (ret != 0)
return ret;
return g_strcmp0(watcher->path, match->path);
}
static void destroy_watcher(gpointer user_data) {
struct watcher *watcher = user_data;
g_free(watcher->path);
g_free(watcher->srv);
g_free(watcher);
}
static struct watcher *find_watcher(GSList *list, const char *sender, const char *path) {
struct watcher *match;
GSList *l;
match = g_new0(struct watcher, 1);
match->srv = g_strdup(sender);
match->path = g_strdup(path);
l = g_slist_find_custom(list, match, cmp_watcher);
destroy_watcher(match);
if (l != NULL)
return l->data;
return NULL;
}
static void queue_message_for_adapter(gpointer data, gpointer user_data) {
struct dropcam_adapter *dc_adapter = data;
struct message *msg = user_data;
queue_pending_read_message(dc_adapter, msg);
}
static DBusMessage *send_message(DBusConnection *conn, DBusMessage *msg, void *data) {
struct message msg_data;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &msg_data.bytes, &msg_data.len, DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
DBG("Received message of length: %d", msg_data.len);
g_slist_foreach(adapters, queue_message_for_adapter, &msg_data);
return dbus_message_new_method_return(msg);
}
static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg, void *data) {
struct watcher *watcher;
const char *sender = dbus_message_get_sender(msg);
char *path;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
watcher = find_watcher(watchers, sender, path);
if (watcher != NULL)
return btd_error_already_exists(msg);
watcher = g_new0(struct watcher, 1);
watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit_cb, watcher, destroy_watcher);
watcher->srv = g_strdup(sender);
watcher->path = g_strdup(path);
watchers = g_slist_prepend(watchers, watcher);
DBG("Dropcam watcher [%s] registered", path);
return dbus_message_new_method_return(msg);
}
static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg, void *data) {
struct watcher *watcher;
const char *sender = dbus_message_get_sender(msg);
char *path;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
watcher = find_watcher(watchers, sender, path);
if (watcher == NULL)
return btd_error_does_not_exist(msg);
watchers = g_slist_remove(watchers, watcher);
g_dbus_remove_watch(conn, watcher->id);
DBG("Dropcam watcher [%s] unregistered", path);
return dbus_message_new_method_return(msg);
}
static const GDBusMethodTable dropcam_methods[] = {
{ GDBUS_METHOD("RegisterWatcher", GDBUS_ARGS({ "agent", "o" }), NULL, register_watcher) },
{ GDBUS_METHOD("UnregisterWatcher", GDBUS_ARGS({ "agent", "o" }), NULL, unregister_watcher) },
{ GDBUS_METHOD("SendMessage", GDBUS_ARGS({ "message", "ay" }), NULL, send_message) },
{ }
};
static int dropcam_adapter_probe(struct btd_adapter *adapter) {
struct dropcam_adapter *dc_adapter = g_new0(struct dropcam_adapter, 1);
dc_adapter->adapter = btd_adapter_ref(adapter);
dc_adapter->last_write_seqnum = 0xff;
dc_adapter->last_read_seqnum = 0xff;
register_dropcam_service(dc_adapter);
adapters = g_slist_append(adapters, dc_adapter);
return 0;
}
static void dropcam_adapter_remove(struct btd_adapter *adapter) {
struct dropcam_adapter *dc_adapter;
dc_adapter = find_dropcam_adapter(adapter);
if (!dc_adapter)
return;
adapters = g_slist_remove(adapters, dc_adapter);
btd_adapter_unref(dc_adapter->adapter);
g_free(dc_adapter);
}
static struct btd_adapter_driver dropcam_adapter_driver = {
.name = "dropcam-adapter-driver",
.probe = dropcam_adapter_probe,
.remove = dropcam_adapter_remove,
};
static int dropcam_init(void) {
if (!g_dbus_register_interface(btd_get_dbus_connection(),
DROPCAM_OBJECT_PATH, DROPCAM_INTERFACE,
dropcam_methods, NULL, NULL, NULL,
dropcam_destroy)) {
error("D-Bus failed to register %s interface", DROPCAM_INTERFACE);
return -EIO;
}
return btd_register_adapter_driver(&dropcam_adapter_driver);
}
static void dropcam_exit(void) {
g_dbus_unregister_interface(btd_get_dbus_connection(),
DROPCAM_OBJECT_PATH, DROPCAM_INTERFACE);
btd_unregister_adapter_driver(&dropcam_adapter_driver);
}
BLUETOOTH_PLUGIN_DEFINE(dropcam, VERSION,
BLUETOOTH_PLUGIN_PRIORITY_LOW,
dropcam_init, dropcam_exit)