Merge BlueZ 5.43 to master
Release of BlueZ 5.43
29th October 2016, 09:44 am by Johan Hedberg
This is almost purely a bug-fix release with fixes to HoG, ATT and PAN.
There’s also a fix for a regression in 5.42 that caused connection failures
for external profiles like OBEX. Feature-wise there’s one notable addition:
LE privacy. By enabling this in main.conf it’s now possible to make BlueZ
generate a local Identity Resolving Key (IRK) and use Resolvable Private
Addresses (RPAs) for itself.
Release of BlueZ 5.42
26th September 2016, 06:33 pm by Johan Hedberg
The major API change with this release is that the GATT D-Bus API is no longer
marked as experimental. This should hopefully encourage the creation of more
applications using it. Feature-wise, btmon received support for decoding full
mgmt message traces – a feature that is available in the bluetooth-next kernel
tree, i.e. on its way to the 4.9 kernel release. In addition to these changes,
this release contains also fixes in areas such as PBAP, transport selection
(BR/EDR vs LE), ATT and A2DP.
NOTE:
Adjustments were necessary to audioOverBle, battery, and oad profiles due to:
commit 82666b345e14b0a723e3d7cf8e56003edef22fed
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Mon Sep 26 16:46:09 2016 +0300
core: Remove attio callbacks
Change-Id: I8d25c7c866f76c23fcca284e3b20b237b4b1b7eb
diff --git a/Makefile.plugins b/Makefile.plugins
index ecf6f08..192a2cc 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -5,6 +5,9 @@
builtin_modules += wiimote
builtin_sources += plugins/wiimote.c
+builtin_modules += gfrm
+builtin_sources += plugins/gfrm.c
+
builtin_modules += autopair
builtin_sources += plugins/autopair.c
@@ -68,6 +71,12 @@
EXTRA_DIST += profiles/input/suspend-dummy.c
+builtin_modules += oad
+builtin_sources += profiles/oad/oad.c
+
+builtin_modules += battery
+builtin_sources += profiles/battery/battery.c
+
if EXPERIMENTAL
builtin_modules += health
builtin_sources += profiles/health/mcap.h profiles/health/mcap.c \
@@ -94,3 +103,9 @@
-no-undefined @UDEV_LIBS@
plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden @UDEV_CFLAGS@
endif
+
+builtin_modules += audioOverBle
+builtin_sources += profiles/audioOverBle/main.c profiles/audioOverBle/manager.h \
+ profiles/audioOverBle/manager.c \
+ profiles/audioOverBle/btvoice.h \
+ profiles/audioOverBle/btvoice.c
diff --git a/Makefile.tools b/Makefile.tools
index 7706dc7..7ed442c 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -343,8 +343,8 @@
endif
if READLINE
-noinst_PROGRAMS += attrib/gatttool tools/btmgmt \
- tools/obex-client-tool tools/obex-server-tool \
+bin_PROGRAMS += attrib/gatttool tools/btmgmt
+noinst_PROGRAMS += tools/obex-client-tool tools/obex-server-tool \
tools/bluetooth-player tools/obexctl
attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
@@ -422,3 +422,5 @@
test/pbap-client test/map-client test/example-advertisement \
test/example-gatt-server test/example-gatt-client \
test/test-gatt-profile
+
+test_scripts += test/gfiber-agent test/unplug-GFRM100 test/gfrm-inventory
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
index 2e1e39a..6bb6e6d 100644
--- a/attrib/gattrib.c
+++ b/attrib/gattrib.c
@@ -151,7 +151,9 @@
__sync_fetch_and_add(&attrib->ref_count, 1);
- DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count);
+ // suppress very common debug message
+ if (attrib->ref_count == 1 || attrib->ref_count > 20)
+ DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count);
return attrib;
}
@@ -184,7 +186,9 @@
if (!attrib)
return;
- DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1);
+ // suppress very common debug message
+ if (attrib->ref_count - 1 == 0 || attrib->ref_count - 1 > 20)
+ DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1);
if (__sync_sub_and_fetch(&attrib->ref_count, 1))
return;
@@ -451,6 +455,14 @@
return attrib->buf;
}
+int g_attrib_get_mtu(GAttrib *attrib)
+{
+ if (!attrib)
+ return -1;
+
+ return bt_att_get_mtu(attrib->att);
+}
+
gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu)
{
if (!attrib)
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
index 611f952..6cd3bf9 100644
--- a/attrib/gattrib.h
+++ b/attrib/gattrib.h
@@ -65,6 +65,7 @@
GDestroyNotify notify);
uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len);
+int g_attrib_get_mtu(GAttrib *attrib);
gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu);
gboolean g_attrib_unregister(GAttrib *attrib, guint id);
diff --git a/lib/hci.c b/lib/hci.c
index a3f5d96..5ce39bd 100644
--- a/lib/hci.c
+++ b/lib/hci.c
@@ -2345,6 +2345,57 @@
return 0;
}
+int hci_read_page_scan_type(int dd, uint8_t *type, int to)
+{
+ read_page_scan_type_rp rp;
+ struct hci_request rq;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_READ_PAGE_SCAN_TYPE;
+ rq.rparam = &rp;
+ rq.rlen = READ_PAGE_SCAN_TYPE_RP_SIZE;
+
+ if (hci_send_req(dd, &rq, to) < 0)
+ return -1;
+
+ if (rp.status) {
+ errno = EIO;
+ return -1;
+ }
+
+ *type = rp.type;
+ return 0;
+}
+
+int hci_write_page_scan_type(int dd, uint8_t type, int to)
+{
+ write_page_scan_type_cp cp;
+ write_page_scan_type_rp rp;
+ struct hci_request rq;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.type = type;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_WRITE_PAGE_SCAN_TYPE;
+ rq.cparam = &cp;
+ rq.clen = WRITE_PAGE_SCAN_TYPE_CP_SIZE;
+ rq.rparam = &rp;
+ rq.rlen = WRITE_PAGE_SCAN_TYPE_RP_SIZE;
+
+ if (hci_send_req(dd, &rq, to) < 0)
+ return -1;
+
+ if (rp.status) {
+ errno = EIO;
+ return -1;
+ }
+
+ return 0;
+}
+
int hci_read_inquiry_mode(int dd, uint8_t *mode, int to)
{
read_inquiry_mode_rp rp;
diff --git a/lib/hci.h b/lib/hci.h
index 794333b..3ef4311 100644
--- a/lib/hci.h
+++ b/lib/hci.h
@@ -1074,10 +1074,23 @@
#define WRITE_INQUIRY_MODE_RP_SIZE 1
#define OCF_READ_PAGE_SCAN_TYPE 0x0046
+typedef struct {
+ uint8_t status;
+ uint8_t type;
+} __attribute__ ((packed)) read_page_scan_type_rp;
+#define READ_PAGE_SCAN_TYPE_RP_SIZE 2
#define OCF_WRITE_PAGE_SCAN_TYPE 0x0047
- #define PAGE_SCAN_TYPE_STANDARD 0x00
- #define PAGE_SCAN_TYPE_INTERLACED 0x01
+typedef struct {
+ uint8_t type;
+} __attribute__ ((packed)) write_page_scan_type_cp;
+#define WRITE_PAGE_SCAN_TYPE_CP_SIZE 1
+typedef struct {
+ uint8_t status;
+} __attribute__ ((packed)) write_page_scan_type_rp;
+#define WRITE_PAGE_SCAN_TYPE_RP_SIZE 1
+#define PAGE_SCAN_TYPE_STANDARD 0x00
+#define PAGE_SCAN_TYPE_INTERLACED 0x01
#define OCF_READ_AFH_MODE 0x0048
typedef struct {
diff --git a/lib/hci_lib.h b/lib/hci_lib.h
index 55aeb17..602a4cb 100644
--- a/lib/hci_lib.h
+++ b/lib/hci_lib.h
@@ -92,6 +92,8 @@
int hci_exit_park_mode(int dd, uint16_t handle, int to);
int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to);
int hci_write_inquiry_scan_type(int dd, uint8_t type, int to);
+int hci_read_page_scan_type(int dd, uint8_t *type, int to);
+int hci_write_page_scan_type(int dd, uint8_t type, int to);
int hci_read_inquiry_mode(int dd, uint8_t *mode, int to);
int hci_write_inquiry_mode(int dd, uint8_t mode, int to);
int hci_read_afh_mode(int dd, uint8_t *mode, int to);
diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index f4b54af..ebc90e0 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -3105,6 +3105,8 @@
avdtp_packet(&frame);
break;
default:
+ /* limit amount of data to log */
+ if (size > 32) size = 32;
packet_hexdump(data, size);
break;
}
diff --git a/monitor/main.c b/monitor/main.c
index f9bca22..c04a94a 100644
--- a/monitor/main.c
+++ b/monitor/main.c
@@ -70,6 +70,7 @@
"\t-T, --date Show time and date information\n"
"\t-S, --sco Dump SCO traffic\n"
"\t-E, --ellisys [ip] Send Ellisys HCI Injection\n"
+ "\t-A, --no_leadv Suppress LE_ADV messages\n"
"\t-h, --help Show help options\n");
}
@@ -86,6 +87,7 @@
{ "date", no_argument, NULL, 'T' },
{ "sco", no_argument, NULL, 'S' },
{ "ellisys", required_argument, NULL, 'E' },
+ { "no_leadv", no_argument, NULL, 'A' },
{ "todo", no_argument, NULL, '#' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
@@ -106,6 +108,7 @@
int exit_status;
sigset_t mask;
+ setvbuf(stdout, NULL, _IOLBF, 0);
mainloop_init();
filter_mask |= PACKET_FILTER_SHOW_TIME_OFFSET;
@@ -113,7 +116,7 @@
for (;;) {
int opt;
- opt = getopt_long(argc, argv, "d:r:w:a:s:p:i:tTSE:vh",
+ opt = getopt_long(argc, argv, "d:r:w:a:s:p:i:tTSAE:vh",
main_options, NULL);
if (opt < 0)
break;
@@ -167,6 +170,9 @@
case 'S':
filter_mask |= PACKET_FILTER_SHOW_SCO_DATA;
break;
+ case 'A':
+ filter_mask |= PACKET_FILTER_SUPPRESS_LE_ADV;
+ break;
case 'E':
ellisys_server = optarg;
ellisys_port = 24352;
diff --git a/monitor/packet.c b/monitor/packet.c
index 6272562..45c9e8c 100644
--- a/monitor/packet.c
+++ b/monitor/packet.c
@@ -3262,12 +3262,12 @@
uuid = data;
print_field(" UUID: %8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x",
- get_le32(&uuid[12]), get_le16(&uuid[10]),
- get_le16(&uuid[8]), get_le16(&uuid[6]),
- get_le32(&uuid[2]), get_le16(&uuid[0]));
+ get_be32(&uuid[0]), get_be16(&uuid[4]),
+ get_be16(&uuid[6]), get_be16(&uuid[8]),
+ get_be32(&uuid[10]), get_be16(&uuid[14]));
- major = get_le16(data + 16);
- minor = get_le16(data + 18);
+ major = get_be16(data + 16);
+ minor = get_be16(data + 18);
print_field(" Version: %u.%u", major, minor);
tx_power = *(int8_t *) (data + 20);
@@ -9038,6 +9038,14 @@
event_str = "Unknown";
}
+ if (filter_mask & PACKET_FILTER_SUPPRESS_LE_ADV && hdr->evt == 0x3e) {
+ uint8_t subevent = *((const uint8_t *) data);
+ if (subevent == 0x02) {
+ /* LE Advertising Report. */
+ return;
+ }
+ }
+
sprintf(extra_str, "(0x%2.2x) plen %d", hdr->evt, hdr->plen);
print_packet(tv, cred, '>', index, NULL, event_color, "HCI Event",
diff --git a/monitor/packet.h b/monitor/packet.h
index 354f4fe..a02ea50 100644
--- a/monitor/packet.h
+++ b/monitor/packet.h
@@ -33,6 +33,7 @@
#define PACKET_FILTER_SHOW_TIME_OFFSET (1 << 3)
#define PACKET_FILTER_SHOW_ACL_DATA (1 << 4)
#define PACKET_FILTER_SHOW_SCO_DATA (1 << 5)
+#define PACKET_FILTER_SUPPRESS_LE_ADV (1 << 31)
void packet_set_filter(unsigned long filter);
void packet_add_filter(unsigned long filter);
diff --git a/plugins/gfrm.c b/plugins/gfrm.c
new file mode 100644
index 0000000..6643022
--- /dev/null
+++ b/plugins/gfrm.c
@@ -0,0 +1,318 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+
+#define BD_ADDR_FIFO "/tmp/bd-addr-fifo"
+
+struct gfrm_device {
+ char *name;
+ uint8_t bdaddr_type;
+ uint16_t vid;
+ uint16_t pid;
+};
+
+static struct gfrm_device gfrm_devs[] = {
+ { "GFRM100", BDADDR_BREDR, 0x58, 0x2000 },
+ { "GFRM200", BDADDR_LE_PUBLIC, 0x471, 0x2210 },
+ { "HID AdvRemote", BDADDR_LE_PUBLIC, 0xD, 0x0 },
+};
+
+static struct gfrm_device *gfrm_find_dev_by_bdaddr_type(uint8_t bdaddr_type)
+{
+ uint16_t i;
+
+ for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
+ if (bdaddr_type == gfrm_devs[i].bdaddr_type) {
+ return &gfrm_devs[i];
+ }
+ }
+
+ return NULL;
+}
+
+static struct gfrm_device *gfrm_find_dev_by_vid_pid(uint16_t vid, uint16_t pid)
+{
+ uint16_t i;
+
+ for (i = 0; i < G_N_ELEMENTS(gfrm_devs); ++i) {
+ if (vid == gfrm_devs[i].vid && pid == gfrm_devs[i].pid) {
+ return &gfrm_devs[i];
+ }
+ }
+
+ return NULL;
+}
+
+static ssize_t gfrm_pincb(struct btd_adapter *adapter, struct btd_device *device,
+ char *pinbuf, bool *display, unsigned int attempt)
+{
+ uint16_t vid, pid;
+ char addr[18], name[25];
+
+ /*
+ * Only try the pin code once per device.
+ * If it's not correct then it's an unknown device.
+ */
+ if (attempt > 1)
+ return 0;
+
+ ba2str(device_get_address(device), addr);
+
+ vid = btd_device_get_vendor(device);
+ pid = btd_device_get_product(device);
+ DBG("vendor 0x%x product 0x%x", vid, pid);
+
+ device_get_name(device, name, sizeof(name));
+ name[sizeof(name) - 1] = 0;
+
+ if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+ DBG("Forcing PIN 0000 on %s at %s", name, addr);
+ memcpy(pinbuf, "0000", 4);
+ return 4;
+ }
+
+ return 0;
+}
+
+static void gfrm_disconnect_cb(struct btd_device *device, uint8_t reason)
+{
+ bdaddr_t bdaddr;
+ uint8_t bdaddr_type;
+ uint16_t vid, pid;
+ char addr[18], name[25];
+
+ memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
+ ba2str(&bdaddr, addr);
+ bdaddr_type = btd_device_get_bdaddr_type(device);
+
+ vid = btd_device_get_vendor(device);
+ pid = btd_device_get_product(device);
+
+ device_get_name(device, name, sizeof(name));
+ name[sizeof(name) - 1] = 0;
+
+ if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+ DBG("%s [%s] disconnected: reason %u", name, addr, reason);
+ }
+}
+
+static void gfrm_conn_fail_cb(struct btd_device *device, uint8_t reason)
+{
+ bdaddr_t bdaddr;
+ uint8_t bdaddr_type;
+ uint16_t vid, pid;
+ char addr[18], name[25];
+
+ memcpy(&bdaddr, device_get_address(device), sizeof(bdaddr));
+ ba2str(&bdaddr, addr);
+ bdaddr_type = btd_device_get_bdaddr_type(device);
+
+ vid = btd_device_get_vendor(device);
+ pid = btd_device_get_product(device);
+
+ device_get_name(device, name, sizeof(name));
+ name[sizeof(name) - 1] = 0;
+
+ if (gfrm_find_dev_by_vid_pid(vid, pid)) {
+ DBG("%s [%s] connection failed: reason %u", name, addr, reason);
+ }
+}
+
+static void gfrm_start_bd_addr_fifo_watch(
+ gboolean (*callback)(GIOChannel *, GIOCondition, gpointer))
+{
+ int fd;
+ GIOChannel *io;
+
+ if ((fd = open(BD_ADDR_FIFO, O_RDONLY | O_NONBLOCK)) < 0) {
+ DBG("open failed");
+ return;
+ }
+
+ if ((io = g_io_channel_unix_new(fd)) == NULL) {
+ DBG("g_io_channel_unix_new failed");
+ close(fd);
+ return;
+ }
+
+ if (!g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ callback, NULL)) {
+ DBG("g_io_add_watch failed");
+ }
+
+ g_io_channel_set_close_on_unref(io, TRUE);
+ g_io_channel_unref(io);
+}
+
+static gboolean gfrm_add_device(GIOChannel *io, GIOCondition condition,
+ gpointer data)
+{
+ int fd;
+ uint8_t bdaddr_and_type[7] = { 0 };
+ uint8_t bdaddr_type;
+ bdaddr_t bdaddr;
+ char bdaddr_str[18];
+ struct gfrm_device *gfrm_dev;
+ struct btd_adapter *adapter;
+ struct btd_device *device;
+
+ if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+ DBG("io error: condition %u", condition);
+ }
+
+ if (!(condition & (G_IO_IN))) {
+ DBG("pipe has no data to read");
+ goto reopen;
+ }
+
+ /* Read BD address from FIFO */
+ fd = g_io_channel_unix_get_fd(io);
+
+ if (read(fd, &bdaddr_and_type, sizeof(bdaddr_and_type)) != 7) {
+ DBG("read failed");
+ goto reopen;
+ }
+
+ bdaddr_type = bdaddr_and_type[6];
+ baswap(&bdaddr, (bdaddr_t*) bdaddr_and_type);
+ ba2str(&bdaddr, bdaddr_str);
+
+ gfrm_dev = gfrm_find_dev_by_bdaddr_type(bdaddr_type);
+ if (!gfrm_dev)
+ goto reopen;
+
+ DBG("Discovered %s at %s", gfrm_dev->name, bdaddr_str);
+
+ /* Add device to BlueZ stack */
+ adapter = btd_adapter_get_default();
+ if (!adapter) {
+ DBG("btd_adapter_get_default failed");
+ goto reopen;
+ }
+
+ device = btd_adapter_find_device(adapter, &bdaddr, bdaddr_type);
+ if (device) {
+ DBG("device %p paired %d bonded %d bonding %d", device,
+ device_is_paired(device, bdaddr_type),
+ device_is_bonded(device, bdaddr_type),
+ device_is_bonding(device, NULL));
+
+ if (device_is_bonding(device, NULL))
+ goto reopen;
+
+ DBG("Removing %s @ %s", gfrm_dev->name, bdaddr_str);
+ btd_device_set_temporary(device, TRUE);
+ btd_adapter_remove_device(adapter, device);
+ }
+
+ DBG("Adding %s @ %s", gfrm_dev->name, bdaddr_str);
+ device = btd_adapter_get_device(adapter, &bdaddr, bdaddr_type);
+ if (device) {
+ btd_device_device_set_name(device, gfrm_dev->name);
+ btd_device_set_pnpid(device, 0x1, gfrm_dev->vid,
+ gfrm_dev->pid, 0x0);
+ btd_device_set_temporary(device, FALSE);
+ }
+
+ /*
+ * Pairing the remote is handled in Python script:
+ * test/gfiber-agent
+ */
+
+reopen:
+ /* Reopen the FIFO (new fd, io channel, and watch) */
+ gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
+
+ /*
+ * Return FALSE, so that the watch on the old io channel is removed.
+ * That, in turn, triggers closing of io channel and file descriptor.
+ */
+ return FALSE;
+}
+
+static int gfrm_probe(struct btd_adapter *adapter)
+{
+ btd_adapter_register_pin_cb(adapter, gfrm_pincb);
+}
+
+static void gfrm_remove(struct btd_adapter *adapter)
+{
+ btd_adapter_unregister_pin_cb(adapter, gfrm_pincb);
+}
+
+static struct btd_adapter_driver gfrm_driver = {
+ .name = "gfrm",
+ .probe = gfrm_probe,
+ .remove = gfrm_remove,
+};
+
+static int gfrm_init(void)
+{
+ btd_add_disconnect_cb(gfrm_disconnect_cb);
+ btd_add_conn_fail_cb(gfrm_conn_fail_cb);
+ btd_register_adapter_driver(&gfrm_driver);
+
+ /* Create FIFO for IR-assisted pairing */
+ if (mkfifo(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH) < 0 && errno != EEXIST) {
+ DBG("mkfifo failed");
+ return 0;
+ }
+ /* Set file mode bits on the fifo since umask clears group/other */
+ chmod(BD_ADDR_FIFO, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+ S_IROTH | S_IWOTH);
+
+ /* Start watching the FIFO */
+ gfrm_start_bd_addr_fifo_watch(gfrm_add_device);
+
+ return 0;
+}
+
+static void gfrm_exit(void)
+{
+ btd_unregister_adapter_driver(&gfrm_driver);
+ btd_remove_conn_fail_cb(gfrm_conn_fail_cb);
+ btd_remove_disconnect_cb(gfrm_disconnect_cb);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(gfrm, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_HIGH, gfrm_init, gfrm_exit)
diff --git a/profiles/audioOverBle/audioOverBle.conf b/profiles/audioOverBle/audioOverBle.conf
new file mode 100644
index 0000000..c96b684
--- /dev/null
+++ b/profiles/audioOverBle/audioOverBle.conf
@@ -0,0 +1,12 @@
+# Configuration file for the audioOverBle service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+#Mode can be PROP_MODE, L2CAP_MODE or ATT_MODE
+mode=ATT_MODE
+l2capchannel=0
+
+# Configuration to allow disabling Proximity services
+# Allowed values: LinkLoss,PathLoss,FindMe
+#Disable=audioOverBle
diff --git a/profiles/audioOverBle/btvoice.c b/profiles/audioOverBle/btvoice.c
new file mode 100644
index 0000000..d6f192b
--- /dev/null
+++ b/profiles/audioOverBle/btvoice.c
@@ -0,0 +1,560 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2016 Google Fiber
+ *
+ * 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include <gdbus/gdbus.h>
+
+#include "attrib/gattrib.h"
+#include "attrib/att.h"
+#include "attrib/gatt.h"
+#include "src/adapter.h"
+#include "src/dbus-common.h"
+#include "src/device.h"
+#include "src/error.h"
+#include "src/log.h"
+#include "src/service.h"
+#include "src/shared/util.h"
+#include "src/textfile.h"
+
+#include "btvoice.h"
+
+#define AUD_START_UUID 0xFFF1
+#define AUD_CONFIG_UUID 0xFFF2
+#define AUD_STREAM_UUID 0xFFF4
+
+struct btvoice_device {
+ uint16_t id;
+ bdaddr_t bdaddr;
+ struct btd_device *device;
+ GAttrib *attrib;
+ struct gatt_primary *audioOverBle;
+ struct btvoiceenable voiceenable;
+ GSList *reports;
+ uint16_t aud_start_handle;
+ uint16_t aud_config_handle;
+ uint16_t aud_stream_handle;
+ uint16_t aud_cust1_handle;
+ uint16_t aud_cust2_handle;
+ uint16_t aud_cust4_handle;
+ uint16_t aud_cust7_handle;
+ uint16_t aud_cust8_handle;
+};
+
+struct audiostream {
+ uint8_t id;
+ uint8_t type;
+ uint16_t ccc_handle;
+ guint notifyid;
+ struct gatt_char *decl;
+ struct btvoice_device *btvoicedev;
+};
+
+static GSList *btvoicedevices = NULL;
+
+static struct btvoice_device *find_btvoicedev(struct btd_device *device) {
+ GSList *l;
+
+ for (l = btvoicedevices; l; l = l->next) {
+ struct btvoice_device *btvoicedev = l->data;
+
+ if (btvoicedev->device == device) return btvoicedev;
+ }
+
+ return NULL;
+}
+
+static void aud_cfg_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void aud_cust1a_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("cust1a failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void aud_cust1_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ struct btvoice_device *btvoicedev = user_data;
+ uint8_t data[] = { 0x0a, 0x01 };
+
+ if (status != 0) {
+ error("cust1 failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust1_handle, data,
+ sizeof(data), aud_cust1a_written_cb, btvoicedev);
+}
+
+static void aud_cust2b_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("cust2b failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void aud_cust2a_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ struct btvoice_device *btvoicedev = user_data;
+ uint8_t data[] = { 0x05, 0x00, 0x01, 0x05 };
+
+ if (status != 0) {
+ error("cust2a failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+ sizeof(data), aud_cust2b_written_cb, btvoicedev);
+}
+
+static void aud_cust2_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ struct btvoice_device *btvoicedev = user_data;
+ uint8_t data[] = { 0x05, 0x00, 0x01, 0x01 };
+
+ if (status != 0) {
+ error("cust2 failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+ sizeof(data), aud_cust2a_written_cb, btvoicedev);
+}
+
+static void aud_cust4_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void aud_cust7_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static void aud_cust8_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+#define RAS_END_CMD 0x00
+#define TI_AUDIO_PATH "\0rc_audio_ti"
+
+static void audio_value_cb(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ struct audiostream *audiostream = user_data;
+ struct btvoice_device *btvoicedev = audiostream->btvoicedev;
+ static int audio_fd = -1;
+ static uint16_t old_mtu;
+ static int sent = 0, dropped = 0;
+
+ if (audio_fd == -1) {
+ struct sockaddr_un sun;
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", TI_AUDIO_PATH);
+
+ audio_fd = socket(AF_UNIX, SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_DGRAM, 0);
+ if (connect(audio_fd, (const struct sockaddr *) &sun, sizeof(sun)) < 0) {
+ perror("connect(AF_UNIX, TI_AUDIO_PATH)");
+ close(audio_fd);
+ audio_fd = -1;
+ dropped++;
+ return;
+ }
+
+ /* Remote sends 103 byte notifications, plus metadata. */
+ old_mtu = g_attrib_get_mtu(btvoicedev->attrib);
+ g_attrib_set_mtu(btvoicedev->attrib, 105);
+ }
+
+ if (audio_fd != -1) {
+ if (len == 4 && pdu[3] == RAS_END_CMD) {
+ close(audio_fd);
+ audio_fd = -1;
+ g_attrib_set_mtu(btvoicedev->attrib, old_mtu);
+ DBG("Finished audio stream; sent = %d dropped = %d\n", sent, dropped);
+ sent = dropped = 0;
+ } else {
+ struct iovec iov[3];
+ struct msghdr mh;
+ uint8_t remote_type = 0; /* GFRM210 */
+
+ memset(&iov, 0, sizeof(iov));
+ iov[0].iov_base = &btvoicedev->bdaddr;
+ iov[0].iov_len = sizeof(btvoicedev->bdaddr);
+ iov[1].iov_base = (void *)&remote_type;
+ iov[1].iov_len = 1;
+ iov[2].iov_base = (void *)(pdu + 3); /* Skip 3 bytes of BLE header */
+ iov[2].iov_len = len - 3;
+
+ memset(&mh, 0, sizeof(mh));
+ mh.msg_iov = iov;
+ mh.msg_iovlen = 3;
+
+ if (sendmsg(audio_fd, &mh, MSG_DONTWAIT) >= 0) {
+ sent++;
+ } else {
+ dropped++;
+ }
+ }
+ }
+}
+
+static void audio_ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ struct audiostream *audiostream = user_data;
+
+ if (status != 0) {
+ error("Write audio characteristic descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+}
+
+static void audio_write_ccc(uint16_t handle, gpointer user_data) {
+ struct audiostream *audiostream = user_data;
+ struct btvoice_device *btvoicedev = audiostream->btvoicedev;
+ uint8_t value[] = {0x01, 0x00};
+
+ audiostream->notifyid = g_attrib_register(
+ btvoicedev->attrib, ATT_OP_HANDLE_NOTIFY, audiostream->decl->value_handle,
+ audio_value_cb, audiostream, NULL);
+
+ gatt_write_char(btvoicedev->attrib, handle, value, sizeof(value),
+ audio_ccc_written_cb, audiostream);
+}
+
+static void report_reference_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data) {
+ struct audiostream *report = user_data;
+
+ if (status != 0) {
+ error("Read Report Reference descriptor failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ if (plen != 3) {
+ error("Malformed ATT read response");
+ return;
+ }
+
+ report->id = pdu[1];
+ report->type = pdu[2];
+}
+
+static void discover_descriptor_cb(uint8_t status, GSList *descs,
+ void *user_data) {
+ struct audiostream *report;
+ GAttrib *attrib = NULL;
+
+ if (status != 0) {
+ error("Discover all characteristic descriptors failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for (; descs; descs = descs->next) {
+ struct gatt_desc *desc = descs->data;
+
+ switch (desc->uuid16) {
+ case GATT_CLIENT_CHARAC_CFG_UUID:
+ report = user_data;
+ report->ccc_handle = desc->handle;
+ attrib = report->btvoicedev->attrib;
+ audio_write_ccc(desc->handle, report);
+ DBG("Audio Notifications Enabled\n");
+ break;
+ case GATT_REPORT_REFERENCE:
+ report = user_data;
+ attrib = report->btvoicedev->attrib;
+ gatt_read_char(attrib, desc->handle, report_reference_cb, report);
+ DBG("Audio Stream Report Reference Characterstic Read\n");
+ break;
+ }
+ }
+}
+
+static void discover_descriptor(GAttrib *attrib, uint16_t start, uint16_t end,
+ gpointer user_data) {
+ if (start > end) {
+ DBG("Start before End\n");
+ return;
+ }
+
+ gatt_discover_desc(attrib, start, end, NULL, discover_descriptor_cb,
+ user_data);
+}
+
+static void char_discovered_cb(guint8 status, GSList *chars, void *user_data) {
+ struct btvoice_device *btvoicedev = user_data;
+ bt_uuid_t audstart_uuid, audconfig_uuid, audstream_uuid;
+ bt_uuid_t audcust1_uuid, audcust2_uuid, audcust4_uuid,
+ audcust7_uuid, audcust8_uuid;
+
+ uint128_t aud_custom1_uuid = {
+ .data = { 0xf0, 0x00, 0xfe, 0xf1, 0x04, 0x51, 0x40, 0x00,
+ 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+
+ uint128_t aud_custom2_uuid = {
+ .data = { 0xf0, 0x00, 0xfe, 0xf2, 0x04, 0x51, 0x40, 0x00,
+ 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+
+ uint128_t aud_custom4_uuid = {
+ .data = { 0xf0, 0x00, 0xfe, 0xf4, 0x04, 0x51, 0x40, 0x00,
+ 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+
+ uint128_t aud_custom7_uuid = {
+ .data = { 0xf0, 0x00, 0xfe, 0xf7, 0x04, 0x51, 0x40, 0x00,
+ 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+
+ uint128_t aud_custom8_uuid = {
+ .data = { 0xf0, 0x00, 0xfe, 0xf8, 0x04, 0x51, 0x40, 0x00,
+ 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
+ };
+
+ struct audiostream *audiostream;
+ GSList *l;
+
+ if (status != 0) {
+ const char *str = att_ecode2str(status);
+ return;
+ }
+
+ bt_uuid16_create(&audstart_uuid, AUD_START_UUID);
+ bt_uuid16_create(&audconfig_uuid, AUD_CONFIG_UUID);
+ bt_uuid16_create(&audstream_uuid, AUD_STREAM_UUID);
+ bt_uuid128_create(&audcust1_uuid, aud_custom1_uuid);
+ bt_uuid128_create(&audcust2_uuid, aud_custom2_uuid);
+ bt_uuid128_create(&audcust4_uuid, aud_custom4_uuid);
+ bt_uuid128_create(&audcust7_uuid, aud_custom7_uuid);
+ bt_uuid128_create(&audcust8_uuid, aud_custom8_uuid);
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr;
+ bt_uuid_t uuid;
+
+ chr = l->data;
+ bt_string_to_uuid(&uuid, chr->uuid);
+
+ if (bt_uuid_cmp(&uuid, &audstart_uuid) == 0) {
+ btvoicedev->aud_start_handle = chr->value_handle;
+ } else if (bt_uuid_cmp(&uuid, &audconfig_uuid) == 0) {
+ btvoicedev->aud_config_handle = chr->value_handle;
+ } else if (bt_uuid_cmp(&uuid, &audstream_uuid) == 0) {
+ audiostream = g_new0(struct audiostream, 1);
+ audiostream->btvoicedev = btvoicedev;
+ audiostream->decl = g_memdup(chr, sizeof(*chr));
+ btvoicedev->reports = g_slist_append(btvoicedev->reports, audiostream);
+
+ btvoicedev->aud_stream_handle = chr->value_handle;
+ discover_descriptor(btvoicedev->attrib, chr->value_handle,
+ btvoicedev->audioOverBle->range.end, audiostream);
+ } else if (bt_uuid_cmp(&uuid, &audcust1_uuid) == 0) {
+ uint8_t data[] = { 0x0a, 0x00 };
+ btvoicedev->aud_cust1_handle = chr->value_handle;
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust1_handle, data,
+ sizeof(data), aud_cust1_written_cb, btvoicedev);
+ } else if (bt_uuid_cmp(&uuid, &audcust2_uuid) == 0) {
+ uint8_t data[] = { 0x00, 0x05, 0x00, 0x00, 0x01 };
+ btvoicedev->aud_cust2_handle = chr->value_handle;
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust2_handle, data,
+ sizeof(data), aud_cust2_written_cb, btvoicedev);
+ } else if (bt_uuid_cmp(&uuid, &audcust4_uuid) == 0) {
+ uint8_t data[] = { 0x0f };
+ btvoicedev->aud_cust4_handle = chr->value_handle;
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust4_handle, data,
+ sizeof(data), aud_cust4_written_cb, btvoicedev);
+ } else if (bt_uuid_cmp(&uuid, &audcust7_uuid) == 0) {
+ uint8_t data[] = { 0x05 };
+ btvoicedev->aud_cust7_handle = chr->value_handle;
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust7_handle, data,
+ sizeof(data), aud_cust7_written_cb, btvoicedev);
+ } else if (bt_uuid_cmp(&uuid, &audcust8_uuid) == 0) {
+ uint8_t data[] = { 0x02 };
+ btvoicedev->aud_cust8_handle = chr->value_handle;
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_cust8_handle, data,
+ sizeof(data), aud_cust8_written_cb, btvoicedev);
+ }
+ }
+
+ if (btvoicedev->aud_config_handle) {
+ uint8_t data[] = {0x03, 0x00, 0x00};
+ gatt_write_char(btvoicedev->attrib, btvoicedev->aud_config_handle, data,
+ sizeof(data), aud_cfg_written_cb, btvoicedev);
+ }
+}
+
+
+int btvoice_accept(struct btd_service *service) {
+ struct btvoice_device *btvoicedev = btd_service_get_user_data(service);
+ struct gatt_primary *prim = btvoicedev->audioOverBle;
+ struct btd_device *device = btd_service_get_device(service);
+ GAttrib *attrib = btd_device_get_attrib(device);
+
+ btvoicedev->attrib = g_attrib_ref(attrib);
+
+ btd_service_connecting_complete(service, 0);
+
+ if (btvoicedev->voiceenable.audioOverBle) {
+ gatt_discover_char(btvoicedev->attrib, prim->range.start, prim->range.end,
+ NULL, char_discovered_cb, btvoicedev);
+ }
+
+ return 0;
+}
+
+int btvoice_disconnect(struct btd_service *service) {
+ struct btvoice_device *btvoicedev = btd_service_get_user_data(service);
+ GSList *l;
+
+ for (l = btvoicedev->reports; l; l = l->next) {
+ struct audiostream *r = l->data;
+
+ g_attrib_unregister(btvoicedev->attrib, r->notifyid);
+ }
+
+ g_attrib_unref(btvoicedev->attrib);
+ btvoicedev->attrib = NULL;
+
+ btd_service_disconnecting_complete(service, 0);
+
+ return 0;
+}
+
+static struct btvoice_device *register_btvoicedevice(
+ struct btd_service *service, struct btd_device *device)
+{
+ struct btvoice_device *btvoicedev;
+ const bdaddr_t *bdaddr = device_get_address(device);
+
+ btvoicedev = find_btvoicedev(device);
+ if (btvoicedev != NULL) return btvoicedev;
+
+ btvoicedev = g_try_new0(struct btvoice_device, 1);
+ if (!btvoicedev) return NULL;
+
+ btvoicedev->device = btd_device_ref(device);
+ btvoicedev->bdaddr = *bdaddr;
+
+ btd_service_set_user_data(service, btvoicedev);
+ btvoicedevices = g_slist_append(btvoicedevices, btvoicedev);
+
+ return btvoicedev;
+}
+
+int btvoice_register_audioOverBle(struct btd_service *service,
+ struct btd_device *device,
+ struct btvoiceenable *voiceenable,
+ struct gatt_primary *prim) {
+ struct btvoice_device *btvoicedev;
+
+ if (!voiceenable->audioOverBle) return 0;
+
+ btvoicedev = register_btvoicedevice(service, device);
+ if (btvoicedev == NULL) return -1;
+
+ btvoicedev->id = prim->range.start;
+ btvoicedev->audioOverBle = g_memdup(prim, sizeof(*prim));
+ btvoicedev->voiceenable.audioOverBle = TRUE;
+
+ return 0;
+}
+
+static void cleanup_btdevice(struct btvoice_device *btvoicedev)
+{
+ struct btd_device *device = btvoicedev->device;
+
+ if (btvoicedev->audioOverBle != NULL) return;
+
+ if (btvoicedev->attrib != NULL) {
+ g_attrib_unref(btvoicedev->attrib);
+ btvoicedev->attrib = NULL;
+ }
+
+ if (btvoicedev->device != NULL) {
+ btd_device_unref(btvoicedev->device);
+ btvoicedev->device = NULL;
+ }
+}
+
+void btvoice_unregister_audioOverBle(struct btd_device *device)
+{
+ struct btvoice_device *btvoicedev;
+
+ btvoicedev = find_btvoicedev(device);
+ if (btvoicedev == NULL) return;
+
+ g_free(btvoicedev->audioOverBle);
+ btvoicedev->audioOverBle = NULL;
+ btvoicedev->voiceenable.audioOverBle = FALSE;
+
+ cleanup_btdevice(btvoicedev);
+
+ g_free(btvoicedev);
+}
diff --git a/profiles/audioOverBle/btvoice.h b/profiles/audioOverBle/btvoice.h
new file mode 100644
index 0000000..6bf57f5
--- /dev/null
+++ b/profiles/audioOverBle/btvoice.h
@@ -0,0 +1,45 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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
+ *
+ */
+
+#ifndef BTVOICE_H_
+#define BTVOICE_H_
+
+struct btvoiceenable {
+ gboolean audioOverBle;
+ int mode;
+ int l2capChannel;
+};
+
+int btvoice_register_audioOverBle(struct btd_service *service,
+ struct btd_device *device,
+ struct btvoiceenable *voiceenable,
+ struct gatt_primary *audioOverBle);
+
+
+void btvoice_unregister_audioOverBle(struct btd_device *device);
+
+int btvoice_accept(struct btd_service *service);
+int btvoice_disconnect(struct btd_service *service);
+
+#endif // BTVOICE_H_
diff --git a/profiles/audioOverBle/main.c b/profiles/audioOverBle/main.c
new file mode 100644
index 0000000..4b44cbd
--- /dev/null
+++ b/profiles/audioOverBle/main.c
@@ -0,0 +1,79 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <glib.h>
+#include <gdbus/gdbus.h>
+
+#include "src/log.h"
+#include "src/plugin.h"
+#include "manager.h"
+#include "src/hcid.h"
+
+static GKeyFile *config = NULL;
+
+static GKeyFile *open_config_file(const char *file)
+{
+ GError *gerr = NULL;
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new();
+
+ g_key_file_set_list_separator(keyfile, ',');
+
+ if (!g_key_file_load_from_file(keyfile, file, 0, &gerr)) {
+ if (!g_error_matches(gerr, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ error("Parsing %s failed: %s", file, gerr->message);
+ g_error_free(gerr);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ return keyfile;
+}
+
+static int audioOverBle_init(void)
+{
+ config = open_config_file(CONFIGDIR "/audioOverBle.conf");
+ if (audioOverBle_manager_init(config) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static void audioOverBle_exit(void)
+{
+ if (config)
+ g_key_file_free(config);
+
+ audioOverBle_manager_exit();
+}
+
+BLUETOOTH_PLUGIN_DEFINE(audioOverBle, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ audioOverBle_init, audioOverBle_exit)
diff --git a/profiles/audioOverBle/manager.c b/profiles/audioOverBle/manager.c
new file mode 100644
index 0000000..b38ed29
--- /dev/null
+++ b/profiles/audioOverBle/manager.c
@@ -0,0 +1,153 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <errno.h>
+
+#include <gdbus/gdbus.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/sdp_lib.h"
+#include "lib/uuid.h"
+
+#include "src/log.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+
+#include "btvoice.h"
+#include "manager.h"
+
+#define AUD_UUID "0000fff0-0000-1000-8000-00805f9b34fb"
+
+static struct btvoiceenable voiceenable = {
+ .audioOverBle = TRUE,
+};
+
+static int btvoice_audioOverBle_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ struct gatt_primary *audioOverBlePrim;
+ const char *path = device_get_path(device);
+
+ DBG("path %s", path);
+
+ audioOverBlePrim = btd_device_get_primary(device, AUD_UUID);
+ if (audioOverBlePrim == NULL)
+ return -1;
+
+ return btvoice_register_audioOverBle(service, device, &voiceenable, audioOverBlePrim);
+}
+
+
+static void btvoice_audioOverBle_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+
+ btvoice_unregister_audioOverBle(device);
+}
+
+
+static struct btd_profile pxp_btvoice_audioOverBle_profile = {
+ .name = "audioOverBle",
+ .remote_uuid = AUD_UUID,
+ .device_probe = btvoice_audioOverBle_probe,
+ .device_remove = btvoice_audioOverBle_remove,
+ .accept = btvoice_accept,
+ .disconnect = btvoice_disconnect,
+ .auto_connect = true,
+};
+
+static void load_config_file(GKeyFile *config)
+{
+ char **list;
+ char *str;
+
+ int i;
+
+ if (config == NULL)
+ {
+ DBG("No Config found, assume default value");
+ voiceenable.audioOverBle = 1;
+ voiceenable.mode = 3;
+ voiceenable.l2capChannel = 0;
+ return;
+ }
+
+ list = g_key_file_get_string_list(config, "General", "Disable",
+ NULL, NULL);
+ for (i = 0; list && list[i] != NULL; i++) {
+ if (g_str_equal(list[i], "audioOverBle"))
+ voiceenable.audioOverBle = FALSE;
+ }
+ DBG("audio OverBLE Enable? %d", voiceenable.audioOverBle);
+ g_strfreev(list);
+
+ str = g_key_file_get_string(config, "General", "Mode", NULL);
+ if (str) {
+ DBG("Mode: %s", str);
+ if (g_str_equal(str, "PROP_MODE"))
+ voiceenable.mode = 1;
+ else if (g_str_equal(str, "L2CAP_MODE"))
+ voiceenable.mode = 2;
+ else if (g_str_equal(str, "ATT_MODE"))
+ voiceenable.mode = 3;
+ g_free(str);
+ }
+ DBG("audio OverBLE Mode? %d", voiceenable.mode);
+
+ voiceenable.l2capChannel = g_key_file_get_integer(config, "General", "l2capChannel", NULL);
+ DBG("audio OverBLE L2cap Channel? %d", voiceenable.l2capChannel);
+}
+
+void audioOverBle_manager_exit(void)
+{
+ btd_profile_unregister(&pxp_btvoice_audioOverBle_profile);
+}
+
+int audioOverBle_manager_init(GKeyFile *config)
+{
+ load_config_file(config);
+
+ if (btd_profile_register(&pxp_btvoice_audioOverBle_profile) < 0)
+ {
+ DBG("Fail to register...");
+ audioOverBle_manager_exit();
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
diff --git a/profiles/audioOverBle/manager.h b/profiles/audioOverBle/manager.h
new file mode 100644
index 0000000..f138dbe
--- /dev/null
+++ b/profiles/audioOverBle/manager.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ * Copyright (C) 2011 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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
+ *
+ */
+
+#ifndef AUDIO_OVER_BLE_MANAGER_H
+#define AUDIO_OVER_BLE_MANAGER_H
+int audioOverBle_manager_init(GKeyFile *conf);
+void audioOverBle_manager_exit(void);
+#endif /* AUDIO_OVER_BLE_MANAGER_H */
diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c
new file mode 100644
index 0000000..4ec35fc
--- /dev/null
+++ b/profiles/battery/battery.c
@@ -0,0 +1,368 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google, Inc. (edjames@google.com)
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ * 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
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* battery - read battery level */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define BATT_UUID "0000180f-0000-1000-8000-00805f9b34fb"
+#define BATT_LEVEL_UUID "00002a19-0000-1000-8000-00805f9b34fb"
+
+#define BATTERY_DIR "/tmp/batteries"
+
+struct service {
+ uint16_t value_handle;
+ int value;
+};
+
+struct batt_device {
+ uint16_t id;
+ struct btd_device *device;
+ GAttrib *attrib;
+ struct gatt_primary *batt_primary;
+
+ struct service level;
+
+ int batt_level;
+};
+
+static GSList *devices = NULL;
+
+static struct batt_device *batt_new_device(struct btd_device *device,
+ uint16_t id)
+{
+ struct batt_device *battdev;
+
+ battdev = g_try_new0(struct batt_device, 1);
+ if (!battdev)
+ return NULL;
+
+ battdev->id = id;
+ battdev->device = btd_device_ref(device);
+
+ return battdev;
+}
+
+static void batt_free_device(struct batt_device *battdev)
+{
+ btd_device_unref(battdev->device);
+ g_attrib_unref(battdev->attrib);
+ g_free(battdev->batt_primary);
+ g_free(battdev);
+}
+
+static time_t wallclock(void)
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ return 0;
+ }
+ return ts.tv_sec;
+}
+
+/* should really be done in the dbus agent, I think */
+static void batt_update_level_file(struct batt_device* battdev)
+{
+ char path[MAXPATHLEN], pathnew[MAXPATHLEN];
+
+ const char* addr = NULL;
+ const char* id = device_get_path(battdev->device);
+
+ /* id == /org/bluez/hci0/dev_D0_5F_B8_29_10_8D */
+
+ if (id != NULL) {
+ addr = rindex(id, '/');
+ if (addr == NULL) {
+ addr = id;
+ } else {
+ addr++;
+ }
+ }
+ if (addr == NULL || *addr == '\0') {
+ addr = "UNKNOWN";
+ }
+
+ snprintf(path, sizeof path, "%s/%s", BATTERY_DIR, addr);
+ snprintf(pathnew, sizeof pathnew, "%s.new", path);
+
+ if (mkdir(BATTERY_DIR, 0777) < 0 && errno != EEXIST) {
+ error("BATT mkdir: %s: %s", BATTERY_DIR, strerror(errno));
+ return;
+ }
+ if (chmod(BATTERY_DIR, 0755) < 0) {
+ error("BATT chmod: %s: %s", BATTERY_DIR, strerror(errno));
+ }
+
+ FILE* fp = fopen(pathnew, "w");
+ if (fp == NULL) {
+ error("BATT fopen: %s: %s", pathnew, strerror(errno));
+ return;
+ }
+ if (fchmod(fileno(fp), 0644) < 0) {
+ error("BATT chmod: %s: %s", pathnew, strerror(errno));
+ }
+ fprintf(fp, "%d\n", battdev->batt_level);
+ fclose(fp);
+
+ if (rename(pathnew, path) != 0) {
+ error("BATT rename: %s -> %s: %s", pathnew, path, strerror(errno));
+ return;
+ }
+}
+
+static void level_read_char_cb(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct batt_device *battdev = user_data;
+
+ if (status != 0) {
+ error("BATT %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ battdev->batt_level = pdu[1];
+ DBG("BATT level=%d", battdev->batt_level);
+ batt_update_level_file(battdev);
+}
+
+static void check_level(struct batt_device *battdev)
+{
+ if (battdev->level.value_handle == 0)
+ return;
+
+ gatt_read_char(battdev->attrib, battdev->level.value_handle,
+ level_read_char_cb, battdev);
+}
+
+static void char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+ struct batt_device *battdev = user_data;
+ struct gatt_primary *prim = battdev->batt_primary;
+ bt_uuid_t img_identity_uuid, img_block_uuid;
+ GSList *l;
+ uint16_t info_handle = 0, proto_mode_handle = 0;
+
+ DBG("BATT inspecting characteristics");
+ if (status != 0) {
+ error("BATT %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ bt_uuid_t uuid;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("BATT 0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : prim->range.end);
+
+ if (g_strcmp0(chr->uuid, BATT_LEVEL_UUID) == 0) {
+ battdev->level.value_handle = chr->value_handle;
+ DBG("BATT found BATT_LEVEL_UUID value_handle=0x%04x", chr->value_handle);
+ check_level(battdev);
+ }
+ }
+}
+
+static int batt_accept(struct btd_service *service)
+{
+ struct batt_device *battdev = btd_service_get_user_data(service);
+ struct gatt_primary *prim = battdev->batt_primary;
+ struct btd_device *device = btd_service_get_device(service);
+ GAttrib *attrib = btd_device_get_attrib(device);
+
+ DBG("BATT connected");
+
+ battdev->attrib = g_attrib_ref(attrib);
+ battdev->level.value = -1;
+
+ btd_service_connecting_complete(service, 0);
+
+ if (battdev->level.value_handle == 0) {
+ DBG("BATT discovering characteristics");
+
+ gatt_discover_char(battdev->attrib, prim->range.start,
+ prim->range.end, NULL,
+ char_discovered_cb, battdev);
+ } else {
+ check_level(battdev);
+ }
+
+ return 0;
+}
+
+static int batt_disconnect(struct btd_service *service)
+{
+ struct batt_device *battdev = btd_service_get_user_data(service);
+
+ DBG("BATT disconnected");
+
+ if (battdev->attrib) {
+ g_attrib_unref(battdev->attrib);
+ battdev->attrib = NULL;
+ }
+
+ btd_service_disconnecting_complete(service, 0);
+
+ return 0;
+}
+
+static struct batt_device *batt_register_device(struct btd_device *device,
+ struct gatt_primary *prim)
+{
+ struct batt_device *battdev;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+ GIOChannel *io;
+
+ battdev = batt_new_device(device, prim->range.start);
+ if (!battdev)
+ return NULL;
+
+ battdev->batt_primary = g_memdup(prim, sizeof(*prim));
+
+ return battdev;
+}
+
+static int batt_unregister_device(struct batt_device *battdev)
+{
+ batt_free_device(battdev);
+
+ return 0;
+}
+
+static int batt_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ GSList *primaries, *l;
+
+ DBG("BATT path %s", path);
+
+ primaries = btd_device_get_primaries(device);
+ if (primaries == NULL)
+ return -EINVAL;
+
+ for (l = primaries; l; l = g_slist_next(l)) {
+ struct gatt_primary *prim = l->data;
+ struct batt_device *battdev;
+
+ DBG("BATT uuid=%s", prim->uuid);
+ if (strcmp(prim->uuid, BATT_UUID) != 0)
+ continue;
+
+ DBG("BATT matched BATT uuid %s", prim->uuid);
+ battdev = batt_register_device(device, prim);
+ if (battdev == NULL)
+ continue;
+
+ btd_service_set_user_data(service, battdev);
+ devices = g_slist_append(devices, battdev);
+ }
+
+ return 0;
+}
+
+static void remove_device(gpointer a, gpointer b)
+{
+ struct batt_device *battdev = a;
+ struct btd_device *device = b;
+
+ if (battdev->device != device)
+ return;
+
+ devices = g_slist_remove(devices, battdev);
+ batt_unregister_device(battdev);
+}
+
+static void batt_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+
+ DBG("BATT path %s", path);
+
+ g_slist_foreach(devices, remove_device, device);
+}
+
+static struct btd_profile batt_profile = {
+ .name = "Battery",
+ .remote_uuid = BATT_UUID,
+ .device_probe = batt_probe,
+ .device_remove = batt_remove,
+ .accept = batt_accept,
+ .disconnect = batt_disconnect,
+ .auto_connect = true,
+};
+
+static int batt_init(void)
+{
+ int err;
+
+ return btd_profile_register(&batt_profile);
+}
+
+static void batt_exit(void)
+{
+ btd_profile_unregister(&batt_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW,
+ batt_init, batt_exit)
diff --git a/profiles/input/device.c b/profiles/input/device.c
index a494ea2..5a590e3 100644
--- a/profiles/input/device.c
+++ b/profiles/input/device.c
@@ -91,6 +91,7 @@
static int idle_timeout = 0;
static bool uhid_enabled = false;
+static bool encryption_enabled = true;
void input_set_idle_timeout(int timeout)
{
@@ -102,6 +103,11 @@
uhid_enabled = state;
}
+void input_enable_encryption(bool state)
+{
+ encryption_enabled = state;
+}
+
static void input_device_enter_reconnect_mode(struct input_device *idev);
static int connection_disconnect(struct input_device *idev, uint32_t flags);
@@ -971,7 +977,7 @@
device_get_name(idev->device, req->name, sizeof(req->name));
/* Encryption is mandatory for keyboards */
- if (req->subclass & 0x40) {
+ if (encryption_enabled && (req->subclass & 0x40)) {
if (!bt_io_set(idev->intr_io, &gerr,
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
BT_IO_OPT_INVALID)) {
@@ -1495,3 +1501,23 @@
return 0;
}
+
+bool input_device_must_unplug(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct input_device *idev = find_device(src, dst);
+ struct btd_device *device;
+
+ if (!idev)
+ return false;
+
+ device = idev->device;
+ if (!device)
+ return false;
+
+ if (btd_device_get_unplug(device)) {
+ btd_device_set_unplug(device, false);
+ return true;
+ }
+
+ return false;
+}
diff --git a/profiles/input/device.h b/profiles/input/device.h
index 51a9aee..6935663 100644
--- a/profiles/input/device.h
+++ b/profiles/input/device.h
@@ -29,6 +29,7 @@
void input_set_idle_timeout(int timeout);
void input_enable_userspace_hid(bool state);
+void input_enable_encryption(bool state);
int input_device_register(struct btd_service *service);
void input_device_unregister(struct btd_service *service);
@@ -40,3 +41,5 @@
int input_device_connect(struct btd_service *service);
int input_device_disconnect(struct btd_service *service);
+
+bool input_device_must_unplug(const bdaddr_t *src, const bdaddr_t *dst);
diff --git a/profiles/input/hog.c b/profiles/input/hog.c
index 9836a6c..233929d 100644
--- a/profiles/input/hog.c
+++ b/profiles/input/hog.c
@@ -243,5 +243,5 @@
btd_profile_unregister(&hog_profile);
}
-BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH,
hog_init, hog_exit)
diff --git a/profiles/input/manager.c b/profiles/input/manager.c
index 1d31b06..96596b5 100644
--- a/profiles/input/manager.c
+++ b/profiles/input/manager.c
@@ -97,6 +97,7 @@
if (config) {
int idle_timeout;
gboolean uhid_enabled;
+ gboolean encryption;
idle_timeout = g_key_file_get_integer(config, "General",
"IdleTimeout", &err);
@@ -114,6 +115,15 @@
input_enable_userspace_hid(uhid_enabled);
} else
g_clear_error(&err);
+
+ encryption = g_key_file_get_boolean(config, "General",
+ "Encryption", &err);
+ if (!err) {
+ DBG("input.conf: Encryption=%s", encryption ?
+ "true" : "false");
+ input_enable_encryption(encryption);
+ } else
+ g_clear_error(&err);
}
btd_profile_register(&input_profile);
@@ -129,5 +139,5 @@
btd_profile_unregister(&input_profile);
}
-BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH,
input_init, input_exit)
diff --git a/profiles/input/server.c b/profiles/input/server.c
index eb3fcf8..3cabbe2 100644
--- a/profiles/input/server.c
+++ b/profiles/input/server.c
@@ -153,6 +153,7 @@
char address[18];
GError *gerr = NULL;
int ret;
+ bool must_unplug = false;
if (err) {
error("%s", err->message);
@@ -174,6 +175,13 @@
ba2str(&dst, address);
DBG("Incoming connection from %s on PSM %d", address, psm);
+ if (input_device_must_unplug(&src, &dst)) {
+ info("connect_event_cb: deliberately sending unplug to %s",
+ address);
+ must_unplug = true;
+ goto do_unplug;
+ }
+
ret = input_device_set_channel(&src, &dst, psm, chan);
if (ret == 0)
return;
@@ -185,8 +193,9 @@
error("Refusing input device connect: %s (%d)", strerror(-ret), -ret);
+do_unplug:
/* Send unplug virtual cable to unknown devices */
- if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) {
+ if (must_unplug || (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL)) {
unsigned char unplug = 0x15;
int sk = g_io_channel_unix_get_fd(chan);
if (write(sk, &unplug, sizeof(unplug)) < 0)
diff --git a/profiles/oad/oad.c b/profiles/oad/oad.c
new file mode 100644
index 0000000..ad09863
--- /dev/null
+++ b/profiles/oad/oad.c
@@ -0,0 +1,915 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google, Inc. (edjames@google.com)
+ * Copyright (C) 2012 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2012 Nordic Semiconductor Inc.
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ * 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
+ *
+ */
+
+/* much lifted from input/hog.c */
+
+/* OAD - over air download of firmware to devices */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/log.h"
+#include "src/plugin.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "attrib/gatt.h"
+#include "src/shared/util.h"
+
+#define OAD_UUID "f000ffc0-0451-4000-b000-000000000000"
+#define OAD_IMG_IDENTITY_UUID "f000ffc1-0451-4000-b000-000000000000"
+#define OAD_IMG_BLOCK_UUID "f000ffc2-0451-4000-b000-000000000000"
+
+#define OAD_MAJOR_VERSION(x) ((x) >> 8)
+#define OAD_MINOR_VERSION(x) ((x) & 0xff)
+
+/* these codes may be returned instead of GP versions */
+#define OAD_VERSION_BAD 0xfefe
+#define OAD_VERSION_BUSY 0xffff
+
+/* remote has 2 processors, 2 firmwares. */
+#define OAD_GP_FIRMWARE_GFRM200 "gfrm200.gp.bin"
+#define OAD_TI_FIRMWARE_GFRM200 "gfrm200.ti.bin"
+#define OAD_GP_FIRMWARE_GFRM210 "gfrm210.gp.bin"
+#define OAD_TI_FIRMWARE_GFRM210 "gfrm210.ti.bin"
+
+/* remote checks in every 15 minutes */
+#define OAD_UPGRADE_DELAY_SECS 1 // delay between wake and upgrade check
+#define OAD_DELAY_NO_UPGRADE (8*60*60) // check for new ACS files (not likely)
+#define OAD_DELAY_NO_UPGRADE_DEBUG 150 // check faster when /tmp firmware exists
+#define OAD_DELAY_UPGRADING 60 // recheck in 1 minute (in case of 2 firmware update)
+
+#define OAD_HEADER_LEN 8 // on-disk header. See OAD_Formatv7.xlsx
+
+enum OAD_Status {
+ OADSTATUS_None = 0,
+ OADSTATUS_DataTransfer = 1,
+ OADSTATUS_TransferSuccess = 2,
+ OADSTATUS_TIActive = 0x81, // maybe flashing
+ OADSTATUS_GPActive = 0x82, // maybe rebooting
+ OADSTATUS_CRCError = 0xE0,
+ OADSTATUS_LVD = 0xE1, // low voltage detection
+ OADSTATUS_KeyPress = 0xE2, // user interrupted (not idle)
+ OADSTATUS_GPWrite = 0xE3, // GP write failed?
+ OADSTATUS_Bootloader = 0xE5, // Bootloader rejected?
+ OADSTATUS_ImageError = 0xE6, // Image format error?
+};
+
+enum OAD_Action {
+ OADACTION_GetRemoteInfo = 0,
+ OADACTION_TransferOADData = 1,
+ OADACTION_StartUpdate = 2,
+};
+
+/* firmware is upgraded in this order */
+enum OAD_FirmwareType {
+ OADFW_Unknown = -1,
+ OADFW_TI = 0,
+ OADFW_GP = 1,
+ OADFW_Total = 2,
+};
+
+struct oad_fwinfo {
+ uint16_t version;
+ uint32_t size;
+ uint8_t header[OAD_HEADER_LEN];
+ char file[MAXPATHLEN];
+};
+
+struct oad_service {
+ guint attio_id;
+ uint16_t ccc_handle;
+ uint16_t value_handle;
+ int notify_enabled;
+};
+
+struct oad_device {
+ uint16_t id;
+ struct btd_device *device;
+ GAttrib *attrib;
+ struct gatt_primary *oad_primary;
+
+ struct oad_service identity;
+ struct oad_service block;
+
+ time_t nextCheck;
+ struct oad_fwinfo disk[OADFW_Total];
+ struct oad_fwinfo remote[OADFW_Total];
+
+ int force_upgrade;
+ int retries;
+ int num_blocks;
+ int progress;
+
+ uint8_t header[OAD_HEADER_LEN];
+ int fd;
+ guint tid; // Timer id to delay starting upgrade
+};
+
+struct oad_characteristic {
+ struct oad_device *oaddev;
+ char uuid[MAX_LEN_UUID_STR + 1];
+};
+
+static GSList *oad_devices = NULL;
+
+static struct oad_device *oad_new_device(struct btd_device *device, uint16_t id)
+{
+ struct oad_device *oaddev;
+
+ oaddev = g_try_new0(struct oad_device, 1);
+ if (!oaddev)
+ return NULL;
+
+ oaddev->id = id;
+ oaddev->device = btd_device_ref(device);
+ oaddev->fd = -1;
+
+ return oaddev;
+}
+
+static void oad_free_device(struct oad_device *oaddev)
+{
+ if (oaddev->fd != -1) {
+ close(oaddev->fd);
+ oaddev->fd = -1;
+ }
+ btd_device_unref(oaddev->device);
+ g_attrib_unref(oaddev->attrib);
+ g_free(oaddev->oad_primary);
+ g_free(oaddev);
+}
+
+static void oad_block_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ oaddev->retries = 0; // successful write
+}
+
+static void oad_identity_char_write_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+}
+
+static void oad_start_transfer(struct oad_device* oaddev)
+{
+ uint8_t attr_val[OAD_HEADER_LEN+1]; // + 1 byte action
+
+ memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
+ attr_val[OAD_HEADER_LEN] = OADACTION_TransferOADData; /* start OAD image transfer */
+
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+}
+
+static void oad_block_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+ int retry = 0;
+
+
+ /* should be at least opcode (1b) + handle (7a00) */
+ if (len < 3) {
+ error("OAD Invalid PDU received");
+ return;
+ }
+
+ // 2 bytes block_num + status byte
+ if (len != 6) {
+ error("OAD block notify format error: expected 6 bytes, got %d", len);
+ return;
+ }
+
+ /* request for block */
+ uint16_t block_num = get_le16(pdu + 3);
+ enum OAD_Status status = (enum OAD_Status) pdu[5];
+
+ switch (status) {
+ case OADSTATUS_DataTransfer: // send a block
+ {
+ uint8_t block[18]; // 2 bytes block_num + 16 data bytes
+
+ memset(block, 0, sizeof block);
+ put_le16(block_num, block);
+
+ if (lseek(oaddev->fd, 16 * block_num, SEEK_SET) < 0) {
+ perror("OAD firmware seek");
+ goto error;
+ }
+ if (read(oaddev->fd, block+2, sizeof block - 2) < 0) {
+ perror("OAD firmware read");
+ goto error;
+ }
+
+ int progress = 0;
+ if (oaddev->num_blocks > 0) progress = 100 * block_num / oaddev->num_blocks;
+ if (oaddev->progress != progress) {
+ oaddev->progress = progress;
+ DBG("OAD sending block %d (%d%%)", block_num, progress);
+ }
+ gatt_write_char(oaddev->attrib, oaddev->block.value_handle, block,
+ sizeof(block), oad_block_char_write_cb, oaddev);
+ }
+ break;
+ case OADSTATUS_TransferSuccess: // OK, successful transfer
+ {
+ DBG("OAD firmware transfer successful");
+ close(oaddev->fd);
+ oaddev->fd = -1;
+
+ uint8_t attr_val[OAD_HEADER_LEN+1]; // + 1 byte action
+ memcpy(attr_val, oaddev->header, OAD_HEADER_LEN);
+ attr_val[OAD_HEADER_LEN] = OADACTION_StartUpdate; /* commit fw image */
+ DBG("OAD committing firmware");
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+ }
+ break;
+ case OADSTATUS_GPActive:
+ DBG("OAD GP is active (good)");
+ break;
+ case OADSTATUS_TIActive:
+ DBG("OAD TI is active (good)");
+ break;
+
+ case OADSTATUS_CRCError:
+ DBG("OAD firmware transfer checksum error");
+ goto error;
+ case OADSTATUS_LVD:
+ DBG("OAD firmware upgrade low battery error");
+ goto error;
+ case OADSTATUS_KeyPress:
+ DBG("OAD firmware upgrade user interrupted");
+ goto error;
+ case OADSTATUS_GPWrite:
+ DBG("OAD firmware upgrade GP write error");
+ goto error;
+ case OADSTATUS_Bootloader:
+ DBG("OAD firmware upgrade Bootloader error");
+ goto error;
+ case OADSTATUS_ImageError:
+ DBG("OAD firmware upgrade Image error");
+ goto error;
+
+ default:
+ /* 0xe0 or greater appear to be unrecoverable errors */
+ if (status >= OADSTATUS_CRCError) {
+ DBG("OAD unexpected block notify error 0x%02x", status);
+ goto error;
+ }
+ DBG("OAD unexpected block notify status 0x%02x (ignoring)", status);
+ break;
+ }
+ /* success so far*/
+ return;
+
+error:
+ DBG("OAD upgrade stopping due to error %d", status);
+ close(oaddev->fd);
+ oaddev->fd = -1;
+}
+
+static time_t oad_wallclock(void)
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ return 0;
+ }
+ return ts.tv_sec;
+}
+
+static int oad_is_time_for_upgrade_check(struct oad_device* oaddev)
+{
+ time_t now = oad_wallclock();
+
+ if (now < oaddev->nextCheck) {
+ int next = oaddev->nextCheck - now;
+ DBG("OAD not time for upgrade check yet (%d seconds left)", next);
+ return 0;
+ }
+ DBG("OAD time for upgrade check");
+ return 1;
+}
+
+static void oad_get_firmware_info(struct oad_device *oaddev, const char* file)
+{
+ uint8_t header[OAD_HEADER_LEN];
+
+ int fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ error("OAD open: %s: %s", file, strerror(errno));
+ return;
+ }
+ int len = read(fd, header, sizeof header);
+ if (len < 0) {
+ error("OAD read: %s: %s", file, strerror(errno));
+ close(fd);
+ return;
+ }
+ if (len < sizeof header) {
+ error("OAD short read: %s: wanted %lu, got %d", file, sizeof header, len);
+ close(fd);
+ return;
+ }
+ close(fd);
+ fd = -1;
+
+ /* See OAD_Formatv7.xlsx, 'Image Info' tab. 8 bytes are:
+ * 2 bytes checksum
+ * 2 bytes version
+ * 3 bytes size
+ * 1 byte id: "T" TI image, "G" GP image
+ */
+ uint16_t version = get_le16(header + 2);
+ uint32_t size = get_le32(header+4) & 0x00ffffff;
+ int id = header[7];
+ enum OAD_FirmwareType type;
+
+ switch(id) {
+ case 'T':
+ case 'U':
+ type = OADFW_TI;
+ break;
+ case 'G':
+ case 'H':
+ type = OADFW_GP;
+ break;
+ default:
+ type = OADFW_Unknown;
+ break;
+ }
+ if (type == OADFW_Unknown) {
+ error("OAD unknown firmware id '%c': file=%s version=%d.%d",
+ id, file, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+ return;
+ }
+ int index = (int)type;
+ if (version <= oaddev->disk[index].version) {
+ error("OAD ignoring older firmware: file=%s type=%d version=%d.%d",
+ file, type, OAD_MAJOR_VERSION(version), OAD_MINOR_VERSION(version));
+ return;
+ }
+ if (strlen(file) >= sizeof oaddev->disk[index].file) {
+ error("OAD firmware file name too long: %s", file);
+ return;
+ }
+
+ oaddev->disk[index].version = version;
+ oaddev->disk[index].size = size;
+ memcpy(oaddev->disk[index].header, header, sizeof oaddev->disk[index].header);
+ strncpy(oaddev->disk[index].file, file, sizeof oaddev->disk[index].file);
+ oaddev->disk[index].file[sizeof oaddev->disk[index].file - 1] = '\0';
+}
+
+static const char* oad_firmware_type_string(enum OAD_FirmwareType type)
+{
+ switch (type) {
+ case OADFW_GP: return "GP";
+ case OADFW_TI: return "TI";
+ }
+ return "Unknown";
+}
+
+static void oad_find_firmware(struct oad_device *oaddev)
+{
+ char name[MAX_NAME_LENGTH + 1] = "";
+ const char *tmp_gp;
+ const char *tmp_ti;
+ const char *etc_gp;
+ const char *etc_ti;
+
+ device_get_name(oaddev->device, name, sizeof(name));
+ if (g_strcmp0(name, "GFRM200") == 0) {
+ tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM200;
+ tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM200;
+ etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM200;
+ etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM200;
+ } else if (g_strcmp0(name, "GFRM201") == 0 ||
+ g_strcmp0(name, "GFRM210") == 0) {
+ tmp_gp = "/tmp/" OAD_GP_FIRMWARE_GFRM210;
+ tmp_ti = "/tmp/" OAD_TI_FIRMWARE_GFRM210;
+ etc_gp = "/etc/remote/" OAD_GP_FIRMWARE_GFRM210;
+ etc_ti = "/etc/remote/" OAD_TI_FIRMWARE_GFRM210;
+ } else {
+ error("OAD unknown model %s, skipping", name);
+ }
+
+ memset(oaddev->disk, 0, sizeof oaddev->disk);
+
+ /* check in tmp first, to make debugging easier */
+ if (access(tmp_gp, R_OK) == 0 || access(tmp_ti, R_OK) == 0) {
+ DBG("OAD found firmware in /tmp, ignoring /etc/remote, enabling downgrade.");
+ oaddev->force_upgrade = 1; // allow downgrade
+ oad_get_firmware_info(oaddev, tmp_gp);
+ oad_get_firmware_info(oaddev, tmp_ti);
+ } else {
+ oaddev->force_upgrade = 0;
+ oad_get_firmware_info(oaddev, etc_gp);
+ oad_get_firmware_info(oaddev, etc_ti);
+ }
+
+ int i;
+ for (i = 0; i < OADFW_Total; i++) {
+ enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+ const char* typeStr = oad_firmware_type_string(type);
+ if (strlen(oaddev->disk[type].file) == 0) {
+ error("OAD no %s firmware found on disk", typeStr);
+ } else {
+ DBG("OAD found %s firmware: file=%s version=%d.%d",
+ typeStr, oaddev->disk[i].file,
+ OAD_MAJOR_VERSION(oaddev->disk[i].version),
+ OAD_MINOR_VERSION(oaddev->disk[i].version));
+ }
+ }
+}
+
+static void oad_check_for_upgrade(struct oad_device *oaddev)
+{
+ if (!oad_is_time_for_upgrade_check(oaddev)) {
+ return;
+ }
+
+ oad_find_firmware(oaddev);
+
+ struct oad_fwinfo* fp = NULL;
+ int i;
+ for (i = 0; i < OADFW_Total; i++) {
+ enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+ const char* typeStr = oad_firmware_type_string(type);
+ if (strlen(oaddev->disk[i].file) == 0) {
+ continue;
+ }
+ if (oaddev->disk[i].version == oaddev->remote[i].version ||
+ (!oaddev->force_upgrade &&
+ oaddev->disk[i].version < oaddev->remote[i].version &&
+ oaddev->remote[i].version != OAD_VERSION_BAD)) {
+ DBG("OAD %s firmware is up to date", typeStr);
+ } else {
+ fp = &oaddev->disk[i];
+ break;
+ }
+ }
+
+ if (fp == NULL) {
+ int next = oaddev->force_upgrade
+ ? OAD_DELAY_NO_UPGRADE_DEBUG
+ : OAD_DELAY_NO_UPGRADE;
+ DBG("OAD next check is in %d seconds", next);
+ oaddev->nextCheck = oad_wallclock() + next;
+ return;
+ }
+ oaddev->nextCheck = oad_wallclock() + OAD_DELAY_UPGRADING;
+
+ DBG("OAD starting upgrade with %s", fp->file);
+
+ if (oaddev->fd != -1) {
+ close(oaddev->fd);
+ }
+ oaddev->fd = open(fp->file, O_RDONLY);
+ if (oaddev->fd < 0) {
+ error("OAD open: %s: %s", fp->file, strerror(errno));
+ return;
+ }
+ if (oaddev->tid) {
+ g_source_remove(oaddev->tid);
+ oaddev->tid = 0;
+ }
+
+ oaddev->progress = 0;
+ oaddev->retries = 0;
+ oaddev->num_blocks = (fp->size + 15) / 16;
+ memcpy(oaddev->header, fp->header, sizeof oaddev->header);
+ oad_start_transfer(oaddev);
+}
+
+static void oad_identity_notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+
+ /* should be at least opcode (1b) + handle (76 00) */
+ if (len < 3) {
+ error("OAD Invalid PDU received");
+ return;
+ }
+
+ /* See OAD_Formatv7.xlsx, 'Image Info' tab. 10 bytes are:
+ * char gp_version[2]; // 09 00 is 0.9
+ * char gp_size[3]; // 00 00 01 is 0x010000 == 64k
+ * char ti_version[2]; // 2a 00 is 0.42
+ * char ti_size[3]; // 00 00 01 is 0x010000 == 64k
+ */
+
+ if (len != 13) {
+ error("OAD identity data format error: expected 13 bytes, got %d", len);
+ return;
+ }
+
+ oaddev->remote[OADFW_GP].version = get_le16(pdu+3);
+ oaddev->remote[OADFW_GP].size = get_le32(pdu+5) & 0x00ffffff;
+
+ oaddev->remote[OADFW_TI].version = get_le16(pdu+8);
+ unsigned char size[4];
+ memcpy(size, pdu+10, 3);
+ size[3] = 0; // need 4 bytes for get_le32
+ oaddev->remote[OADFW_TI].size = get_le32(size) & 0x00ffffff;
+
+ int i;
+ for (i = 0; i < OADFW_Total; i++) {
+ enum OAD_FirmwareType type = (enum OAD_FirmwareType) i;
+ const char* typeStr = oad_firmware_type_string(type);
+ DBG("OAD %s firmware on remote: version=%d.%d",
+ typeStr, OAD_MAJOR_VERSION(oaddev->remote[i].version),
+ OAD_MINOR_VERSION(oaddev->remote[i].version));
+ }
+ oad_check_for_upgrade(oaddev);
+}
+
+static void oad_check_fwversion(struct oad_device *oaddev)
+{
+ uint8_t attr_val[9]; // 8 byte header + 1 command byte (all zeros)
+
+ if (!oaddev->identity.notify_enabled || !oaddev->block.notify_enabled) {
+ return;
+ }
+
+ DBG("OAD sending fw version request");
+ memset(attr_val, 0, sizeof attr_val);
+ attr_val[8] = OADACTION_GetRemoteInfo; /* trigger fw version notify */
+ gatt_write_char(oaddev->attrib, oaddev->identity.value_handle, attr_val,
+ sizeof(attr_val), oad_identity_char_write_cb, oaddev);
+}
+
+static void oad_ccc_char_write_identity_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ oaddev->identity.notify_enabled = 1;
+
+ oad_check_fwversion(oaddev);
+}
+
+static void oad_ccc_char_write_block_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data)
+{
+ struct oad_device *oaddev = user_data;
+
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+ oaddev->block.notify_enabled = 1;
+
+ oad_check_fwversion(oaddev);
+}
+
+static void oad_enable_notify(struct oad_device* oaddev)
+{
+ if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+ oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+ error("OAD discovery not complete, cannot enable notifiers");
+ return;
+ }
+
+ oaddev->identity.attio_id =
+ g_attrib_register(oaddev->attrib,
+ ATT_OP_HANDLE_NOTIFY, oaddev->identity.value_handle,
+ oad_identity_notify_handler, oaddev, NULL);
+ oaddev->block.attio_id =
+ g_attrib_register(oaddev->attrib,
+ ATT_OP_HANDLE_NOTIFY, oaddev->block.value_handle,
+ oad_block_notify_handler, oaddev, NULL);
+
+ uint8_t attr_val[2];
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val);
+ DBG("OAD enabling notify for OAD_IMG_IDENTITY_UUID");
+ gatt_write_char(oaddev->attrib, oaddev->identity.ccc_handle, attr_val,
+ sizeof(attr_val), oad_ccc_char_write_identity_cb, oaddev);
+ DBG("OAD enabling notify for OAD_IMG_BLOCK_UUID");
+ gatt_write_char(oaddev->attrib, oaddev->block.ccc_handle, attr_val,
+ sizeof(attr_val), oad_ccc_char_write_block_cb, oaddev);
+}
+
+static gboolean oad_upgrade_timer(gpointer data)
+{
+ struct oad_device *oaddev = data;
+
+ g_source_remove(oaddev->tid);
+ oaddev->tid = 0;
+
+ DBG("OAD timer fired");
+ oad_enable_notify(oaddev);
+}
+
+static void oad_desc_discovered_cb(uint8_t status, GSList *descs, void* user_data)
+{
+ struct oad_characteristic *ch = user_data;
+ struct oad_device *oaddev = ch->oaddev;
+ struct GSList *list = NULL;
+
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ goto done;
+ }
+
+ for (; descs; descs = descs->next) {
+ struct gatt_desc *desc = descs->data;
+
+ if (desc->uuid16 != GATT_CLIENT_CHARAC_CFG_UUID)
+ continue;
+
+ if (g_strcmp0(ch->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+ DBG("OAD found OAD_IMG_IDENTITY_UUID ccc_handle=0x%04x", desc->handle);
+ oaddev->identity.ccc_handle = desc->handle;
+
+ } else if (g_strcmp0(ch->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+ DBG("OAD found OAD_IMG_BLOCK_UUID ccc_handle=0x%04x", desc->handle);
+ oaddev->block.ccc_handle = desc->handle;
+ }
+ }
+ if (oaddev->identity.ccc_handle && oaddev->block.ccc_handle)
+ oaddev->tid = g_timeout_add_seconds(OAD_UPGRADE_DELAY_SECS, oad_upgrade_timer, oaddev);
+
+done:
+ g_free(ch);
+}
+
+static void oad_discover_desc(struct oad_device *oaddev, struct gatt_char *c, struct gatt_char *c_next)
+{
+ struct gatt_primary *prim = oaddev->oad_primary;
+ struct oad_characteristic *ch;
+ uint16_t start, end;
+
+ start = c->value_handle + 1;
+
+ if (c_next != NULL) {
+ if (start == c_next->handle)
+ return;
+ end = c_next->handle - 1;
+ } else if (c->value_handle != prim->range.end) {
+ end = prim->range.end;
+ } else {
+ return;
+ }
+
+ ch = g_new0(struct oad_characteristic, 1);
+ ch->oaddev = oaddev;
+ memcpy(ch->uuid, c->uuid, sizeof(c->uuid));
+
+ DBG("OAD discovering descriptors start=0x%04x end=0x%04x", start, end);
+
+ gatt_discover_desc(oaddev->attrib, start, end, NULL, oad_desc_discovered_cb, ch);
+}
+
+static void oad_char_discovered_cb(uint8_t status, GSList *chars, void* user_data)
+{
+ struct oad_device *oaddev = user_data;
+ struct gatt_primary *prim = oaddev->oad_primary;
+ bt_uuid_t img_identity_uuid, img_block_uuid;
+ GSList *l;
+ uint16_t info_handle = 0, proto_mode_handle = 0;
+
+ DBG("OAD inspecting characteristics");
+ if (status != 0) {
+ error("OAD %s failed: %s", __func__, att_ecode2str(status));
+ return;
+ }
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr, *next;
+ bt_uuid_t uuid;
+ uint16_t start, end;
+
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
+ DBG("OAD 0x%04x UUID: %s properties: %02x", chr->handle, chr->uuid, chr->properties);
+
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : prim->range.end);
+
+ if (g_strcmp0(chr->uuid, OAD_IMG_IDENTITY_UUID) == 0) {
+ oaddev->identity.value_handle = chr->value_handle;
+ DBG("OAD found OAD_IMG_IDENTITY_UUID value_handle=0x%04x", chr->value_handle);
+ oad_discover_desc(oaddev, chr, next);
+ } else if (g_strcmp0(chr->uuid, OAD_IMG_BLOCK_UUID) == 0) {
+ oaddev->block.value_handle = chr->value_handle;
+ DBG("OAD found OAD_IMG_BLOCK_UUID value_handle=0x%04x", chr->value_handle);
+ oad_discover_desc(oaddev, chr, next);
+ }
+
+ }
+}
+
+static int oad_accept(struct btd_service *service)
+{
+ struct oad_device *oaddev = btd_service_get_user_data(service);
+ struct gatt_primary *prim = oaddev->oad_primary;
+ struct btd_device *device = btd_service_get_device(service);
+ GAttrib *attrib = btd_device_get_attrib(device);
+
+ DBG("OAD connected");
+
+ oaddev->attrib = g_attrib_ref(attrib);
+ oaddev->identity.notify_enabled = 0;
+ oaddev->block.notify_enabled = 0;
+
+ btd_service_connecting_complete(service, 0);
+
+ if (oaddev->identity.ccc_handle == 0 || oaddev->block.ccc_handle == 0 ||
+ oaddev->identity.value_handle == 0 || oaddev->block.value_handle == 0) {
+ DBG("OAD discovering characteristics");
+
+ gatt_discover_char(oaddev->attrib, prim->range.start, prim->range.end, NULL,
+ oad_char_discovered_cb, oaddev);
+ } else {
+ if (oad_is_time_for_upgrade_check(oaddev)) {
+ oad_enable_notify(oaddev);
+ }
+ }
+
+ return 0;
+}
+
+static int oad_disconnect(struct btd_service *service)
+{
+ struct oad_device *oaddev = btd_service_get_user_data(service);
+
+ DBG("OAD disconnected");
+
+ if (oaddev->identity.attio_id > 0) {
+ g_attrib_unregister(oaddev->attrib, oaddev->identity.attio_id);
+ oaddev->identity.attio_id = 0;
+ }
+ if (oaddev->block.attio_id > 0) {
+ g_attrib_unregister(oaddev->attrib, oaddev->block.attio_id);
+ oaddev->block.attio_id = 0;
+ }
+ if (oaddev->attrib) {
+ g_attrib_unref(oaddev->attrib);
+ oaddev->attrib = NULL;
+ }
+
+ btd_service_disconnecting_complete(service, 0);
+
+ return 0;
+}
+
+static struct oad_device *oad_register_device(struct btd_device *device, struct gatt_primary *prim)
+{
+ struct oad_device *oaddev;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+ GIOChannel *io;
+
+ oaddev = oad_new_device(device, prim->range.start);
+ if (!oaddev)
+ return NULL;
+
+ oaddev->oad_primary = g_memdup(prim, sizeof(*prim));
+
+ return oaddev;
+}
+
+static int oad_unregister_device(struct oad_device *oaddev)
+{
+ if (oaddev->tid) {
+ g_source_remove(oaddev->tid);
+ oaddev->tid = 0;
+ }
+ oad_free_device(oaddev);
+
+ return 0;
+}
+
+static int oad_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+ GSList *primaries, *l;
+
+ DBG("OAD path %s", path);
+
+ primaries = btd_device_get_primaries(device);
+ if (primaries == NULL)
+ return -EINVAL;
+
+ for (l = primaries; l; l = g_slist_next(l)) {
+ struct gatt_primary *prim = l->data;
+ struct oad_device *oaddev;
+
+ DBG("OAD uuid=%s", prim->uuid);
+ if (strcmp(prim->uuid, OAD_UUID) != 0)
+ continue;
+
+ DBG("OAD matched OAD uuid");
+ oaddev = oad_register_device(device, prim);
+ if (oaddev == NULL)
+ continue;
+
+ btd_service_set_user_data(service, oaddev);
+ oad_devices = g_slist_append(oad_devices, oaddev);
+ }
+
+ return 0;
+}
+
+static void oad_remove_device(gpointer a, gpointer b)
+{
+ struct oad_device *oaddev = a;
+ struct btd_device *device = b;
+
+ if (oaddev->device != device)
+ return;
+
+ oad_devices = g_slist_remove(oad_devices, oaddev);
+ oad_unregister_device(oaddev);
+}
+
+static void oad_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ const char *path = device_get_path(device);
+
+ DBG("OAD path %s", path);
+
+ g_slist_foreach(oad_devices, oad_remove_device, device);
+}
+
+static struct btd_profile oad_profile = {
+ .name = "OAD",
+ .remote_uuid = OAD_UUID,
+ .device_probe = oad_probe,
+ .device_remove = oad_remove,
+ .accept = oad_accept,
+ .disconnect = oad_disconnect,
+ .auto_connect = true,
+};
+
+static int oad_init(void)
+{
+ int err;
+
+ return btd_profile_register(&oad_profile);
+}
+
+static void oad_exit(void)
+{
+ btd_profile_unregister(&oad_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(oad, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, oad_init, oad_exit)
diff --git a/src/adapter.c b/src/adapter.c
index 5d7a9a9..e7cb86b 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -6649,6 +6649,7 @@
adapter->dev_id);
adapter->pair_device_timeout = 0;
+ adapter->pair_device_id = 0;
adapter_cancel_bonding(adapter, &data->bdaddr, data->addr_type);
@@ -6782,6 +6783,11 @@
device = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type);
if (device) {
+ DBG("device %p paired %d bonded %d bonding %d", device,
+ device_is_paired(device, addr->type),
+ device_is_bonded(device, addr->type),
+ device_is_bonding(device, NULL));
+
adapter_remove_connection(adapter, device, addr->type);
disconnect_notify(device, reason);
}
@@ -7697,6 +7703,11 @@
return;
}
+ DBG("device %p paired %d bonded %d bonding %d", device,
+ device_is_paired(device, ev->addr.type),
+ device_is_bonded(device, ev->addr.type),
+ device_is_bonding(device, NULL));
+
memset(&eir_data, 0, sizeof(eir_data));
if (eir_len > 0)
eir_parse(&eir_data, ev->eir, eir_len);
@@ -8157,6 +8168,11 @@
if (adapter->current_settings & MGMT_SETTING_POWERED)
adapter_start(adapter);
+ else
+ set_mode(adapter, MGMT_OP_SET_POWERED, 0x01);
+
+ set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x01);
+ set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, 0x01);
return;
@@ -8189,6 +8205,16 @@
return;
}
+ /*
+ * allow an interface to be ignored.
+ */
+ char ignore[64] = { 0 };
+ snprintf(ignore, sizeof ignore, "IGNORE_hci%d", index);
+ if (getenv(ignore) != NULL) {
+ warn("Ignoring index due to environment variable: %s", ignore);
+ return;
+ }
+
adapter = btd_adapter_new(index);
if (!adapter) {
btd_error(index,
diff --git a/src/attrib-server.c b/src/attrib-server.c
index 4439c27..658db55 100644
--- a/src/attrib-server.c
+++ b/src/attrib-server.c
@@ -997,7 +997,8 @@
size_t vlen;
uint8_t *value = g_attrib_get_buffer(channel->attrib, &vlen);
- DBG("op 0x%02x", ipdu[0]);
+ // suppress very common debug message
+ // DBG("op 0x%02x", ipdu[0]);
if (len > vlen) {
error("Too much data on ATT socket");
@@ -1306,9 +1307,11 @@
atval, 5);
/* GAP service: device name attribute */
+ const char* name = btd_adapter_get_name(server->adapter);
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
attrib_db_add_new(server, server->name_handle, &uuid, ATT_NONE,
- ATT_NOT_PERMITTED, NULL, 0);
+ ATT_NOT_PERMITTED, name,
+ strlen(name));
/* GAP service: device appearance characteristic */
server->appearance_handle = 0x0008;
diff --git a/src/device.c b/src/device.c
index 99454a7..6ed9509 100644
--- a/src/device.c
+++ b/src/device.c
@@ -243,6 +243,7 @@
gboolean auto_connect;
gboolean disable_auto_connect;
gboolean general_connect;
+ gboolean unplug;
bool legacy;
int8_t rssi;
@@ -405,6 +406,9 @@
g_key_file_set_boolean(key_file, "General", "Blocked",
device->blocked);
+ g_key_file_set_boolean(key_file, "General", "Unplug",
+ device->unplug);
+
if (device->uuids) {
GSList *l;
int i;
@@ -1060,6 +1064,46 @@
set_blocked(id, b, data);
}
+static gboolean dev_property_get_unplug(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct btd_device *device = data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN,
+ &device->unplug);
+
+ return TRUE;
+}
+
+static void set_unplug(GDBusPendingPropertySet id, gboolean value,
+ void *data)
+{
+ struct btd_device *device = data;
+
+ btd_device_set_unplug(device, value);
+
+ g_dbus_pending_property_success(id);
+}
+
+static void dev_property_set_unplug(const GDBusPropertyTable *property,
+ DBusMessageIter *value,
+ GDBusPendingPropertySet id, void *data)
+{
+ dbus_bool_t b;
+
+ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) {
+ g_dbus_pending_property_error(id,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+ return;
+ }
+
+ dbus_message_iter_get_basic(value, &b);
+
+ set_unplug(id, b, data);
+}
+
+
static gboolean dev_property_get_connected(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
@@ -2548,6 +2592,7 @@
{ "Paired", "b", dev_property_get_paired },
{ "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted },
{ "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked },
+ { "Unplug", "b", dev_property_get_unplug, dev_property_set_unplug },
{ "LegacyPairing", "b", dev_property_get_legacy },
{ "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi },
{ "Connected", "b", dev_property_get_connected },
@@ -2891,6 +2936,9 @@
if (blocked)
device_block(device, FALSE);
+ device->unplug = g_key_file_get_boolean(key_file, "General",
+ "Unplug", NULL);
+
/* Load device profile list */
uuids = g_key_file_get_string_list(key_file, "General", "Services",
NULL, NULL);
@@ -5168,6 +5216,29 @@
DEVICE_INTERFACE, "Trusted");
}
+gboolean btd_device_get_unplug(struct btd_device *device)
+{
+ return device->unplug;
+}
+
+void btd_device_set_unplug(struct btd_device *device, gboolean unplug)
+{
+ if (!device)
+ return;
+
+ if (device->unplug == unplug)
+ return;
+
+ DBG("unplug %d", unplug);
+
+ device->unplug = unplug;
+
+ store_device_info(device);
+
+ g_dbus_emit_property_changed(dbus_conn, device->path,
+ DEVICE_INTERFACE, "Unplug");
+}
+
void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type)
{
if (!device)
diff --git a/src/device.h b/src/device.h
index 3cab366..bc5535c 100644
--- a/src/device.h
+++ b/src/device.h
@@ -163,3 +163,6 @@
void btd_device_init(void);
void btd_device_cleanup(void);
+
+gboolean btd_device_get_unplug(struct btd_device *device);
+void btd_device_set_unplug(struct btd_device *device, gboolean value);
diff --git a/src/main.c b/src/main.c
index bcc1e6f..21ca830 100644
--- a/src/main.c
+++ b/src/main.c
@@ -599,6 +599,7 @@
guint signal, watchdog;
const char *watchdog_usec;
+ setvbuf(stdout, NULL, _IOLBF, 0);
init_defaults();
context = g_option_context_new(NULL);
diff --git a/test/gfiber-agent b/test/gfiber-agent
new file mode 100755
index 0000000..ca9929f
--- /dev/null
+++ b/test/gfiber-agent
@@ -0,0 +1,229 @@
+#!/usr/bin/python -u
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import dbus
+import dbus.exceptions
+import dbus.service
+import dbus.mainloop.glib
+import os
+import subprocess
+import sys
+try:
+ from gi.repository import GObject
+except ImportError:
+ import gobject as GObject
+
+BLUEZ_BUS = 'org.bluez'
+BLUEZ_ROOT_OBJ = '/org/bluez'
+AGENT_INTF = 'org.bluez.Agent1'
+AGENT_MGR_INTF = 'org.bluez.AgentManager1'
+DEVICE_INTF = 'org.bluez.Device1'
+OBJECT_MGR_INTF = 'org.freedesktop.DBus.ObjectManager'
+PROPERTY_INTF = 'org.freedesktop.DBus.Properties'
+AGENT_PATH = '/com/google/gfiber/agent'
+AGENT_CAPABILITY = 'NoInputNoOutput'
+DEV_NAME_GFRM100 = 'GFRM100'
+DEV_NAME_GFRM200 = 'GFRM200'
+DEV_NAME_GFRM210 = 'GFRM210'
+# Early GFRM210s identified themselves as GFRM201
+DEV_NAME_GFRM201 = 'GFRM201'
+DEV_NAME_TIARC = 'HID AdvRemote'
+DEV_OUI_TIARC = '90:59:AF'
+
+
+g = {'bus': None, 'agent': None, 'adaptdir': None}
+
+
+def dev_trusted(path):
+ bus = g['bus']
+ obj = bus.get_object(BLUEZ_BUS, path)
+ props = dbus.Interface(obj, PROPERTY_INTF)
+ props.Set(DEVICE_INTF, "Trusted", True)
+
+class Rejected(dbus.DBusException):
+ _dbus_error_name = "org.bluez.Error.Rejected"
+
+class Agent(dbus.service.Object):
+ exit_on_release = True
+
+ def set_exit_on_release(self, exit_on_release):
+ self.exit_on_release = exit_on_release
+
+ @dbus.service.method(AGENT_INTF, in_signature="", out_signature="")
+ def Release(self):
+ print("Release")
+ if self.exit_on_release:
+ mainloop.quit()
+
+ @dbus.service.method(AGENT_INTF, in_signature="os", out_signature="")
+ def AuthorizeService(self, device, uuid):
+ print("AuthorizeService (%s, %s)" % (device, uuid))
+ raise Rejected("Not implemented")
+
+ @dbus.service.method(AGENT_INTF, in_signature="o", out_signature="s")
+ def RequestPinCode(self, device):
+ print("RequestPinCode (%s)" % (device))
+ dev_trusted(device)
+ return '0000'
+
+ @dbus.service.method(AGENT_INTF, in_signature="o", out_signature="u")
+ def RequestPasskey(self, device):
+ print("RequestPasskey (%s)" % (device))
+ dev_trusted(device)
+ return dbus.UInt32('0000')
+
+ @dbus.service.method(AGENT_INTF, in_signature="ouq", out_signature="")
+ def DisplayPasskey(self, device, passkey, entered):
+ print("DisplayPasskey (%s, %06u entered %u)" %
+ (device, passkey, entered))
+
+ @dbus.service.method(AGENT_INTF, in_signature="os", out_signature="")
+ def DisplayPinCode(self, device, pincode):
+ print("DisplayPinCode (%s, %s)" % (device, pincode))
+
+ @dbus.service.method(AGENT_INTF, in_signature="ou", out_signature="")
+ def RequestConfirmation(self, device, passkey):
+ print("RequestConfirmation (%s, %06d)" % (device, passkey))
+ raise Rejected("Not implemented")
+
+ @dbus.service.method(AGENT_INTF, in_signature="o", out_signature="")
+ def RequestAuthorization(self, device):
+ print("RequestAuthorization (%s)" % (device))
+ raise Rejected("Not implemented")
+
+ @dbus.service.method(AGENT_INTF, in_signature="", out_signature="")
+ def Cancel(self):
+ print("Cancel")
+
+def register_agent():
+ bus = g['bus']
+ obj = bus.get_object(BLUEZ_BUS, BLUEZ_ROOT_OBJ)
+ mgr = dbus.Interface(obj, AGENT_MGR_INTF)
+ mgr.RegisterAgent(AGENT_PATH, AGENT_CAPABILITY)
+ mgr.RequestDefaultAgent(AGENT_PATH)
+ print("Agent registered")
+
+def unregister_agent():
+ bus = g['bus']
+ obj = bus.get_object(BLUEZ_BUS, BLUEZ_ROOT_OBJ)
+ mgr = dbus.Interface(obj, AGENT_MGR_INTF)
+ mgr.UnregisterAgent(AGENT_PATH)
+ print("Agent unregistered")
+
+def set_le_adv_ind_filter(adap_path, dev_addr):
+ name = adap_path.split('/')[-1]
+ path = "/sys/kernel/debug/bluetooth/%s/le_adv_ind_filter" % name
+ try:
+ f = open(path, 'w')
+ f.write(dev_addr)
+ f.close()
+ except IOError as e:
+ print("%s" % e)
+
+def dev_pair_and_connect(path):
+ bus = g['bus']
+ obj = bus.get_object(BLUEZ_BUS, path)
+ dev = dbus.Interface(obj, DEVICE_INTF)
+ props = dbus.Interface(obj, PROPERTY_INTF)
+ adap_path = props.Get(DEVICE_INTF, "Adapter")
+ paired = props.Get(DEVICE_INTF, "Paired")
+
+ if paired == True:
+ print("%s is already paired" % (path))
+ return
+
+ def dev_pair_reply():
+ print("Device pairing completed for %s" % path)
+ set_le_adv_ind_filter(adap_path, '00:00:00:00:00:00')
+ props.Set(DEVICE_INTF, "Trusted", True)
+ dev.Connect()
+
+ def dev_pair_error(error):
+ print("Device pairing failed for %s" % path)
+ set_le_adv_ind_filter(adap_path, '00:00:00:00:00:00')
+ err_name = error.get_dbus_name()
+ print("Device pairing error: %s" % err_name)
+ dev.CancelPairing()
+
+ dev_addr = props.Get(DEVICE_INTF, "Address")
+ set_le_adv_ind_filter(adap_path, dev_addr)
+ dev.Pair(reply_handler=dev_pair_reply, error_handler=dev_pair_error, timeout=15.0)
+
+def get_name_and_addr(path):
+ bus = g['bus']
+ obj = bus.get_object(BLUEZ_BUS, path)
+ props = dbus.Interface(obj, PROPERTY_INTF)
+ addr = props.Get(DEVICE_INTF, "Address")
+ try:
+ name = props.Get(DEVICE_INTF, "Name")
+ except dbus.exceptions.DBusException:
+ name = ""
+ return (name, addr)
+
+def is_gfiber_remote(name):
+ return (name == DEV_NAME_GFRM100 or
+ name == DEV_NAME_GFRM200 or
+ name == DEV_NAME_GFRM210 or
+ name == DEV_NAME_GFRM201)
+
+def interfaces_added(path, interfaces):
+ if not DEVICE_INTF in interfaces:
+ return
+ (name, addr) = get_name_and_addr(path)
+ print("Discovered %s [%s] [%s]" % (path, addr, name))
+ if (is_gfiber_remote(name) or
+ name == DEV_NAME_TIARC or
+ addr.startswith(DEV_OUI_TIARC)):
+ print("Pair with %s [%s] [%s]" % (path, addr, name))
+ dev_pair_and_connect(path)
+
+def properties_changed(interface, changed, invalidated, path):
+ if interface != DEVICE_INTF:
+ return
+ connected = changed.get('Connected', False)
+ if not connected:
+ return
+ (name, addr) = get_name_and_addr(path)
+ if is_gfiber_remote(name):
+ jsonfile = os.path.join(g['adaptdir'], addr, 'gfiber-inventory')
+ subprocess.call(["gfrm-inventory", addr, jsonfile])
+
+
+def main():
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+ g['bus'] = bus
+ g['agent'] = Agent(bus, AGENT_PATH)
+
+ if len(sys.argv) == 1:
+ print("usage: %s adaptpath" % sys.argv[0])
+ print(" where adaptpath is the adaptor directory like "
+ "/user/bluez/lib/bluetooth/F4:F5:E8:80:XX:XX")
+ sys.exit(1)
+ g['adaptdir'] = sys.argv[1]
+
+ register_agent()
+ bus.add_signal_receiver(interfaces_added, bus_name=BLUEZ_BUS,
+ dbus_interface=OBJECT_MGR_INTF,
+ signal_name="InterfacesAdded")
+ bus.add_signal_receiver(properties_changed,
+ dbus_interface=PROPERTY_INTF,
+ signal_name="PropertiesChanged",
+ arg0=DEVICE_INTF, path_keyword="path")
+
+ mainloop = GObject.MainLoop()
+ mainloop.run()
+
+ bus.remove_signal_receiver(interfaces_added, bus_name=BLUEZ_BUS,
+ dbus_interface=OBJECT_MGR_INTF,
+ signal_name="InterfacesAdded")
+ bus.remove_signal_receiver(properties_changed,
+ dbus_interface=PROPERTY_INTF,
+ signal_name="PropertiesChanged",
+ arg0=DEVICE_INTF, path_keyword="path")
+ unregister_agent()
+
+if __name__ == '__main__':
+ main()
diff --git a/test/gfrm-inventory b/test/gfrm-inventory
new file mode 100755
index 0000000..93053f4
--- /dev/null
+++ b/test/gfrm-inventory
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+
+import copy
+import dbus
+import dbus.mainloop.glib
+import errno
+import gobject
+import json
+import os
+import sys
+
+
+FIRMWARE_VERSION_UUID = u'00002a26-0000-1000-8000-00805f9b34fb'
+HARDWARE_REV_UUID = u'00002a27-0000-1000-8000-00805f9b34fb'
+SERIAL_NUMBER_UUID = u'00002a25-0000-1000-8000-00805f9b34fb'
+
+
+# Inventory information for the remote control in question.
+rcu = {}
+orig = {}
+status = {'nqueries': 0}
+
+
+def TimeoutInventory():
+ mainloop.quit()
+
+
+def CheckIfCallbacksDone():
+ """Decrement count of outstanding queries."""
+ status['nqueries'] -= 1
+ if status['nqueries'] <= 0:
+ mainloop.quit()
+
+
+def FirmwareVersionCallback(value):
+ """Render the firmware version string like 'T0055.12 G001E.04'."""
+ rcu['firmware'] = ''.join([chr(byte) for byte in value])
+ CheckIfCallbacksDone()
+
+
+def HardwareVersionCallback(value):
+ """Render the Google Part Number string like 'GPN#07081865-01'.
+
+ The GPN string contains a trailing NUL, which is annoying so we suppress it.
+ """
+ rcu['hardware'] = ''.join([chr(byte) for byte in value if byte != 0])
+ CheckIfCallbacksDone()
+
+
+def SerialNumberCallback(value):
+ """Render the serial number.
+
+ The Serial number is a series of integers.
+ The first is always '2', not sure what it means.
+ Then (all numbers are decimal):
+ YY == last two digits of the year, like 15 for 2015.
+ WW == week number, where 51 would be the last week of the year.
+ DD == day of the week, 0-6.
+ LL == "line number" in the factory.
+ XX XX XX XX XX == an incrementing digit.
+
+ The sticker on the remote inserts a space as the third character, so this:
+ 2, 14, 51, 2, 0, 0, 0, 20, 0, 0
+ is printed as '21451 20002000'. We match this formatting.
+ """
+
+ array = [int(byte) for byte in value]
+ array.insert(3, ' ')
+ rcu['serial'] = ''.join([str(byte) for byte in array])
+ CheckIfCallbacksDone()
+
+
+def DBusErrorHandler(error):
+ sys.stderr.write('D-Bus call failed: ' + str(error))
+ mainloop.quit()
+
+
+def GetDeviceByAddress(remote):
+ """Look up a device by its BDADDR, like 5C:31:3E:08:25:44."""
+ bus = dbus.SystemBus()
+ om = dbus.Interface(bus.get_object('org.bluez', '/'),
+ 'org.freedesktop.DBus.ObjectManager')
+ objects = om.GetManagedObjects()
+ for path, interfaces in objects.iteritems():
+ if 'org.bluez.Device1' in interfaces:
+ device = interfaces['org.bluez.Device1']
+ if device.get('Address', 'noaddress').lower() == remote.lower():
+ return device
+ return {}
+
+
+def GetGattService(device):
+ """Return the General GATT service for device."""
+ bus = dbus.SystemBus()
+ service_path = ''
+ for s in device.get('GattServices', []):
+ if 'service0010' in s:
+ service_path = s
+ if not service_path:
+ return None
+ service = bus.get_object('org.bluez', service_path)
+ p = 'org.freedesktop.DBus.Properties'
+ return service.GetAll('org.bluez.GattService1', dbus_interface=p)
+
+
+def CallbackIfInteresting(uuid):
+ """If we are interested in this UUID, return the callback for it."""
+ if uuid == FIRMWARE_VERSION_UUID:
+ return FirmwareVersionCallback
+ elif uuid == HARDWARE_REV_UUID:
+ return HardwareVersionCallback
+ elif uuid == SERIAL_NUMBER_UUID:
+ return SerialNumberCallback
+
+
+def GetRcuGattCharacteristics(service):
+ """Send queries for the characteristics we are interested in."""
+ bus = dbus.SystemBus()
+ characteristics = service.get('Characteristics', [])
+ for path in characteristics:
+ c = bus.get_object('org.bluez', path)
+ props = c.GetAll('org.bluez.GattCharacteristic1',
+ dbus_interface='org.freedesktop.DBus.Properties')
+ uuid = props.get('UUID', None)
+ callback = CallbackIfInteresting(uuid)
+ if callback:
+ status['nqueries'] += 1
+ c.ReadValue(reply_handler=callback,
+ error_handler=DBusErrorHandler,
+ dbus_interface='org.bluez.GattCharacteristic1')
+
+
+def ReadFromJson(filename):
+ try:
+ with open(filename) as f:
+ return json.load(f)
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ return {}
+
+
+def MakeDirectoryIfNotExist(directory):
+ try:
+ os.makedirs(directory)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+
+def WriteToJsonIfChanged(filename):
+ """Write the contents of 'rcu' to filename if they have changed."""
+ if rcu == orig:
+ return
+ with open(filename + '.tmp', 'w') as f:
+ json.dump(rcu, f, sort_keys=True)
+ os.rename(filename + '.tmp', filename)
+
+
+def usage():
+ sys.stderr.write('usage: %s addr jsonfile' % sys.argv[0])
+ sys.stderr.write(' where addr is a Bluetooth device BDADDR')
+ sys.stderr.write(' and jsonfile is the path to a JSON file to update')
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 3:
+ usage()
+
+ remote = sys.argv[1]
+ jsonfile = sys.argv[2]
+
+ directory = os.path.dirname(jsonfile)
+ MakeDirectoryIfNotExist(directory)
+
+ # Bluetooth is kindof flakey. We want to always retain information we've
+ # retrieved from a remote, and supplement it with any additional information
+ # we find this time (or when things change, like a firmware update).
+ #
+ # We don't want to start afresh each time the remote appears and expect to
+ # always reliably retrieve its inventory information.
+ orig.update(ReadFromJson(jsonfile))
+ rcu = copy.copy(orig)
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ gobject.timeout_add(20000, TimeoutInventory)
+ device = GetDeviceByAddress(remote)
+ if 'Name' in device:
+ rcu['model'] = device['Name']
+ if 'Address' in device:
+ rcu['bdaddr'] = device['Address']
+
+ # Get more information from BLE remotes like GFRM200/GFRM210.
+ service = GetGattService(device)
+ if service:
+ GetRcuGattCharacteristics(service)
+ mainloop = gobject.MainLoop()
+ mainloop.run()
+
+ WriteToJsonIfChanged(jsonfile)
diff --git a/test/test-device b/test/test-device
index b490d53..310f9cd 100755
--- a/test/test-device
+++ b/test/test-device
@@ -38,6 +38,7 @@
print(" alias <address> [alias]")
print(" trusted <address> [yes/no]")
print(" blocked <address> [yes/no]")
+ print(" unplug <address> [yes/no]")
sys.exit(1)
if (args[0] == "list"):
@@ -198,5 +199,26 @@
props.Set("org.bluez.Device1", "Blocked", value)
sys.exit(0)
+if (args[0] == "unplug"):
+ if (len(args) < 2):
+ print("Need address parameter")
+ else:
+ device = bluezutils.find_device(args[1], options.dev_id)
+ path = device.object_path
+ props = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.freedesktop.DBus.Properties")
+ if (len(args) < 3):
+ unplug = props.Get("org.bluez.Device1", "Unplug")
+ print(unplug)
+ else:
+ if (args[2] == "yes"):
+ value = dbus.Boolean(1)
+ elif (args[2] == "no"):
+ value = dbus.Boolean(0)
+ else:
+ value = dbus.Boolean(args[2])
+ props.Set("org.bluez.Device1", "Unplug", value)
+ sys.exit(0)
+
print("Unknown command")
sys.exit(1)
diff --git a/test/unplug-GFRM100 b/test/unplug-GFRM100
new file mode 100755
index 0000000..f89eb25
--- /dev/null
+++ b/test/unplug-GFRM100
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import dbus.mainloop.glib
+
+
+def SetUnplug(path):
+ bus = dbus.SystemBus()
+ props = dbus.Interface(bus.get_object('org.bluez', path),
+ 'org.freedesktop.DBus.Properties')
+ value = dbus.Boolean(1)
+ props.Set('org.bluez.Device1', 'Unplug', value)
+
+
+def getAllDevices():
+ bus = dbus.SystemBus()
+ om = dbus.Interface(bus.get_object('org.bluez', '/'),
+ 'org.freedesktop.DBus.ObjectManager')
+ objects = om.GetManagedObjects()
+ devices = {}
+ for path, interfaces in objects.iteritems():
+ if 'org.bluez.Device1' in interfaces:
+ devices[path] = interfaces['org.bluez.Device1']
+ return devices
+
+
+def GetAllRemotes():
+ devices = getAllDevices()
+ remotes = []
+ for (key, value) in devices.iteritems():
+ name = value.get('Name', '')
+ if name == 'GFRM100':
+ remotes.append(key)
+ return remotes
+
+
+def GetDevicesByAddresses(addresses):
+ devices = getAllDevices()
+ compare = [a.lower() for a in addresses]
+ remotes = []
+ for (key, value) in devices.iteritems():
+ a = value.get('Address', '').lower()
+ if a in compare:
+ remotes.append(key)
+ return remotes
+
+
+def usage():
+ print 'usage: %s [addr1 addr2 | all]' % sys.argv[0]
+ print ' where addr is a Bluetooth device BDADDR'
+ print ' "all" will unplug all paired GFRM100 devices.'
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ if len(sys.argv) == 1:
+ usage()
+ if len(sys.argv) > 2 and sys.argv[1] == 'all':
+ usage()
+
+ if sys.argv[1] == 'all':
+ remotes = GetAllRemotes()
+ else:
+ remotes = GetDevicesByAddresses(sys.argv[1:])
+
+ for remote in remotes:
+ SetUnplug(remote)
diff --git a/tools/hciconfig.c b/tools/hciconfig.c
index 8a97cc4..a609008 100644
--- a/tools/hciconfig.c
+++ b/tools/hciconfig.c
@@ -1470,6 +1470,43 @@
hci_close_dev(s);
}
+static void cmd_page_type(int ctl, int hdev, char *opt)
+{
+ int dd;
+
+ dd = hci_open_dev(hdev);
+ if (dd < 0) {
+ fprintf(stderr, "Can't open device hci%d: %s (%d)\n",
+ hdev, strerror(errno), errno);
+ exit(1);
+ }
+
+ if (opt) {
+ uint8_t type = atoi(opt);
+
+ if (hci_write_page_scan_type(dd, type, 2000) < 0) {
+ fprintf(stderr, "Can't set page scan type on hci%d: %s (%d)\n",
+ hdev, strerror(errno), errno);
+ exit(1);
+ }
+ } else {
+ uint8_t type;
+
+ if (hci_read_page_scan_type(dd, &type, 1000) < 0) {
+ fprintf(stderr, "Can't read page scan type on hci%d: %s (%d)\n",
+ hdev, strerror(errno), errno);
+ exit(1);
+ }
+
+ print_dev_hdr(&di);
+ printf("\tPage scan type: %s\n",
+ type == PAGE_SCAN_TYPE_INTERLACED ?
+ "Interlaced Page Scan" : "Standard Page Scan");
+ }
+
+ hci_close_dev(dd);
+}
+
static void cmd_page_parms(int ctl, int hdev, char *opt)
{
struct hci_request rq;
@@ -1950,6 +1987,7 @@
{ "inqdata", cmd_inq_data, "[data]", "Get/Set inquiry data" },
{ "inqtype", cmd_inq_type, "[type]", "Get/Set inquiry scan type" },
{ "inqparms", cmd_inq_parms, "[win:int]", "Get/Set inquiry scan window and interval" },
+ { "pagetype", cmd_page_type, "[type]", "Get/Set page scan type" },
{ "pageparms", cmd_page_parms, "[win:int]", "Get/Set page scan window and interval" },
{ "pageto", cmd_page_to, "[to]", "Get/Set page timeout" },
{ "afhmode", cmd_afh_mode, "[mode]", "Get/Set AFH mode" },