| /* |
| * |
| * 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) |