Hi,
This adds initial code for BTP client. BTP stands for Bluetooth Tester
Protocol and aims at automated testing of BT stack. BTP is binary
protocol and is already implemented in Zephyr Project. This adds
similar client to BlueZ tree. BTP specification is available at [1].
Notable thing about this serie is that it doesn't use glib, gdbus or
utils from src/shared folder. Instead it makes use of Embedded Linux
Library which is available at [2].
Comments are welcome.
BR
Szymon Janc
[1] https://github.com/zephyrproject-rtos/zephyr/blob/master/tests/bluetooth/tester/btp_spec.txt
[2] https://git.kernel.org/pub/scm/libs/ell/ell.git/
Szymon Janc (9):
shared/btp: Add initial code for library
tools/btpclient: Add initial code
shared/btp: Add definitions for GAP service
tools/btpclient: Store index along with adapter proxy
tools/btpclient: Add initial support for GAP service
tools/btpclient: Add initial support for read controller info command
tools/btpclient: Get initial values for adapter setttings
tools/btpclient: Add support for tracking mutable adapter settings
tools/btpclient: Add support for configuring adapter settings
.gitignore | 1 +
Makefile.tools | 8 +
configure.ac | 11 +
src/shared/btp.c | 385 ++++++++++++++++++++++++++
src/shared/btp.h | 321 ++++++++++++++++++++++
tools/btpclient.c | 788 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 1514 insertions(+)
create mode 100644 src/shared/btp.c
create mode 100644 src/shared/btp.h
create mode 100644 tools/btpclient.c
--
2.14.3
Hi Luiz,
On Thursday, 7 December 2017 18:42:55 CET Luiz Augusto von Dentz wrote:
> Hi Szymon,
>
> On Thu, Dec 7, 2017 at 12:21 PM, Szymon Janc <[email protected]>
wrote:
> > ---
> >
> > tools/btpclient.c | 10 ++++++++++
> > 1 file changed, 10 insertions(+)
> >
> > diff --git a/tools/btpclient.c b/tools/btpclient.c
> > index c7ff239b0..3a4971eeb 100644
> > --- a/tools/btpclient.c
> > +++ b/tools/btpclient.c
> > @@ -37,6 +37,7 @@
> >
> > struct btp_adapter {
> >
> > struct l_dbus_proxy *proxy;
> >
> > + unsigned int index;
> >
> > };
> >
> > struct btp_device {
> >
> > @@ -157,9 +158,18 @@ static void proxy_added(struct l_dbus_proxy *proxy,
> > void *user_data)>
> > if (!strcmp(interface, "org.bluez.Adapter1")) {
> >
> > struct btp_adapter *adapter;
> >
> > + unsigned int index;
> > +
> > + /* TODO should we rely on path for index or use other
> > mapping + * like queue size?
> > + */
> > + if (sscanf(l_dbus_proxy_get_path(proxy),
> > "/org/bluez/hci%u", +
> > &index) != 1) + return;
>
> Usually, we don't operate by index over D-Bus so if all operation are
> over D-Bus knowing the index shall not be needed, if this is for BTP
> then we could perhaps just map directly to the queue directly but Im
> not sure why would BTP have an adapter index?
This is only for BTP to identify adapter, I'll just simple counter. If adapter
is gone we terminate anyway. And index is part of BTP header for historical
reasons:)
>
> > adapter = l_new(struct btp_adapter, 1);
> > adapter->proxy = proxy;
> >
> > + adapter->index = index;
> >
> > l_queue_push_tail(adapters, adapter);
> > return;
> >
> > --
> > 2.14.3
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-bluetooth"
> > in the body of a message to [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
--
pozdrawiam
Szymon Janc
Hi Szymon,
On Thu, Dec 7, 2017 at 12:21 PM, Szymon Janc <[email protected]> wrote:
> ---
> tools/btpclient.c | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/tools/btpclient.c b/tools/btpclient.c
> index c7ff239b0..3a4971eeb 100644
> --- a/tools/btpclient.c
> +++ b/tools/btpclient.c
> @@ -37,6 +37,7 @@
>
> struct btp_adapter {
> struct l_dbus_proxy *proxy;
> + unsigned int index;
> };
>
> struct btp_device {
> @@ -157,9 +158,18 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
>
> if (!strcmp(interface, "org.bluez.Adapter1")) {
> struct btp_adapter *adapter;
> + unsigned int index;
> +
> + /* TODO should we rely on path for index or use other mapping
> + * like queue size?
> + */
> + if (sscanf(l_dbus_proxy_get_path(proxy), "/org/bluez/hci%u",
> + &index) != 1)
> + return;
Usually, we don't operate by index over D-Bus so if all operation are
over D-Bus knowing the index shall not be needed, if this is for BTP
then we could perhaps just map directly to the queue directly but Im
not sure why would BTP have an adapter index?
> adapter = l_new(struct btp_adapter, 1);
> adapter->proxy = proxy;
> + adapter->index = index;
>
> l_queue_push_tail(adapters, adapter);
> return;
> --
> 2.14.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Luiz Augusto von Dentz
This allows to get list of supported commands and present adapters.
---
tools/btpclient.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 116 insertions(+)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index 3a4971eeb..e63159824 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -49,6 +49,69 @@ static struct l_queue *devices;
static char *socket_path;
static struct btp *btp;
+static bool gap_service_registered;
+
+static void btp_gap_read_commands(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ uint16_t commands = 0;
+
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_GAP_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ commands |= (1 << BTP_OP_GAP_READ_SUPPORTED_COMMANDS);
+ commands |= (1 << BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST);
+
+ commands = L_CPU_TO_LE16(commands);
+
+ btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS,
+ BTP_INDEX_NON_CONTROLLER, sizeof(commands), &commands);
+}
+
+static void btp_gap_read_controller_index(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ const struct l_queue_entry *entry;
+ struct btp_gap_read_index_rp *rp;
+ uint8_t cnt;
+ int i;
+
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_GAP_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ cnt = l_queue_length(adapters);
+
+ rp = l_malloc(sizeof(*rp) + cnt);
+
+ rp->num = cnt;
+
+ for (i = 0, entry = l_queue_get_entries(adapters); entry;
+ i++, entry = entry->next) {
+ struct btp_adapter *adapter = entry->data;
+
+ rp->indexes[i] = adapter->index;
+ }
+
+ btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST,
+ BTP_INDEX_NON_CONTROLLER, sizeof(*rp) + cnt, rp);
+}
+
+static void register_gap_service(void)
+{
+ btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS,
+ btp_gap_read_commands, NULL, NULL);
+
+ btp_register(btp, BTP_GAP_SERVICE,
+ BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST,
+ btp_gap_read_controller_index, NULL, NULL);
+}
+
static void btp_core_read_commands(uint16_t index, const void *param,
uint16_t length, void *user_data)
{
@@ -81,6 +144,7 @@ static void btp_core_read_services(uint16_t index, const void *param,
}
services |= (1 << BTP_CORE_SERVICE);
+ services |= (1 << BTP_GAP_SERVICE);
btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_SERVICES,
BTP_INDEX_NON_CONTROLLER, sizeof(services), &services);
@@ -89,24 +153,76 @@ static void btp_core_read_services(uint16_t index, const void *param,
static void btp_core_register(uint16_t index, const void *param,
uint16_t length, void *user_data)
{
+ const struct btp_core_register_cp *cp = param;
+
+ if (length < sizeof(*cp))
+ goto failed;
+
if (index != BTP_INDEX_NON_CONTROLLER) {
btp_send_error(btp, BTP_CORE_SERVICE, index,
BTP_ERROR_INVALID_INDEX);
return;
}
+ switch (cp->service_id) {
+ case BTP_GAP_SERVICE:
+ if (gap_service_registered)
+ goto failed;
+
+ register_gap_service();
+ gap_service_registered = true;
+ break;
+ case BTP_GATT_SERVICE:
+ case BTP_L2CAP_SERVICE:
+ case BTP_MESH_NODE_SERVICE:
+ case BTP_CORE_SERVICE:
+ default:
+ goto failed;
+ }
+
+ btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_REGISTER,
+ BTP_INDEX_NON_CONTROLLER, 0, NULL);
+ return;
+
+failed:
btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL);
}
static void btp_core_unregister(uint16_t index, const void *param,
uint16_t length, void *user_data)
{
+ const struct btp_core_unregister_cp *cp = param;
+
+ if (length < sizeof(*cp))
+ goto failed;
+
if (index != BTP_INDEX_NON_CONTROLLER) {
btp_send_error(btp, BTP_CORE_SERVICE, index,
BTP_ERROR_INVALID_INDEX);
return;
}
+ switch (cp->service_id) {
+ case BTP_GAP_SERVICE:
+ if (!gap_service_registered)
+ goto failed;
+
+ btp_unregister_service(btp, BTP_GAP_SERVICE);
+ gap_service_registered = false;
+ break;
+ case BTP_GATT_SERVICE:
+ case BTP_L2CAP_SERVICE:
+ case BTP_MESH_NODE_SERVICE:
+ case BTP_CORE_SERVICE:
+ default:
+ goto failed;
+ }
+
+ btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_UNREGISTER,
+ BTP_INDEX_NON_CONTROLLER, 0, NULL);
+ return;
+
+failed:
btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL);
}
--
2.14.3
---
tools/btpclient.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index e63159824..7db65b8b4 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -38,6 +38,8 @@
struct btp_adapter {
struct l_dbus_proxy *proxy;
unsigned int index;
+ uint32_t supported_settings;
+ uint32_t current_settings;
};
struct btp_device {
@@ -51,6 +53,21 @@ static struct btp *btp;
static bool gap_service_registered;
+static struct btp_adapter *find_adapter_by_index(uint8_t index)
+{
+ const struct l_queue_entry *entry;
+
+ for (entry = l_queue_get_entries(adapters); entry;
+ entry = entry->next) {
+ struct btp_adapter *adapter = entry->data;
+
+ if (adapter->index == index)
+ return adapter;
+ }
+
+ return NULL;
+}
+
static void btp_gap_read_commands(uint16_t index, const void *param,
uint16_t length, void *user_data)
{
@@ -64,6 +81,7 @@ static void btp_gap_read_commands(uint16_t index, const void *param,
commands |= (1 << BTP_OP_GAP_READ_SUPPORTED_COMMANDS);
commands |= (1 << BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST);
+ commands |= (1 << BTP_OP_GAP_READ_COTROLLER_INFO);
commands = L_CPU_TO_LE16(commands);
@@ -102,6 +120,46 @@ static void btp_gap_read_controller_index(uint16_t index, const void *param,
BTP_INDEX_NON_CONTROLLER, sizeof(*rp) + cnt, rp);
}
+static void btp_gap_read_info(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ struct btp_adapter *adapter = find_adapter_by_index(index);
+ struct btp_gap_read_info_rp rp;
+ const char *str;
+ uint8_t status = BTP_ERROR_FAIL;
+
+ if (!adapter) {
+ status = BTP_ERROR_INVALID_INDEX;
+ goto failed;
+ }
+
+ memset(&rp, 0, sizeof(rp));
+
+ if (!l_dbus_proxy_get_property(adapter->proxy, "Address", "s", &str))
+ goto failed;
+
+ if (sscanf(str,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &rp.address[5], &rp.address[4], &rp.address[3],
+ &rp.address[2], &rp.address[1], &rp.address[0]) != 6)
+ goto failed;
+
+ if (!l_dbus_proxy_get_property(adapter->proxy, "Name", "s", &str)) {
+ goto failed;
+ }
+
+ strncpy((char *) rp.name, str, sizeof(rp.name));
+ strncpy((char *) rp.short_name, str, sizeof(rp.short_name));
+ rp.supported_settings = L_CPU_TO_LE32(adapter->supported_settings);
+ rp.current_settings = L_CPU_TO_LE32(adapter->current_settings);
+
+ btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_COTROLLER_INFO, index,
+ sizeof(rp), &rp);
+
+ return;
+failed:
+ btp_send_error(btp, BTP_GAP_SERVICE, index, status);
+}
+
static void register_gap_service(void)
{
btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS,
@@ -110,6 +168,9 @@ static void register_gap_service(void)
btp_register(btp, BTP_GAP_SERVICE,
BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST,
btp_gap_read_controller_index, NULL, NULL);
+
+ btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_COTROLLER_INFO,
+ btp_gap_read_info, NULL, NULL);
}
static void btp_core_read_commands(uint16_t index, const void *param,
--
2.14.3
This allows to configure Powered, Discoverable and Pairable options.
---
tools/btpclient.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index 2c7c40241..7a3bb80c3 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -175,6 +175,136 @@ failed:
btp_send_error(btp, BTP_GAP_SERVICE, index, status);
}
+struct set_setting_data {
+ struct btp_adapter *adapter;
+ uint8_t opcode;
+ uint32_t setting;
+ bool value;
+};
+
+static void set_setting_reply(struct l_dbus_proxy *proxy,
+ struct l_dbus_message *result, void *user_data)
+{
+ struct set_setting_data *data = user_data;
+ struct btp_adapter *adapter = data->adapter;
+ uint32_t settings;
+
+ if (l_dbus_message_is_error(result)) {
+ btp_send_error(btp, BTP_GAP_SERVICE, data->adapter->index,
+ BTP_ERROR_FAIL);
+ return;
+ }
+
+ if (data->value)
+ adapter->current_settings |= data->setting;
+ else
+ adapter->current_settings &= ~data->setting;
+
+ settings = L_CPU_TO_LE32(adapter->current_settings);
+
+ btp_send(btp, BTP_GAP_SERVICE, data->opcode, adapter->index,
+ sizeof(settings), &settings);
+}
+
+static void btp_gap_set_powered(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ struct btp_adapter *adapter = find_adapter_by_index(index);
+ const struct btp_gap_set_powered_cp *cp = param;
+ uint8_t status = BTP_ERROR_FAIL;
+ struct set_setting_data *data;
+
+ if (length < sizeof(*cp))
+ goto failed;
+
+ if (!adapter) {
+ status = BTP_ERROR_INVALID_INDEX;
+ goto failed;
+ }
+
+ data = l_new(struct set_setting_data, 1);
+ data->adapter = adapter;
+ data->opcode = BTP_OP_GAP_SET_POWERED;
+ data->setting = BTP_GAP_SETTING_POWERED;
+ data->value = cp->powered;
+
+ if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply,
+ data, l_free, "Powered", "b",
+ &data->value))
+ return;
+
+ l_free(data);
+
+failed:
+ btp_send_error(btp, BTP_GAP_SERVICE, index, status);
+}
+
+static void btp_gap_set_discoverable(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ struct btp_adapter *adapter = find_adapter_by_index(index);
+ const struct btp_gap_set_discoverable_cp *cp = param;
+ uint8_t status = BTP_ERROR_FAIL;
+ struct set_setting_data *data;
+
+ if (length < sizeof(*cp))
+ goto failed;
+
+ if (!adapter) {
+ status = BTP_ERROR_INVALID_INDEX;
+ goto failed;
+ }
+
+ data = l_new(struct set_setting_data, 1);
+ data->adapter = adapter;
+ data->opcode = BTP_OP_GAP_SET_DISCOVERABLE;
+ data->setting = BTP_GAP_SETTING_DISCOVERABLE;
+ data->value = cp->discoverable;
+
+ if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply,
+ data, l_free, "Discoverable", "b",
+ &data->value))
+ return;
+
+ l_free(data);
+
+failed:
+ btp_send_error(btp, BTP_GAP_SERVICE, index, status);
+}
+
+static void btp_gap_set_bondable(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ struct btp_adapter *adapter = find_adapter_by_index(index);
+ const struct btp_gap_set_bondable_cp *cp = param;
+ uint8_t status = BTP_ERROR_FAIL;
+ struct set_setting_data *data;
+
+ if (length < sizeof(*cp))
+ goto failed;
+
+ if (!adapter) {
+ status = BTP_ERROR_INVALID_INDEX;
+ goto failed;
+ }
+
+ data = l_new(struct set_setting_data, 1);
+ data->adapter = adapter;
+ data->opcode = BTP_OP_GAP_SET_BONDABLE;
+ data->setting = BTP_GAP_SETTING_BONDABLE;
+ data->value = cp->bondable;
+
+ if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply,
+ data, l_free, "Pairable", "b",
+ &data->value))
+ return;
+
+ l_free(data);
+
+failed:
+ btp_send_error(btp, BTP_GAP_SERVICE, index, status);
+}
+
static void register_gap_service(void)
{
btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS,
@@ -186,6 +316,15 @@ static void register_gap_service(void)
btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_COTROLLER_INFO,
btp_gap_read_info, NULL, NULL);
+
+ btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_POWERED,
+ btp_gap_set_powered, NULL, NULL);
+
+ btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_DISCOVERABLE,
+ btp_gap_set_discoverable, NULL, NULL);
+
+ btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_BONDABLE,
+ btp_gap_set_bondable, NULL, NULL);
}
static void btp_core_read_commands(uint16_t index, const void *param,
--
2.14.3
---
tools/btpclient.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index 7db65b8b4..5f7fb85f1 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -326,6 +326,49 @@ static void btp_device_free(struct btp_device *device)
l_free(device);
}
+static void extract_settings(struct l_dbus_proxy *proxy, uint32_t *current,
+ uint32_t *supported)
+{
+ bool prop;
+
+ *supported = 0;
+ *current = 0;
+
+ /* TODO not all info is available via D-Bus API */
+ *supported |= BTP_GAP_SETTING_POWERED;
+ *supported |= BTP_GAP_SETTING_CONNECTABLE;
+ *supported |= BTP_GAP_SETTING_DISCOVERABLE;
+ *supported |= BTP_GAP_SETTING_BONDABLE;
+ *supported |= BTP_GAP_SETTING_SSP;
+ *supported |= BTP_GAP_SETTING_BREDR;
+ *supported |= BTP_GAP_SETTING_LE;
+ *supported |= BTP_GAP_SETTING_ADVERTISING;
+ *supported |= BTP_GAP_SETTING_SC;
+ *supported |= BTP_GAP_SETTING_PRIVACY;
+ /* *supported |= BTP_GAP_SETTING_STATIC_ADDRESS; */
+
+ /* TODO not all info is availbe via D-Bus API so some are assumed to be
+ * enabled by bluetoothd or simply hardcoded until API is extended
+ */
+ *current |= BTP_GAP_SETTING_CONNECTABLE;
+ *current |= BTP_GAP_SETTING_SSP;
+ *current |= BTP_GAP_SETTING_BREDR;
+ *current |= BTP_GAP_SETTING_LE;
+ *current |= BTP_GAP_SETTING_PRIVACY;
+ *current |= BTP_GAP_SETTING_SC;
+ /* *supported |= BTP_GAP_SETTING_STATIC_ADDRESS; */
+
+ if (l_dbus_proxy_get_property(proxy, "Powered", "b", &prop) && prop)
+ *current |= BTP_GAP_SETTING_POWERED;
+
+ if (l_dbus_proxy_get_property(proxy, "Discoverable", "b", &prop) &&
+ prop)
+ *current |= BTP_GAP_SETTING_DISCOVERABLE;
+
+ if (l_dbus_proxy_get_property(proxy, "Pairable", "b", &prop) && prop)
+ *current |= BTP_GAP_SETTING_BONDABLE;
+}
+
static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
{
const char *interface = l_dbus_proxy_get_interface(proxy);
@@ -348,6 +391,9 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
adapter->proxy = proxy;
adapter->index = index;
+ extract_settings(proxy, &adapter->current_settings,
+ &adapter->supported_settings);
+
l_queue_push_tail(adapters, adapter);
return;
}
--
2.14.3
---
tools/btpclient.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 81 insertions(+), 2 deletions(-)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index 5f7fb85f1..2c7c40241 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -53,6 +53,21 @@ static struct btp *btp;
static bool gap_service_registered;
+static struct btp_adapter *find_adapter_by_proxy(struct l_dbus_proxy *proxy)
+{
+ const struct l_queue_entry *entry;
+
+ for (entry = l_queue_get_entries(adapters); entry;
+ entry = entry->next) {
+ struct btp_adapter *adapter = entry->data;
+
+ if (adapter->proxy == proxy)
+ return adapter;
+ }
+
+ return NULL;
+}
+
static struct btp_adapter *find_adapter_by_index(uint8_t index)
{
const struct l_queue_entry *entry;
@@ -437,11 +452,75 @@ static void proxy_removed(struct l_dbus_proxy *proxy, void *user_data)
}
}
+static void update_current_settings(struct btp_adapter *adapter,
+ uint32_t new_settings)
+{
+ struct btp_new_settings_ev ev;
+
+ adapter->current_settings = new_settings;
+
+ ev.current_settings = L_CPU_TO_LE32(adapter->current_settings);
+
+ btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_NEW_SETTINGS, adapter->index,
+ sizeof(ev), &ev);
+}
+
static void property_changed(struct l_dbus_proxy *proxy, const char *name,
struct l_dbus_message *msg, void *user_data)
{
- l_info("property_changed %s %s %s", name, l_dbus_proxy_get_path(proxy),
- l_dbus_proxy_get_interface(proxy));
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ l_info("property_changed %s %s %s", name, path, interface);
+
+ if (!strcmp(interface, "org.bluez.Adapter1")) {
+ struct btp_adapter *adapter = find_adapter_by_proxy(proxy);
+ uint32_t new_settings;
+
+ if (!adapter)
+ return;
+
+ new_settings = adapter->current_settings;
+
+ if (!strcmp(name, "Powered")) {
+ bool prop;
+
+ if (!l_dbus_message_get_arguments(msg, "b", &prop))
+ return;
+
+ if (prop)
+ new_settings |= BTP_GAP_SETTING_POWERED;
+ else
+ new_settings &= ~BTP_GAP_SETTING_POWERED;
+ } else if (!strcmp(name, "Discoverable")) {
+ bool prop;
+
+ if (!l_dbus_message_get_arguments(msg, "b", &prop))
+ return;
+
+ if (prop)
+ new_settings |= BTP_GAP_SETTING_DISCOVERABLE;
+ else
+ new_settings &= ~BTP_GAP_SETTING_DISCOVERABLE;
+ }
+
+ if (!strcmp(name, "Pairable")) {
+ bool prop;
+
+ if (!l_dbus_message_get_arguments(msg, "b", &prop))
+ return;
+
+ if (prop)
+ new_settings |= BTP_GAP_SETTING_BONDABLE;
+ else
+ new_settings &= ~BTP_GAP_SETTING_BONDABLE;
+ }
+
+ if (new_settings != adapter->current_settings)
+ update_current_settings(adapter, new_settings);
+
+ return;
+ }
}
static void client_connected(struct l_dbus *dbus, void *user_data)
--
2.14.3
---
tools/btpclient.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tools/btpclient.c b/tools/btpclient.c
index c7ff239b0..3a4971eeb 100644
--- a/tools/btpclient.c
+++ b/tools/btpclient.c
@@ -37,6 +37,7 @@
struct btp_adapter {
struct l_dbus_proxy *proxy;
+ unsigned int index;
};
struct btp_device {
@@ -157,9 +158,18 @@ static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
if (!strcmp(interface, "org.bluez.Adapter1")) {
struct btp_adapter *adapter;
+ unsigned int index;
+
+ /* TODO should we rely on path for index or use other mapping
+ * like queue size?
+ */
+ if (sscanf(l_dbus_proxy_get_path(proxy), "/org/bluez/hci%u",
+ &index) != 1)
+ return;
adapter = l_new(struct btp_adapter, 1);
adapter->proxy = proxy;
+ adapter->index = index;
l_queue_push_tail(adapters, adapter);
return;
--
2.14.3
---
src/shared/btp.h | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 225 insertions(+)
diff --git a/src/shared/btp.h b/src/shared/btp.h
index 1bb3d6313..10b729afd 100644
--- a/src/shared/btp.h
+++ b/src/shared/btp.h
@@ -71,6 +71,231 @@ struct btp_core_unregister_cp {
#define BTP_EV_CORE_READY 0x80
+#define BTP_OP_GAP_READ_SUPPORTED_COMMANDS 0x01
+
+#define BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST 0x02
+struct btp_gap_read_index_rp {
+ uint8_t num;
+ uint8_t indexes[0];
+} __packed;
+
+#define BTP_GAP_SETTING_POWERED 0x00000001
+#define BTP_GAP_SETTING_CONNECTABLE 0x00000002
+#define BTP_GAP_SETTING_FAST_CONNECTABLE 0x00000004
+#define BTP_GAP_SETTING_DISCOVERABLE 0x00000008
+#define BTP_GAP_SETTING_BONDABLE 0x00000010
+#define BTP_GAP_SETTING_LL_SECURITY 0x00000020
+#define BTP_GAP_SETTING_SSP 0x00000040
+#define BTP_GAP_SETTING_BREDR 0x00000080
+#define BTP_GAP_SETTING_HS 0x00000100
+#define BTP_GAP_SETTING_LE 0x00000200
+#define BTP_GAP_SETTING_ADVERTISING 0x00000400
+#define BTP_GAP_SETTING_SC 0x00000800
+#define BTP_GAP_SETTING_DEBUG_KEYS 0x00001000
+#define BTP_GAP_SETTING_PRIVACY 0x00002000
+#define BTP_GAP_SETTING_CONTROLLER_CONF 0x00004000
+#define BTP_GAP_SETTING_STATIC_ADDRESS 0x00008000
+
+#define BTP_OP_GAP_READ_COTROLLER_INFO 0x03
+struct btp_gap_read_info_rp {
+ uint8_t address[6];
+ uint32_t supported_settings;
+ uint32_t current_settings;
+ uint8_t cod[3];
+ uint8_t name[249];
+ uint8_t short_name[11];
+} __packed;
+
+#define BTP_OP_GAP_RESET 0x04
+struct btp_gap_reset_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_SET_POWERED 0x05
+struct btp_gap_set_powered_cp {
+ uint8_t powered;
+} __packed;
+
+struct btp_gap_set_powered_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_SET_CONNECTABLE 0x06
+struct btp_gap_set_connectable_cp {
+ uint8_t connectable;
+} __packed;
+
+struct btp_gap_set_connectable_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_SET_FAST_CONNECTABLE 0x07
+struct btp_gap_set_fast_connectable_cp {
+ uint8_t fast_connectable;
+} __packed;
+
+struct btp_gap_set_fast_connectable_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_SET_DISCOVERABLE 0x08
+struct btp_gap_set_discoverable_cp {
+ uint8_t discoverable;
+} __packed;
+
+struct btp_gap_set_discoverable_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_SET_BONDABLE 0x09
+struct btp_gap_set_bondable_cp {
+ uint8_t bondable;
+} __packed;
+
+struct btp_gap_set_bondable_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_START_ADVERTISING 0x0a
+struct btp_gap_start_adv_cp {
+ uint8_t adv_data_len;
+ uint8_t scan_rsp_len;
+ uint8_t data[0];
+} __packed;
+
+struct btp_gap_start_adv_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_OP_GAP_STOP_ADVERTISING 0x0b
+struct btp_gap_stop_adv_rp {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_GAP_DISCOVERY_FLAG_LE 0x01
+#define BTP_GAP_DISCOVERY_FLAG_BREDR 0x02
+#define BTP_GAP_DISCOVERY_FLAG_LIMITED 0x04
+#define BTP_GAP_DISCOVERY_FLAG_ACTIVE 0x08
+#define BTP_GAP_DISCOVERY_FLAG_OBSERVATION 0x10
+
+#define BTP_OP_GAP_START_DISCOVERY 0x0c
+struct btp_gap_start_discovery_cp {
+ uint8_t flags;
+} __packed;
+
+#define BTP_OP_GAP_STOP_DISCOVERY 0x0d
+
+#define BTP_GAP_ADDR_PUBLIC 0x00
+#define BTP_GAP_ADDR_RANDOM 0x01
+
+#define BTP_OP_GAP_CONNECT 0x0e
+struct btp_gap_connect_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_OP_GAP_DISCONNECT 0x0f
+struct btp_gap_disconnect_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_GAP_IOCAPA_DISPLAY_ONLY 0x00
+#define BTP_GAP_IOCAPA_DISPLAY_YESNO 0x01
+#define BTP_GAP_IOCAPA_KEYBOARD_ONLY 0x02
+#define BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT 0x03
+#define BTP_GAP_IOCAPA_KEYBOARD_DISPLAY 0x04
+
+#define BTP_OP_GAP_SET_IO_CAPA 0x10
+struct btp_gap_set_io_capa_cp {
+ uint8_t capa;
+} __packed;
+
+#define BTP_OP_GAP_PAIR 0x11
+struct btp_gap_pair_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_OP_GAP_UNPAIR 0x12
+struct btp_gap_unpair_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_OP_GAP_PASSKEY_ENTRY_RSP 0x13
+struct btp_gap_passkey_entry_rsp_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+ uint32_t passkey;
+} __packed;
+
+#define BTP_OP_GAP_PASSKEY_CONFIRM_RSP 0x14
+struct btp_gap_passkey_confirm_rsp_cp {
+ uint8_t address_type;
+ uint8_t address[6];
+ uint8_t match;
+} __packed;
+
+#define BTP_EV_GAP_NEW_SETTINGS 0x80
+struct btp_new_settings_ev {
+ uint32_t current_settings;
+} __packed;
+
+#define BTP_EV_GAP_DEVICE_FOUND_FLAG_RSSI 0x00
+#define BTP_EV_GAP_DEVICE_FOUND_FLAG_AD 0x01
+#define BTP_EV_GAP_DEVICE_FOUND_FLAG_SR 0x02
+
+#define BTP_EV_GAP_DEVICE_FOUND 0x81
+struct btp_device_found_ev {
+ uint8_t address[6];
+ uint8_t address_type;
+ int8_t rssi;
+ uint8_t flags;
+ uint16_t eir_len;
+ uint8_t eir[0];
+} __packed;
+
+#define BTP_EV_GAP_DEVICE_CONNECTED 0x82
+struct btp_gap_device_connected_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_EV_GAP_DEVICE_DISCONNECTED 0x83
+struct btp_gap_device_disconnected_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_EV_GAP_PASSKEY_DISPLAY 0x84
+struct btp_gap_passkey_display_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+ uint32_t passkey;
+} __packed;
+
+#define BTP_EV_GAP_PASSKEY_REQUEST 0x85
+struct btp_gap_passkey_req_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+} __packed;
+
+#define BTP_EV_GAP_PASSKEY_CONFIRM 0x86
+struct btp_gap_passkey_confirm_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+ uint32_t passkey;
+} __packed;
+
+#define BTP_EV_GAP_IDENTITY_RESOLVED 0x87
+struct btp_gap_identity_resolved_ev {
+ uint8_t address_type;
+ uint8_t address[6];
+ uint8_t identity_address_type;
+ uint8_t identity_address[6];
+} __packed;
+
struct btp;
typedef void (*btp_destroy_func_t)(void *user_data);
--
2.14.3
This adds initial code for BTP client tool that allows for automated
(binary protocol ) control of BlueZ stack. Currently this tool depends
only on Embedded Linux Library and requires master branch of ELL.
When 0.3 is released dependencies will be bumped.
Initial code allows to connect D-Bus client, discover objects and keep
proxies for it. It also implements basics for BTP core service.
---
.gitignore | 1 +
Makefile.tools | 8 ++
configure.ac | 11 ++
tools/btpclient.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 357 insertions(+)
create mode 100644 tools/btpclient.c
diff --git a/.gitignore b/.gitignore
index af205ec6a..393735e0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,6 +116,7 @@ tools/btattach
tools/btconfig
tools/btmgmt
tools/btsnoop
+tools/btpclient
peripheral/btsensor
monitor/btmon
emulator/btvirt
diff --git a/Makefile.tools b/Makefile.tools
index 561302fa1..c4fea3d49 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -446,3 +446,11 @@ test_scripts += test/sap_client.py test/bluezutils.py \
test/pbap-client test/map-client test/example-advertisement \
test/example-gatt-server test/example-gatt-client \
test/test-gatt-profile
+
+if BTPCLIENT
+noinst_PROGRAMS += tools/btpclient
+
+tools_btpclient_SOURCES = tools/btpclient.c src/shared/btp.c src/shared/btp.h
+tools_btpclient_CFLAGS = $(AM_CFLAGS) @ELL_CFLAGS@
+tools_btpclient_LDADD = @ELL_LIBS@
+endif
diff --git a/configure.ac b/configure.ac
index 964101412..fcba28543 100644
--- a/configure.ac
+++ b/configure.ac
@@ -244,6 +244,17 @@ if (test "${enable_obex}" != "no"); then
fi
AM_CONDITIONAL(OBEX, test "${enable_obex}" != "no")
+AC_ARG_ENABLE(btpclient, AC_HELP_STRING([--enable-btpclient],
+ [enable BTP client]), [enable_btpclient=${enableval}])
+AM_CONDITIONAL(BTPCLIENT, test "${enable_btpclient}" = "yes")
+
+if (test "${enable_btpclient}" = "yes"); then
+ PKG_CHECK_MODULES(ELL, ell >= 0.2, dummy=yes,
+ AC_MSG_ERROR(ell library >= 0.2 is required))
+ AC_SUBST(ELL_CFLAGS)
+ AC_SUBST(ELL_LIBS)
+fi
+
AC_ARG_ENABLE(client, AC_HELP_STRING([--disable-client],
[disable command line client]), [enable_client=${enableval}])
AM_CONDITIONAL(CLIENT, test "${enable_client}" != "no")
diff --git a/tools/btpclient.c b/tools/btpclient.c
new file mode 100644
index 000000000..c7ff239b0
--- /dev/null
+++ b/tools/btpclient.c
@@ -0,0 +1,337 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2017 Codecoup. All rights reserved.
+ * Copyright (C) 2011-2016 Intel Corporation. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <getopt.h>
+
+#include <ell/ell.h>
+
+#include "src/shared/btp.h"
+
+struct btp_adapter {
+ struct l_dbus_proxy *proxy;
+};
+
+struct btp_device {
+ struct l_dbus_proxy *proxy;
+};
+
+static struct l_queue *adapters;
+static struct l_queue *devices;
+static char *socket_path;
+static struct btp *btp;
+
+static void btp_core_read_commands(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ uint8_t commands = 0;
+
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_CORE_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ commands |= (1 << BTP_OP_CORE_READ_SUPPORTED_COMMANDS);
+ commands |= (1 << BTP_OP_CORE_READ_SUPPORTED_SERVICES);
+ commands |= (1 << BTP_OP_CORE_REGISTER);
+ commands |= (1 << BTP_OP_CORE_UNREGISTER);
+
+ btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_COMMANDS,
+ BTP_INDEX_NON_CONTROLLER, sizeof(commands), &commands);
+}
+
+static void btp_core_read_services(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ uint8_t services = 0;
+
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_CORE_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ services |= (1 << BTP_CORE_SERVICE);
+
+ btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_SERVICES,
+ BTP_INDEX_NON_CONTROLLER, sizeof(services), &services);
+}
+
+static void btp_core_register(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_CORE_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL);
+}
+
+static void btp_core_unregister(uint16_t index, const void *param,
+ uint16_t length, void *user_data)
+{
+ if (index != BTP_INDEX_NON_CONTROLLER) {
+ btp_send_error(btp, BTP_CORE_SERVICE, index,
+ BTP_ERROR_INVALID_INDEX);
+ return;
+ }
+
+ btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL);
+}
+
+static void register_core_service(void)
+{
+ btp_register(btp, BTP_CORE_SERVICE,
+ BTP_OP_CORE_READ_SUPPORTED_COMMANDS,
+ btp_core_read_commands, NULL, NULL);
+
+ btp_register(btp, BTP_CORE_SERVICE,
+ BTP_OP_CORE_READ_SUPPORTED_SERVICES,
+ btp_core_read_services, NULL, NULL);
+
+ btp_register(btp, BTP_CORE_SERVICE, BTP_OP_CORE_REGISTER,
+ btp_core_register, NULL, NULL);
+
+ btp_register(btp, BTP_CORE_SERVICE, BTP_OP_CORE_UNREGISTER,
+ btp_core_unregister, NULL, NULL);
+}
+
+static void signal_handler(struct l_signal *signal, uint32_t signo,
+ void *user_data)
+{
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ l_info("Terminating");
+ l_main_quit();
+ break;
+ }
+}
+
+static void btp_adapter_free(struct btp_adapter *adapter)
+{
+ l_free(adapter);
+}
+
+static void btp_device_free(struct btp_device *device)
+{
+ l_free(device);
+}
+
+static void proxy_added(struct l_dbus_proxy *proxy, void *user_data)
+{
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ l_info("Proxy added: %s (%s)", interface, path);
+
+ if (!strcmp(interface, "org.bluez.Adapter1")) {
+ struct btp_adapter *adapter;
+
+ adapter = l_new(struct btp_adapter, 1);
+ adapter->proxy = proxy;
+
+ l_queue_push_tail(adapters, adapter);
+ return;
+ }
+
+ if (!strcmp(interface, "org.bluez.Device1")) {
+ struct btp_device *device;
+
+ device = l_new(struct btp_device, 1);
+ device->proxy = proxy;
+
+ l_queue_push_tail(devices, device);
+ return;
+ }
+}
+
+static bool device_match_by_proxy(const void *a, const void *b)
+{
+ const struct btp_device *device = a;
+ const struct l_dbus_proxy *proxy = b;
+
+ return device->proxy == proxy;
+}
+
+static void proxy_removed(struct l_dbus_proxy *proxy, void *user_data)
+{
+ const char *interface = l_dbus_proxy_get_interface(proxy);
+ const char *path = l_dbus_proxy_get_path(proxy);
+
+ l_info("Proxy removed: %s (%s)", interface, path);
+
+ if (!strcmp(interface, "org.bluez.Adapter1")) {
+ l_info("Adapter removed, terminating.");
+ l_main_quit();
+ return;
+ }
+
+ if (!strcmp(interface, "org.bluez.Device1")) {
+ l_queue_remove_if(devices, device_match_by_proxy, proxy);
+
+ return;
+ }
+}
+
+static void property_changed(struct l_dbus_proxy *proxy, const char *name,
+ struct l_dbus_message *msg, void *user_data)
+{
+ l_info("property_changed %s %s %s", name, l_dbus_proxy_get_path(proxy),
+ l_dbus_proxy_get_interface(proxy));
+}
+
+static void client_connected(struct l_dbus *dbus, void *user_data)
+{
+ l_info("D-Bus client connected");
+}
+
+static void client_disconnected(struct l_dbus *dbus, void *user_data)
+{
+ l_info("D-Bus client disconnected, terminated");
+ l_main_quit();
+}
+
+static void client_ready(struct l_dbus_client *client, void *user_data)
+{
+ l_info("D-Bus client ready, connecting BTP");
+
+ btp = btp_new(socket_path);
+ if (!btp) {
+ l_error("Failed to connect BTP, terminating");
+ l_main_quit();
+ return;
+ }
+
+ register_core_service();
+
+ btp_send(btp, BTP_CORE_SERVICE, BTP_EV_CORE_READY,
+ BTP_INDEX_NON_CONTROLLER, 0, NULL);
+}
+
+static void usage(void)
+{
+ l_info("btpclient - Bluetooth tester");
+ l_info("Usage:");
+ l_info("\tbtpclient [options]");
+ l_info("options:\n"
+ "\t-s, --socket <socket> Socket to use for BTP\n"
+ "\t-q, --quiet Don't emit any logs\n"
+ "\t-v, --version Show version\n"
+ "\t-h, --help Show help options");
+}
+
+static const struct option options[] = {
+ { "socket", 1, 0, 's' },
+ { "quiet", 0, 0, 'q' },
+ { "version", 0, 0, 'v' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+ struct l_dbus_client *client;
+ struct l_signal *signal;
+ struct l_dbus *dbus;
+ sigset_t mask;
+ int opt;
+
+ l_log_set_stderr();
+
+ while ((opt = getopt_long(argc, argv, "+hs:vq", options, NULL)) != -1) {
+ switch (opt) {
+ case 's':
+ socket_path = l_strdup(optarg);
+ break;
+ case 'q':
+ l_log_set_null();
+ break;
+ case 'd':
+ break;
+ case 'v':
+ l_info("%s", VERSION);
+ return EXIT_SUCCESS;
+ case 'h':
+ default:
+ usage();
+ return EXIT_SUCCESS;
+ }
+ }
+
+ if (!socket_path) {
+ l_info("Socket option is required");
+ l_info("Type --help for usage");
+ return EXIT_FAILURE;
+ }
+
+ if (!l_main_init())
+ return EXIT_FAILURE;
+
+
+ adapters = l_queue_new();
+ devices = l_queue_new();
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ signal = l_signal_create(&mask, signal_handler, NULL, NULL);
+
+ dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS);
+ client = l_dbus_client_new(dbus, "org.bluez", "/org/bluez");
+
+ l_dbus_client_set_connect_handler(client, client_connected, NULL, NULL);
+ l_dbus_client_set_disconnect_handler(client, client_disconnected, NULL,
+ NULL);
+
+ l_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
+ property_changed, NULL, NULL);
+
+ l_dbus_client_set_ready_handler(client, client_ready, NULL, NULL);
+
+ l_main_run();
+
+ l_dbus_client_destroy(client);
+ l_dbus_destroy(dbus);
+ l_signal_remove(signal);
+ btp_cleanup(btp);
+
+ l_queue_destroy(devices, (l_queue_destroy_func_t)btp_device_free);
+ l_queue_destroy(adapters, (l_queue_destroy_func_t)btp_adapter_free);
+
+ l_free(socket_path);
+
+ l_main_exit();
+
+ return EXIT_SUCCESS;
+}
--
2.14.3
This allows to connect to BTP socket, process message one-by-one (next
message is processed only after reply to previous was sent), reply and
send notifications.
Currently it is very simple and push most of error checking to user.
Could be extended in future if needed.
---
src/shared/btp.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/btp.h | 96 ++++++++++++++
2 files changed, 481 insertions(+)
create mode 100644 src/shared/btp.c
create mode 100644 src/shared/btp.h
diff --git a/src/shared/btp.c b/src/shared/btp.c
new file mode 100644
index 000000000..5af473e95
--- /dev/null
+++ b/src/shared/btp.c
@@ -0,0 +1,385 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012-2014 Intel Corporation. All rights reserved.
+ * Copyright (C) 2017 Codecoup. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <ell/ell.h>
+
+#include "src/shared/btp.h"
+
+#define BTP_MTU 512
+
+struct btp_handler {
+ unsigned int id;
+ uint8_t service;
+ uint8_t opcode;
+
+ btp_cmd_func_t callback;
+ void *user_data;
+ btp_destroy_func_t destroy;
+};
+
+struct btp {
+ struct l_io *io;
+
+ struct l_queue *pending;
+ bool writer_active;
+ bool reader_active;
+
+ struct l_queue *handlers;
+ unsigned int next_handler;
+
+ uint8_t buf[BTP_MTU];
+
+ btp_disconnect_func_t disconnect_cb;
+ void *disconnect_cb_data;
+ btp_destroy_func_t disconnect_cb_data_destroy;
+};
+
+
+static struct l_io *btp_connect(const char *path)
+{
+ struct sockaddr_un addr;
+ struct l_io *io;
+ int sk;
+
+ sk = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (sk < 0)
+ return NULL;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
+
+ if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ close(sk);
+ return NULL;
+ }
+
+ io = l_io_new(sk);
+ if (!io) {
+ close(sk);
+ return NULL;
+ }
+
+ l_io_set_close_on_destroy(io, true);
+ return io;
+}
+
+static void disconnect_handler(struct l_io *io, void *user_data)
+{
+ struct btp *btp = user_data;
+
+ btp->disconnect_cb(btp, btp->disconnect_cb_data);
+}
+
+static void disconnect_handler_destroy(void *user_data)
+{
+ struct btp *btp = user_data;
+
+ if (btp->disconnect_cb_data_destroy)
+ btp->disconnect_cb_data_destroy(btp->disconnect_cb_data);
+}
+
+struct handler_match_data {
+ uint8_t service;
+ uint8_t opcode;
+};
+
+static bool handler_match(const void *a, const void *b)
+{
+ const struct btp_handler *handler = a;
+ const struct handler_match_data *match = b;
+
+ return (handler->service == match->service) &&
+ (handler->opcode == match->opcode);
+}
+
+static bool can_read_data(struct l_io *io, void *user_data)
+{
+ struct handler_match_data match;
+ struct btp *btp = user_data;
+ struct btp_handler *handler;
+ struct btp_hdr *hdr;
+ ssize_t bytes_read;
+ uint16_t data_len;
+
+ bytes_read = read(l_io_get_fd(btp->io), btp->buf, sizeof(btp->buf));
+ if (bytes_read < 0)
+ return false;
+
+ if ((size_t) bytes_read < sizeof(*hdr))
+ return false;
+
+ hdr = (void *)btp->buf;
+
+ data_len = L_LE16_TO_CPU(hdr->data_len);
+
+ if ((size_t) bytes_read < sizeof(*hdr) + data_len)
+ return false;
+
+ match.service = hdr->service;
+ match.opcode = hdr->opcode;
+
+ handler = l_queue_find(btp->handlers, handler_match, &match);
+ if (handler) {
+ handler->callback(hdr->index, hdr->data, data_len,
+ handler->user_data);
+ return false;
+ }
+
+ /* keep reader active if we sent error reply */
+ btp_send_error(btp, match.service, hdr->index, BTP_ERROR_UNKNOWN_CMD);
+ return true;
+}
+
+static void read_watch_destroy(void *user_data)
+{
+ struct btp *btp = user_data;
+
+ btp->reader_active = false;
+}
+
+static void wakeup_reader(struct btp *btp)
+{
+ if (btp->reader_active)
+ return;
+
+ btp->reader_active = l_io_set_read_handler(btp->io, can_read_data, btp,
+ read_watch_destroy);
+}
+
+struct btp *btp_new(const char *path)
+{
+ struct btp *btp;
+ struct l_io *io;
+
+ io = btp_connect(path);
+ if (!io)
+ return NULL;
+
+ btp = l_new(struct btp, 1);
+ btp->pending = l_queue_new();
+ btp->handlers = l_queue_new();
+ btp->io = io;
+ btp->next_handler = 1;
+
+ wakeup_reader(btp);
+
+ return btp;
+}
+
+struct pending_message {
+ size_t len;
+ void *data;
+ bool wakeup_read;
+};
+
+static void destroy_message(struct pending_message *msg)
+{
+ l_free(msg->data);
+ l_free(msg);
+}
+
+void btp_cleanup(struct btp *btp)
+{
+ if (!btp)
+ return;
+
+ l_io_destroy(btp->io);
+ l_queue_destroy(btp->pending, (l_queue_destroy_func_t)destroy_message);
+ l_queue_destroy(btp->handlers, (l_queue_destroy_func_t)l_free);
+ l_free(btp);
+}
+
+bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback,
+ void *user_data, btp_destroy_func_t destroy)
+{
+ if (callback) {
+ if (!l_io_set_disconnect_handler(btp->io, disconnect_handler,
+ btp, disconnect_handler_destroy))
+ return false;
+ } else {
+ if (!l_io_set_disconnect_handler(btp->io, NULL, NULL, NULL))
+ return false;
+ }
+
+ btp->disconnect_cb = callback;
+ btp->disconnect_cb_data = user_data;
+ btp->disconnect_cb_data_destroy = destroy;
+
+ return true;
+}
+
+static bool can_write_data(struct l_io *io, void *user_data)
+{
+ struct btp *btp = user_data;
+ struct pending_message *msg;
+
+ msg = l_queue_pop_head(btp->pending);
+ if (!msg)
+ return false;
+
+ if (msg->wakeup_read)
+ wakeup_reader(btp);
+
+ if (write(l_io_get_fd(btp->io), msg->data, msg->len) < 0) {
+ l_error("Failed to send BTP message");
+ destroy_message(msg);
+ return false;
+ }
+
+ return !l_queue_isempty(btp->pending);
+}
+
+static void write_watch_destroy(void *user_data)
+{
+ struct btp *btp = user_data;
+
+ btp->writer_active = false;
+}
+
+static void wakeup_writer(struct btp *btp)
+{
+ if (l_queue_isempty(btp->pending))
+ return;
+
+ if (btp->writer_active)
+ return;
+
+ btp->writer_active = l_io_set_write_handler(btp->io, can_write_data,
+ btp, write_watch_destroy);
+}
+
+bool btp_send_error(struct btp *btp, uint8_t service, uint16_t index,
+ uint8_t status)
+{
+ struct btp_error rsp;
+
+ rsp.status = status;
+
+ return btp_send(btp, service, BTP_OP_ERROR, index, sizeof(rsp), &rsp);
+}
+
+bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode, uint16_t index,
+ uint16_t length, const void *param)
+{
+ struct btp_hdr *hdr;
+ struct pending_message *msg;
+ size_t len;
+
+ if (index > 0xff)
+ return NULL;
+
+ len = sizeof(*hdr) + length;
+ hdr = l_malloc(len);
+ if (!hdr)
+ return NULL;
+
+ hdr->service = service;
+ hdr->opcode = opcode;
+ hdr->index = index;
+ hdr->data_len = L_CPU_TO_LE16(length);
+ if (length)
+ memcpy(hdr->data, param, length);
+
+ msg = l_new(struct pending_message, 1);
+ msg->len = len;
+ msg->data = hdr;
+ msg->wakeup_read = opcode < 0x80;
+
+ l_queue_push_tail(btp->pending, msg);
+ wakeup_writer(btp);
+
+ return true;
+}
+
+unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode,
+ btp_cmd_func_t callback, void *user_data,
+ btp_destroy_func_t destroy)
+{
+ struct btp_handler *handler;
+
+ handler = l_new(struct btp_handler, 1);
+
+ handler->id = btp->next_handler++;
+ handler->service = service;
+ handler->opcode = opcode;
+ handler->callback = callback;
+ handler->user_data = user_data;
+ handler->destroy = destroy;
+
+ l_queue_push_tail(btp->handlers, handler);
+
+ return handler->id;
+}
+
+static bool handler_match_by_id(const void *a, const void *b)
+{
+ const struct btp_handler *handler = a;
+ unsigned int id = L_PTR_TO_UINT(b);
+
+ return handler->id == id;
+}
+
+bool btp_unregister(struct btp *btp, unsigned int id)
+{
+ struct btp_handler *handler;
+
+ handler = l_queue_remove_if(btp->handlers, handler_match_by_id,
+ L_UINT_TO_PTR(id));
+ if (!handler)
+ return false;
+
+ if (handler->destroy)
+ handler->destroy(handler->user_data);
+
+ l_free(handler);
+
+ return true;
+}
+
+static bool handler_remove_by_service(void *a, void *b)
+{
+ struct btp_handler *handler = a;
+ uint8_t service = L_PTR_TO_UINT(b);
+
+ if (handler->service != service)
+ return false;
+
+ if (handler->destroy)
+ handler->destroy(handler->user_data);
+
+ l_free(handler);
+ return true;
+}
+
+void btp_unregister_service(struct btp *btp, uint8_t service)
+{
+ l_queue_foreach_remove(btp->handlers, handler_remove_by_service,
+ L_UINT_TO_PTR(service));
+}
diff --git a/src/shared/btp.h b/src/shared/btp.h
new file mode 100644
index 000000000..1bb3d6313
--- /dev/null
+++ b/src/shared/btp.h
@@ -0,0 +1,96 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012-2014 Intel Corporation. All rights reserved.
+ * Copyright (C) 2017 Codecoup. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdbool.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#define BTP_INDEX_NON_CONTROLLER 0xff
+
+#define BTP_ERROR_FAIL 0x01
+#define BTP_ERROR_UNKNOWN_CMD 0x02
+#define BTP_ERROR_NOT_READY 0x03
+#define BTP_ERROR_INVALID_INDEX 0x04
+
+#define BTP_CORE_SERVICE 0
+#define BTP_GAP_SERVICE 1
+#define BTP_GATT_SERVICE 2
+#define BTP_L2CAP_SERVICE 3
+#define BTP_MESH_NODE_SERVICE 4
+
+struct btp_hdr {
+ uint8_t service;
+ uint8_t opcode;
+ uint8_t index;
+ uint16_t data_len;
+ uint8_t data[0];
+} __packed;
+
+struct btp_error {
+ uint8_t status;
+} __packed;
+
+#define BTP_OP_ERROR 0x00
+
+#define BTP_OP_CORE_READ_SUPPORTED_COMMANDS 0x01
+
+#define BTP_OP_CORE_READ_SUPPORTED_SERVICES 0x02
+
+#define BTP_OP_CORE_REGISTER 0x03
+struct btp_core_register_cp {
+ uint8_t service_id;
+} __packed;
+
+#define BTP_OP_CORE_UNREGISTER 0x04
+struct btp_core_unregister_cp {
+ uint8_t service_id;
+} __packed;
+
+#define BTP_EV_CORE_READY 0x80
+
+struct btp;
+
+typedef void (*btp_destroy_func_t)(void *user_data);
+typedef void (*btp_disconnect_func_t)(struct btp *btp, void *user_data);
+typedef void (*btp_cmd_func_t)(uint16_t index, const void *param,
+ uint16_t length, void *user_data);
+
+struct btp *btp_new(const char *path);
+void btp_cleanup(struct btp *btp);
+
+bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback,
+ void *user_data, btp_destroy_func_t destroy);
+
+bool btp_send_error(struct btp *btp, uint8_t service, uint16_t index,
+ uint8_t status);
+bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode,
+ uint16_t index, uint16_t length, const void *param);
+
+unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode,
+ btp_cmd_func_t callback, void *user_data,
+ btp_destroy_func_t destroy);
+bool btp_unregister(struct btp *btp, unsigned int id);
+void btp_unregister_service(struct btp *btp, uint8_t service);
--
2.14.3