summaryrefslogtreecommitdiff
path: root/attrib
diff options
context:
space:
mode:
authorAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
committerAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
commit799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch)
treea8c3be85c730de28b012586591b76301033d3d21 /attrib
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'attrib')
-rw-r--r--attrib/att.c968
-rw-r--r--attrib/att.h306
-rw-r--r--attrib/client.c1116
-rw-r--r--attrib/client.h28
-rw-r--r--attrib/example.c341
-rw-r--r--attrib/example.h26
-rw-r--r--attrib/gatt.c577
-rw-r--r--attrib/gatt.h53
-rw-r--r--attrib/gattrib.c636
-rw-r--r--attrib/gattrib.h80
-rw-r--r--attrib/gatttool.c634
-rw-r--r--attrib/gatttool.h28
-rw-r--r--attrib/interactive.c842
-rw-r--r--attrib/main.c60
-rw-r--r--attrib/manager.c105
-rw-r--r--attrib/manager.h26
-rw-r--r--attrib/utils.c126
17 files changed, 5952 insertions, 0 deletions
diff --git a/attrib/att.c b/attrib/att.c
new file mode 100644
index 0000000..08000e0
--- /dev/null
+++ b/attrib/att.c
@@ -0,0 +1,968 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include "att.h"
+
+const char *att_ecode2str(uint8_t status)
+{
+ switch (status) {
+ case ATT_ECODE_INVALID_HANDLE:
+ return "Invalid handle";
+ case ATT_ECODE_READ_NOT_PERM:
+ return "Atribute can't be read";
+ case ATT_ECODE_WRITE_NOT_PERM:
+ return "Attribute can't be written";
+ case ATT_ECODE_INVALID_PDU:
+ return "Attribute PDU was invalid";
+ case ATT_ECODE_INSUFF_AUTHEN:
+ return "Attribute requires authentication before read/write";
+ case ATT_ECODE_REQ_NOT_SUPP:
+ return "Server doesn't support the request received";
+ case ATT_ECODE_INVALID_OFFSET:
+ return "Offset past the end of the attribute";
+ case ATT_ECODE_INSUFF_AUTHO:
+ return "Attribute requires authorization before read/write";
+ case ATT_ECODE_PREP_QUEUE_FULL:
+ return "Too many prepare writes have been queued";
+ case ATT_ECODE_ATTR_NOT_FOUND:
+ return "No attribute found within the given range";
+ case ATT_ECODE_ATTR_NOT_LONG:
+ return "Attribute can't be read/written using Read Blob Req";
+ case ATT_ECODE_INSUFF_ENCR_KEY_SIZE:
+ return "Encryption Key Size is insufficient";
+ case ATT_ECODE_INVAL_ATTR_VALUE_LEN:
+ return "Attribute value length is invalid";
+ case ATT_ECODE_UNLIKELY:
+ return "Request attribute has encountered an unlikely error";
+ case ATT_ECODE_INSUFF_ENC:
+ return "Encryption required before read/write";
+ case ATT_ECODE_UNSUPP_GRP_TYPE:
+ return "Attribute type is not a supported grouping attribute";
+ case ATT_ECODE_INSUFF_RESOURCES:
+ return "Insufficient Resources to complete the request";
+ case ATT_ECODE_IO:
+ return "Internal application error: I/O";
+ default:
+ return "Unexpected error code";
+ }
+}
+
+void att_data_list_free(struct att_data_list *list)
+{
+ if (list == NULL)
+ return;
+
+ if (list->data) {
+ int i;
+ for (i = 0; i < list->num; i++)
+ g_free(list->data[i]);
+ }
+
+ g_free(list->data);
+ g_free(list);
+}
+
+struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len)
+{
+ struct att_data_list *list;
+ int i;
+
+ list = g_new0(struct att_data_list, 1);
+ list->len = len;
+ list->num = num;
+
+ list->data = g_malloc0(sizeof(uint8_t *) * num);
+
+ for (i = 0; i < num; i++)
+ list->data[i] = g_malloc0(sizeof(uint8_t) * len);
+
+ return list;
+}
+
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+ uint16_t length;
+
+ if (!uuid)
+ return 0;
+
+ if (uuid->type == BT_UUID16)
+ length = 2;
+ else if (uuid->type == BT_UUID128)
+ length = 16;
+ else
+ return 0;
+
+ if (len < min_len + length)
+ return 0;
+
+ pdu[0] = ATT_OP_READ_BY_GROUP_REQ;
+ att_put_u16(start, &pdu[1]);
+ att_put_u16(end, &pdu[3]);
+
+ att_put_uuid(*uuid, &pdu[5]);
+
+ return min_len + length;
+}
+
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (start == NULL || end == NULL || uuid == NULL)
+ return 0;
+
+ if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ)
+ return 0;
+
+ if (len < min_len + 2)
+ return 0;
+
+ *start = att_get_u16(&pdu[1]);
+ *end = att_get_u16(&pdu[3]);
+ if (len == min_len + 2)
+ *uuid = att_get_uuid16(&pdu[5]);
+ else
+ *uuid = att_get_uuid128(&pdu[5]);
+
+ return len;
+}
+
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu,
+ int len)
+{
+ int i;
+ uint16_t w;
+ uint8_t *ptr;
+
+ if (list == NULL)
+ return 0;
+
+ if (len < list->len + 2)
+ return 0;
+
+ pdu[0] = ATT_OP_READ_BY_GROUP_RESP;
+ pdu[1] = list->len;
+
+ ptr = &pdu[2];
+
+ for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+ memcpy(ptr, list->data[i], list->len);
+ ptr += list->len;
+ w += list->len;
+ }
+
+ return w;
+}
+
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len)
+{
+ struct att_data_list *list;
+ const uint8_t *ptr;
+ uint16_t elen, num;
+ int i;
+
+ if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP)
+ return NULL;
+
+ elen = pdu[1];
+ num = (len - 2) / elen;
+ list = att_data_list_alloc(num, elen);
+
+ ptr = &pdu[2];
+
+ for (i = 0; i < num; i++) {
+ memcpy(list->data[i], ptr, list->len);
+ ptr += list->len;
+ }
+
+ return list;
+}
+
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ const uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+ uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) +
+ sizeof(uint16_t);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (!uuid)
+ return 0;
+
+ if (uuid->type != BT_UUID16)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (vlen > len - min_len)
+ vlen = len - min_len;
+
+ pdu[0] = ATT_OP_FIND_BY_TYPE_REQ;
+ att_put_u16(start, &pdu[1]);
+ att_put_u16(end, &pdu[3]);
+ att_put_uuid16(*uuid, &pdu[5]);
+
+ if (vlen > 0) {
+ memcpy(&pdu[7], value, vlen);
+ return min_len + vlen;
+ }
+
+ return min_len;
+}
+
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen)
+{
+ int valuelen;
+ uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) +
+ sizeof(*end) + sizeof(uint16_t);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ)
+ return 0;
+
+ /* First requested handle number */
+ if (start)
+ *start = att_get_u16(&pdu[1]);
+
+ /* Last requested handle number */
+ if (end)
+ *end = att_get_u16(&pdu[3]);
+
+ /* Always UUID16 */
+ if (uuid)
+ *uuid = att_get_uuid16(&pdu[5]);
+
+ valuelen = len - min_len;
+
+ /* Attribute value to find */
+ if (valuelen > 0 && value)
+ memcpy(value, pdu + min_len, valuelen);
+
+ if (vlen)
+ *vlen = valuelen;
+
+ return len;
+}
+
+uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, int len)
+{
+ GSList *l;
+ uint16_t offset;
+
+ if (pdu == NULL || len < 5)
+ return 0;
+
+ pdu[0] = ATT_OP_FIND_BY_TYPE_RESP;
+
+ for (l = matches, offset = 1; l && len >= (offset + 4);
+ l = l->next, offset += 4) {
+ struct att_range *range = l->data;
+
+ att_put_u16(range->start, &pdu[offset]);
+ att_put_u16(range->end, &pdu[offset + 2]);
+ }
+
+ return offset;
+}
+
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len)
+{
+ struct att_range *range;
+ GSList *matches;
+ int offset;
+
+ if (pdu == NULL || len < 5)
+ return NULL;
+
+ if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP)
+ return NULL;
+
+ for (offset = 1, matches = NULL; len >= (offset + 4); offset += 4) {
+ range = g_new0(struct att_range, 1);
+ range->start = att_get_u16(&pdu[offset]);
+ range->end = att_get_u16(&pdu[offset + 2]);
+
+ matches = g_slist_append(matches, range);
+ }
+
+ return matches;
+}
+
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+ uint16_t length;
+
+ if (!uuid)
+ return 0;
+
+ if (uuid->type == BT_UUID16)
+ length = 2;
+ else if (uuid->type == BT_UUID128)
+ length = 16;
+ else
+ return 0;
+
+ if (len < min_len + length)
+ return 0;
+
+ pdu[0] = ATT_OP_READ_BY_TYPE_REQ;
+ att_put_u16(start, &pdu[1]);
+ att_put_u16(end, &pdu[3]);
+
+ att_put_uuid(*uuid, &pdu[5]);
+
+ return min_len + length;
+}
+
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (start == NULL || end == NULL || uuid == NULL)
+ return 0;
+
+ if (len < min_len + 2)
+ return 0;
+
+ if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ)
+ return 0;
+
+ *start = att_get_u16(&pdu[1]);
+ *end = att_get_u16(&pdu[3]);
+
+ if (len == min_len + 2)
+ *uuid = att_get_uuid16(&pdu[5]);
+ else
+ *uuid = att_get_uuid128(&pdu[5]);
+
+ return len;
+}
+
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, int len)
+{
+ uint8_t *ptr;
+ int i, w, l;
+
+ if (list == NULL)
+ return 0;
+
+ if (pdu == NULL)
+ return 0;
+
+ l = MIN(len - 2, list->len);
+
+ pdu[0] = ATT_OP_READ_BY_TYPE_RESP;
+ pdu[1] = l;
+ ptr = &pdu[2];
+
+ for (i = 0, w = 2; i < list->num && w + l <= len; i++) {
+ memcpy(ptr, list->data[i], l);
+ ptr += l;
+ w += l;
+ }
+
+ return w;
+}
+
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len)
+{
+ struct att_data_list *list;
+ const uint8_t *ptr;
+ uint16_t elen, num;
+ int i;
+
+ if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP)
+ return NULL;
+
+ elen = pdu[1];
+ num = (len - 2) / elen;
+ list = att_data_list_alloc(num, elen);
+
+ ptr = &pdu[2];
+
+ for (i = 0; i < num; i++) {
+ memcpy(list->data[i], ptr, list->len);
+ ptr += list->len;
+ }
+
+ return list;
+}
+
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+ uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (vlen > len - min_len)
+ vlen = len - min_len;
+
+ pdu[0] = ATT_OP_WRITE_CMD;
+ att_put_u16(handle, &pdu[1]);
+
+ if (vlen > 0) {
+ memcpy(&pdu[3], value, vlen);
+ return min_len + vlen;
+ }
+
+ return min_len;
+}
+
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+ uint8_t *value, int *vlen)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (value == NULL || vlen == NULL || handle == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_WRITE_CMD)
+ return 0;
+
+ *handle = att_get_u16(&pdu[1]);
+ memcpy(value, pdu + min_len, len - min_len);
+ *vlen = len - min_len;
+
+ return len;
+}
+
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+ uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (vlen > len - min_len)
+ vlen = len - min_len;
+
+ pdu[0] = ATT_OP_WRITE_REQ;
+ att_put_u16(handle, &pdu[1]);
+
+ if (vlen > 0) {
+ memcpy(&pdu[3], value, vlen);
+ return min_len + vlen;
+ }
+
+ return min_len;
+}
+
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+ uint8_t *value, int *vlen)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (value == NULL || vlen == NULL || handle == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_WRITE_REQ)
+ return 0;
+
+ *handle = att_get_u16(&pdu[1]);
+ *vlen = len - min_len;
+ if (*vlen > 0)
+ memcpy(value, pdu + min_len, *vlen);
+
+ return len;
+}
+
+uint16_t enc_write_resp(uint8_t *pdu, int len)
+{
+ if (pdu == NULL)
+ return 0;
+
+ pdu[0] = ATT_OP_WRITE_RESP;
+
+ return sizeof(pdu[0]);
+}
+
+uint16_t dec_write_resp(const uint8_t *pdu, int len)
+{
+ if (pdu == NULL)
+ return 0;
+
+ if (pdu[0] != ATT_OP_WRITE_RESP)
+ return 0;
+
+ return len;
+}
+
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_READ_REQ;
+ att_put_u16(handle, &pdu[1]);
+
+ return min_len;
+}
+
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+ int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) +
+ sizeof(offset);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_READ_BLOB_REQ;
+ att_put_u16(handle, &pdu[1]);
+ att_put_u16(offset, &pdu[3]);
+
+ return min_len;
+}
+
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (handle == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_READ_REQ)
+ return 0;
+
+ *handle = att_get_u16(&pdu[1]);
+
+ return min_len;
+}
+
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+ uint16_t *offset)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) +
+ sizeof(*offset);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (handle == NULL)
+ return 0;
+
+ if (offset == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_READ_BLOB_REQ)
+ return 0;
+
+ *handle = att_get_u16(&pdu[1]);
+ *offset = att_get_u16(&pdu[3]);
+
+ return min_len;
+}
+
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len)
+{
+ if (pdu == NULL)
+ return 0;
+
+ /* If the attribute value length is longer than the allowed PDU size,
+ * send only the octets that fit on the PDU. The remaining octets can
+ * be requested using the Read Blob Request. */
+ if (vlen > len - 1)
+ vlen = len - 1;
+
+ pdu[0] = ATT_OP_READ_RESP;
+
+ memcpy(pdu + 1, value, vlen);
+
+ return vlen + 1;
+}
+
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+ uint8_t *pdu, int len)
+{
+ if (pdu == NULL)
+ return 0;
+
+ vlen -= offset;
+ if (vlen > len - 1)
+ vlen = len - 1;
+
+ pdu[0] = ATT_OP_READ_BLOB_RESP;
+
+ memcpy(pdu + 1, &value[offset], vlen);
+
+ return vlen + 1;
+}
+
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen)
+{
+ if (pdu == NULL)
+ return 0;
+
+ if (value == NULL || vlen == NULL)
+ return 0;
+
+ if (pdu[0] != ATT_OP_READ_RESP)
+ return 0;
+
+ memcpy(value, pdu + 1, len - 1);
+
+ *vlen = len - 1;
+
+ return len;
+}
+
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+ uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(opcode) +
+ sizeof(handle) + sizeof(status);
+ uint16_t u16;
+
+ if (len < min_len)
+ return 0;
+
+ u16 = htobs(handle);
+ pdu[0] = ATT_OP_ERROR;
+ pdu[1] = opcode;
+ memcpy(&pdu[2], &u16, sizeof(u16));
+ pdu[4] = status;
+
+ return min_len;
+}
+
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_FIND_INFO_REQ;
+ att_put_u16(start, &pdu[1]);
+ att_put_u16(end, &pdu[3]);
+
+ return min_len;
+}
+
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (start == NULL || end == NULL)
+ return 0;
+
+ if (pdu[0] != ATT_OP_FIND_INFO_REQ)
+ return 0;
+
+ *start = att_get_u16(&pdu[1]);
+ *end = att_get_u16(&pdu[3]);
+
+ return min_len;
+}
+
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+ uint8_t *pdu, int len)
+{
+ uint8_t *ptr;
+ int i, w;
+
+ if (pdu == NULL)
+ return 0;
+
+ if (list == NULL)
+ return 0;
+
+ if (len < list->len + 2)
+ return 0;
+
+ pdu[0] = ATT_OP_FIND_INFO_RESP;
+ pdu[1] = format;
+ ptr = (void *) &pdu[2];
+
+ for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) {
+ memcpy(ptr, list->data[i], list->len);
+ ptr += list->len;
+ w += list->len;
+ }
+
+ return w;
+}
+
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+ uint8_t *format)
+{
+ struct att_data_list *list;
+ uint8_t *ptr;
+ uint16_t elen, num;
+ int i;
+
+ if (pdu == NULL)
+ return 0;
+
+ if (format == NULL)
+ return 0;
+
+ if (pdu[0] != ATT_OP_FIND_INFO_RESP)
+ return 0;
+
+ *format = pdu[1];
+ elen = sizeof(pdu[0]) + sizeof(*format);
+ if (*format == 0x01)
+ elen += 2;
+ else if (*format == 0x02)
+ elen += 16;
+
+ num = (len - 2) / elen;
+
+ ptr = (void *) &pdu[2];
+
+ list = att_data_list_alloc(num, elen);
+
+ for (i = 0; i < num; i++) {
+ memcpy(list->data[i], ptr, list->len);
+ ptr += list->len;
+ }
+
+ return list;
+}
+
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < (a->len + min_len))
+ return 0;
+
+ pdu[0] = ATT_OP_HANDLE_NOTIFY;
+ att_put_u16(a->handle, &pdu[1]);
+ memcpy(&pdu[3], a->data, a->len);
+
+ return a->len + min_len;
+}
+
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < (a->len + min_len))
+ return 0;
+
+ pdu[0] = ATT_OP_HANDLE_IND;
+ att_put_u16(a->handle, &pdu[1]);
+ memcpy(&pdu[3], a->data, a->len);
+
+ return a->len + min_len;
+}
+
+struct attribute *dec_indication(const uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
+
+ struct attribute *a;
+
+ if (pdu == NULL)
+ return NULL;
+
+ if (pdu[0] != ATT_OP_HANDLE_IND)
+ return NULL;
+
+ if (len < min_len)
+ return NULL;
+
+ a = g_malloc0(sizeof(struct attribute) + len - min_len);
+ a->len = len - min_len;
+
+ a->handle = att_get_u16(&pdu[1]);
+ memcpy(a->data, &pdu[3], a->len);
+
+ return a;
+}
+
+uint16_t enc_confirmation(uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_HANDLE_CNF;
+
+ return min_len;
+}
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_MTU_REQ;
+ att_put_u16(mtu, &pdu[1]);
+
+ return min_len;
+}
+
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (mtu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_MTU_REQ)
+ return 0;
+
+ *mtu = att_get_u16(&pdu[1]);
+
+ return min_len;
+}
+
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(mtu);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ pdu[0] = ATT_OP_MTU_RESP;
+ att_put_u16(mtu, &pdu[1]);
+
+ return min_len;
+}
+
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu)
+{
+ const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu);
+
+ if (pdu == NULL)
+ return 0;
+
+ if (mtu == NULL)
+ return 0;
+
+ if (len < min_len)
+ return 0;
+
+ if (pdu[0] != ATT_OP_MTU_RESP)
+ return 0;
+
+ *mtu = att_get_u16(&pdu[1]);
+
+ return min_len;
+}
diff --git a/attrib/att.h b/attrib/att.h
new file mode 100644
index 0000000..7a83bfa
--- /dev/null
+++ b/attrib/att.h
@@ -0,0 +1,306 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+/* GATT Profile Attribute types */
+#define GATT_PRIM_SVC_UUID 0x2800
+#define GATT_SND_SVC_UUID 0x2801
+#define GATT_INCLUDE_UUID 0x2802
+#define GATT_CHARAC_UUID 0x2803
+
+/* GATT Characteristic Types */
+#define GATT_CHARAC_DEVICE_NAME 0x2A00
+#define GATT_CHARAC_APPEARANCE 0x2A01
+#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG 0x2A02
+#define GATT_CHARAC_RECONNECTION_ADDRESS 0x2A03
+#define GATT_CHARAC_PERIPHERAL_PREF_CONN 0x2A04
+#define GATT_CHARAC_SERVICE_CHANGED 0x2A05
+
+/* GATT Characteristic Descriptors */
+#define GATT_CHARAC_EXT_PROPER_UUID 0x2900
+#define GATT_CHARAC_USER_DESC_UUID 0x2901
+#define GATT_CLIENT_CHARAC_CFG_UUID 0x2902
+#define GATT_SERVER_CHARAC_CFG_UUID 0x2903
+#define GATT_CHARAC_FMT_UUID 0x2904
+#define GATT_CHARAC_AGREG_FMT_UUID 0x2905
+
+/* Attribute Protocol Opcodes */
+#define ATT_OP_ERROR 0x01
+#define ATT_OP_MTU_REQ 0x02
+#define ATT_OP_MTU_RESP 0x03
+#define ATT_OP_FIND_INFO_REQ 0x04
+#define ATT_OP_FIND_INFO_RESP 0x05
+#define ATT_OP_FIND_BY_TYPE_REQ 0x06
+#define ATT_OP_FIND_BY_TYPE_RESP 0x07
+#define ATT_OP_READ_BY_TYPE_REQ 0x08
+#define ATT_OP_READ_BY_TYPE_RESP 0x09
+#define ATT_OP_READ_REQ 0x0A
+#define ATT_OP_READ_RESP 0x0B
+#define ATT_OP_READ_BLOB_REQ 0x0C
+#define ATT_OP_READ_BLOB_RESP 0x0D
+#define ATT_OP_READ_MULTI_REQ 0x0E
+#define ATT_OP_READ_MULTI_RESP 0x0F
+#define ATT_OP_READ_BY_GROUP_REQ 0x10
+#define ATT_OP_READ_BY_GROUP_RESP 0x11
+#define ATT_OP_WRITE_REQ 0x12
+#define ATT_OP_WRITE_RESP 0x13
+#define ATT_OP_WRITE_CMD 0x52
+#define ATT_OP_PREP_WRITE_REQ 0x16
+#define ATT_OP_PREP_WRITE_RESP 0x17
+#define ATT_OP_EXEC_WRITE_REQ 0x18
+#define ATT_OP_EXEC_WRITE_RESP 0x19
+#define ATT_OP_HANDLE_NOTIFY 0x1B
+#define ATT_OP_HANDLE_IND 0x1D
+#define ATT_OP_HANDLE_CNF 0x1E
+#define ATT_OP_SIGNED_WRITE_CMD 0xD2
+
+/* Error codes for Error response PDU */
+#define ATT_ECODE_INVALID_HANDLE 0x01
+#define ATT_ECODE_READ_NOT_PERM 0x02
+#define ATT_ECODE_WRITE_NOT_PERM 0x03
+#define ATT_ECODE_INVALID_PDU 0x04
+#define ATT_ECODE_INSUFF_AUTHEN 0x05
+#define ATT_ECODE_REQ_NOT_SUPP 0x06
+#define ATT_ECODE_INVALID_OFFSET 0x07
+#define ATT_ECODE_INSUFF_AUTHO 0x08
+#define ATT_ECODE_PREP_QUEUE_FULL 0x09
+#define ATT_ECODE_ATTR_NOT_FOUND 0x0A
+#define ATT_ECODE_ATTR_NOT_LONG 0x0B
+#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE 0x0C
+#define ATT_ECODE_INVAL_ATTR_VALUE_LEN 0x0D
+#define ATT_ECODE_UNLIKELY 0x0E
+#define ATT_ECODE_INSUFF_ENC 0x0F
+#define ATT_ECODE_UNSUPP_GRP_TYPE 0x10
+#define ATT_ECODE_INSUFF_RESOURCES 0x11
+/* Application error */
+#define ATT_ECODE_IO 0xFF
+
+/* Characteristic Property bit field */
+#define ATT_CHAR_PROPER_BROADCAST 0x01
+#define ATT_CHAR_PROPER_READ 0x02
+#define ATT_CHAR_PROPER_WRITE_WITHOUT_RESP 0x04
+#define ATT_CHAR_PROPER_WRITE 0x08
+#define ATT_CHAR_PROPER_NOTIFY 0x10
+#define ATT_CHAR_PROPER_INDICATE 0x20
+#define ATT_CHAR_PROPER_AUTH 0x40
+#define ATT_CHAR_PROPER_EXT_PROPER 0x80
+
+
+#define ATT_MAX_MTU 256
+#define ATT_DEFAULT_L2CAP_MTU 48
+#define ATT_DEFAULT_LE_MTU 23
+
+/* Requirements for read/write operations */
+enum {
+ ATT_NONE, /* No restrictions */
+ ATT_AUTHENTICATION, /* Authentication required */
+ ATT_AUTHORIZATION, /* Authorization required */
+ ATT_NOT_PERMITTED, /* Operation not permitted */
+};
+
+struct attribute {
+ uint16_t handle;
+ bt_uuid_t uuid;
+ int read_reqs;
+ int write_reqs;
+ uint8_t (*read_cb)(struct attribute *a, gpointer user_data);
+ uint8_t (*write_cb)(struct attribute *a, gpointer user_data);
+ gpointer cb_user_data;
+ int len;
+ uint8_t data[0];
+};
+
+struct att_data_list {
+ uint16_t num;
+ uint16_t len;
+ uint8_t **data;
+};
+
+struct att_range {
+ uint16_t start;
+ uint16_t end;
+};
+
+struct att_primary {
+ char uuid[MAX_LEN_UUID_STR + 1];
+ uint16_t start;
+ uint16_t end;
+};
+
+struct att_char {
+ char uuid[MAX_LEN_UUID_STR + 1];
+ uint16_t handle;
+ uint8_t properties;
+ uint16_t value_handle;
+};
+
+/* These functions do byte conversion */
+static inline uint8_t att_get_u8(const void *ptr)
+{
+ const uint8_t *u8_ptr = ptr;
+ return bt_get_unaligned(u8_ptr);
+}
+
+static inline uint16_t att_get_u16(const void *ptr)
+{
+ const uint16_t *u16_ptr = ptr;
+ return btohs(bt_get_unaligned(u16_ptr));
+}
+
+static inline uint32_t att_get_u32(const void *ptr)
+{
+ const uint32_t *u32_ptr = ptr;
+ return btohl(bt_get_unaligned(u32_ptr));
+}
+
+static inline uint128_t att_get_u128(const void *ptr)
+{
+ const uint128_t *u128_ptr = ptr;
+ uint128_t dst;
+
+ btoh128(u128_ptr, &dst);
+
+ return dst;
+}
+
+static inline void att_put_u8(uint8_t src, void *dst)
+{
+ bt_put_unaligned(src, (uint8_t *) dst);
+}
+
+static inline void att_put_u16(uint16_t src, void *dst)
+{
+ bt_put_unaligned(htobs(src), (uint16_t *) dst);
+}
+
+static inline void att_put_u32(uint32_t src, void *dst)
+{
+ bt_put_unaligned(htobl(src), (uint32_t *) dst);
+}
+
+static inline void att_put_u128(uint128_t src, void *dst)
+{
+ uint128_t *d128 = dst;
+
+ htob128(&src, d128);
+}
+
+static inline void att_put_uuid16(bt_uuid_t src, void *dst)
+{
+ att_put_u16(src.value.u16, dst);
+}
+
+static inline void att_put_uuid128(bt_uuid_t src, void *dst)
+{
+ att_put_u128(src.value.u128, dst);
+}
+
+static inline void att_put_uuid(bt_uuid_t src, void *dst)
+{
+ if (src.type == BT_UUID16)
+ att_put_uuid16(src, dst);
+ else
+ att_put_uuid128(src, dst);
+}
+
+static inline bt_uuid_t att_get_uuid16(const void *ptr)
+{
+ bt_uuid_t uuid;
+
+ bt_uuid16_create(&uuid, att_get_u16(ptr));
+
+ return uuid;
+}
+
+static inline bt_uuid_t att_get_uuid128(const void *ptr)
+{
+ bt_uuid_t uuid;
+ uint128_t value;
+
+ value = att_get_u128(ptr);
+ bt_uuid128_create(&uuid, value);
+
+ return uuid;
+}
+
+struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len);
+void att_data_list_free(struct att_data_list *list);
+
+const char *att_ecode2str(uint8_t status);
+uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ uint8_t *pdu, int len);
+uint16_t dec_read_by_grp_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid);
+uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, int len);
+uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ const uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t dec_find_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid, uint8_t *value, int *vlen);
+uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, int len);
+GSList *dec_find_by_type_resp(const uint8_t *pdu, int len);
+struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid,
+ uint8_t *pdu, int len);
+uint16_t dec_read_by_type_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end, bt_uuid_t *uuid);
+uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu,
+ int len);
+uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, int vlen,
+ uint8_t *pdu, int len);
+uint16_t dec_write_cmd(const uint8_t *pdu, int len, uint16_t *handle,
+ uint8_t *value, int *vlen);
+struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, int len);
+uint16_t enc_write_req(uint16_t handle, const uint8_t *value, int vlen,
+ uint8_t *pdu, int len);
+uint16_t dec_write_req(const uint8_t *pdu, int len, uint16_t *handle,
+ uint8_t *value, int *vlen);
+uint16_t enc_write_resp(uint8_t *pdu, int len);
+uint16_t dec_write_resp(const uint8_t *pdu, int len);
+uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, int len);
+uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu,
+ int len);
+uint16_t dec_read_req(const uint8_t *pdu, int len, uint16_t *handle);
+uint16_t dec_read_blob_req(const uint8_t *pdu, int len, uint16_t *handle,
+ uint16_t *offset);
+uint16_t enc_read_resp(uint8_t *value, int vlen, uint8_t *pdu, int len);
+uint16_t enc_read_blob_resp(uint8_t *value, int vlen, uint16_t offset,
+ uint8_t *pdu, int len);
+uint16_t dec_read_resp(const uint8_t *pdu, int len, uint8_t *value, int *vlen);
+uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status,
+ uint8_t *pdu, int len);
+uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, int len);
+uint16_t dec_find_info_req(const uint8_t *pdu, int len, uint16_t *start,
+ uint16_t *end);
+uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list,
+ uint8_t *pdu, int len);
+struct att_data_list *dec_find_info_resp(const uint8_t *pdu, int len,
+ uint8_t *format);
+uint16_t enc_notification(struct attribute *a, uint8_t *pdu, int len);
+uint16_t enc_indication(struct attribute *a, uint8_t *pdu, int len);
+struct attribute *dec_indication(const uint8_t *pdu, int len);
+uint16_t enc_confirmation(uint8_t *pdu, int len);
+
+uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_req(const uint8_t *pdu, int len, uint16_t *mtu);
+uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, int len);
+uint16_t dec_mtu_resp(const uint8_t *pdu, int len, uint16_t *mtu);
diff --git a/attrib/client.c b/attrib/client.c
new file mode 100644
index 0000000..54bdc79
--- /dev/null
+++ b/attrib/client.c
@@ -0,0 +1,1116 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "gdbus.h"
+#include "error.h"
+#include "dbus-common.h"
+#include "btio.h"
+#include "storage.h"
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "client.h"
+
+#define CHAR_INTERFACE "org.bluez.Characteristic"
+
+struct gatt_service {
+ struct btd_device *dev;
+ bdaddr_t sba;
+ bdaddr_t dba;
+ char *path;
+ GSList *primary;
+ GAttrib *attrib;
+ DBusMessage *msg;
+ int psm;
+ gboolean listen;
+};
+
+struct format {
+ guint8 format;
+ guint8 exponent;
+ guint16 unit;
+ guint8 namespace;
+ guint16 desc;
+} __attribute__ ((packed));
+
+struct primary {
+ struct gatt_service *gatt;
+ struct att_primary *att;
+ char *path;
+ GSList *chars;
+ GSList *watchers;
+};
+
+struct characteristic {
+ struct primary *prim;
+ char *path;
+ uint16_t handle;
+ uint16_t end;
+ uint8_t perm;
+ char type[MAX_LEN_UUID_STR + 1];
+ char *name;
+ char *desc;
+ struct format *format;
+ uint8_t *value;
+ size_t vlen;
+};
+
+struct query_data {
+ struct primary *prim;
+ struct characteristic *chr;
+ DBusMessage *msg;
+ uint16_t handle;
+};
+
+struct watcher {
+ guint id;
+ char *name;
+ char *path;
+ struct primary *prim;
+};
+
+static GSList *gatt_services = NULL;
+
+static DBusConnection *connection;
+
+static void characteristic_free(void *user_data)
+{
+ struct characteristic *chr = user_data;
+
+ g_free(chr->path);
+ g_free(chr->desc);
+ g_free(chr->format);
+ g_free(chr->value);
+ g_free(chr->name);
+ g_free(chr);
+}
+
+static void watcher_free(void *user_data)
+{
+ struct watcher *watcher = user_data;
+
+ g_free(watcher->path);
+ g_free(watcher->name);
+ g_free(watcher);
+}
+
+static void primary_free(void *user_data)
+{
+ struct primary *prim = user_data;
+ GSList *l;
+
+ for (l = prim->watchers; l; l = l->next) {
+ struct watcher *watcher = l->data;
+ g_dbus_remove_watch(connection, watcher->id);
+ }
+
+ g_slist_foreach(prim->chars, (GFunc) characteristic_free, NULL);
+ g_slist_free(prim->chars);
+ g_free(prim->path);
+ g_free(prim);
+}
+
+static void gatt_service_free(void *user_data)
+{
+ struct gatt_service *gatt = user_data;
+
+ g_slist_foreach(gatt->primary, (GFunc) primary_free, NULL);
+ g_slist_free(gatt->primary);
+ g_attrib_unref(gatt->attrib);
+ g_free(gatt->path);
+ btd_device_unref(gatt->dev);
+ g_free(gatt);
+}
+
+static int gatt_dev_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct gatt_service *gatt = a;
+ const struct btd_device *dev = b;
+
+ return gatt->dev != dev;
+}
+
+static int characteristic_handle_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct characteristic *chr = a;
+ uint16_t handle = GPOINTER_TO_UINT(b);
+
+ return chr->handle - handle;
+}
+
+static int watcher_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct watcher *watcher = a;
+ const struct watcher *match = b;
+ int ret;
+
+ ret = g_strcmp0(watcher->name, match->name);
+ if (ret != 0)
+ return ret;
+
+ return g_strcmp0(watcher->path, match->path);
+}
+
+static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr)
+{
+ DBusMessageIter dict;
+ const char *name = "";
+ char *uuid;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ uuid = g_strdup(chr->type);
+ dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+ g_free(uuid);
+
+ /* FIXME: Translate UUID to name. */
+ dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &name);
+
+ if (chr->desc)
+ dict_append_entry(&dict, "Description", DBUS_TYPE_STRING,
+ &chr->desc);
+
+ if (chr->value)
+ dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value,
+ chr->vlen);
+
+ /* FIXME: Missing Format, Value and Representation */
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+static void watcher_exit(DBusConnection *conn, void *user_data)
+{
+ struct watcher *watcher = user_data;
+ struct primary *prim = watcher->prim;
+ struct gatt_service *gatt = prim->gatt;
+
+ DBG("%s watcher %s exited", prim->path, watcher->name);
+
+ prim->watchers = g_slist_remove(prim->watchers, watcher);
+
+ g_attrib_unref(gatt->attrib);
+}
+
+static int characteristic_set_value(struct characteristic *chr,
+ const uint8_t *value, size_t vlen)
+{
+ chr->value = g_try_realloc(chr->value, vlen);
+ if (chr->value == NULL)
+ return -ENOMEM;
+
+ memcpy(chr->value, value, vlen);
+ chr->vlen = vlen;
+
+ return 0;
+}
+
+static void update_watchers(gpointer data, gpointer user_data)
+{
+ struct watcher *w = data;
+ struct characteristic *chr = user_data;
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call(w->name, w->path,
+ "org.bluez.Watcher", "ValueChanged");
+ if (msg == NULL)
+ return;
+
+ dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &chr->path,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &chr->value, chr->vlen, DBUS_TYPE_INVALID);
+
+ dbus_message_set_no_reply(msg, TRUE);
+ g_dbus_send_message(connection, msg);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ struct gatt_service *gatt = user_data;
+ struct characteristic *chr;
+ struct primary *prim;
+ GSList *lprim, *lchr;
+ uint8_t opdu[ATT_MAX_MTU];
+ guint handle;
+ uint16_t olen;
+
+ if (len < 3) {
+ DBG("Malformed notification/indication packet (opcode 0x%02x)",
+ pdu[0]);
+ return;
+ }
+
+ handle = att_get_u16(&pdu[1]);
+
+ for (lprim = gatt->primary, prim = NULL, chr = NULL; lprim;
+ lprim = lprim->next) {
+ prim = lprim->data;
+
+ lchr = g_slist_find_custom(prim->chars,
+ GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+ if (lchr) {
+ chr = lchr->data;
+ break;
+ }
+ }
+
+ if (chr == NULL) {
+ DBG("Attribute handle 0x%02x not found", handle);
+ return;
+ }
+
+ switch (pdu[0]) {
+ case ATT_OP_HANDLE_IND:
+ olen = enc_confirmation(opdu, sizeof(opdu));
+ g_attrib_send(gatt->attrib, 0, opdu[0], opdu, olen,
+ NULL, NULL, NULL);
+ case ATT_OP_HANDLE_NOTIFY:
+ if (characteristic_set_value(chr, &pdu[3], len - 3) < 0)
+ DBG("Can't change Characteristic 0x%02x", handle);
+
+ g_slist_foreach(prim->watchers, update_watchers, chr);
+ break;
+ }
+}
+
+static void attrib_destroy(gpointer user_data)
+{
+ struct gatt_service *gatt = user_data;
+
+ gatt->attrib = NULL;
+}
+
+static void attrib_disconnect(gpointer user_data)
+{
+ struct gatt_service *gatt = user_data;
+
+ /* Remote initiated disconnection only */
+ g_attrib_unref(gatt->attrib);
+}
+
+static void connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data)
+{
+ struct gatt_service *gatt = user_data;
+
+ if (gerr) {
+ if (gatt->msg) {
+ DBusMessage *reply = btd_error_failed(gatt->msg,
+ gerr->message);
+ g_dbus_send_message(connection, reply);
+ }
+
+ error("%s", gerr->message);
+ goto fail;
+ }
+
+ if (gatt->attrib == NULL)
+ return;
+
+ /* Listen mode: used for notification and indication */
+ if (gatt->listen == TRUE) {
+ g_attrib_register(gatt->attrib,
+ ATT_OP_HANDLE_NOTIFY,
+ events_handler, gatt, NULL);
+ g_attrib_register(gatt->attrib,
+ ATT_OP_HANDLE_IND,
+ events_handler, gatt, NULL);
+ return;
+ }
+
+ return;
+fail:
+ g_attrib_unref(gatt->attrib);
+}
+
+static int l2cap_connect(struct gatt_service *gatt, GError **gerr,
+ gboolean listen)
+{
+ GIOChannel *io;
+
+ if (gatt->attrib != NULL) {
+ gatt->attrib = g_attrib_ref(gatt->attrib);
+ gatt->listen = listen;
+ return 0;
+ }
+
+ /*
+ * FIXME: If the service doesn't support Client Characteristic
+ * Configuration it is necessary to poll the server from time
+ * to time checking for modifications.
+ */
+ if (gatt->psm < 0)
+ io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+ BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+ BT_IO_OPT_CID, GATT_CID,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ else
+ io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &gatt->sba,
+ BT_IO_OPT_DEST_BDADDR, &gatt->dba,
+ BT_IO_OPT_PSM, gatt->psm,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ if (!io)
+ return -1;
+
+ gatt->attrib = g_attrib_new(io);
+ g_io_channel_unref(io);
+ gatt->listen = listen;
+
+ g_attrib_set_destroy_function(gatt->attrib, attrib_destroy, gatt);
+ g_attrib_set_disconnect_function(gatt->attrib, attrib_disconnect,
+ gatt);
+
+ return 0;
+}
+
+static DBusMessage *register_watcher(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *sender = dbus_message_get_sender(msg);
+ struct primary *prim = data;
+ struct watcher *watcher;
+ GError *gerr = NULL;
+ char *path;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ if (l2cap_connect(prim->gatt, &gerr, TRUE) < 0) {
+ DBusMessage *reply = btd_error_failed(msg, gerr->message);
+ g_error_free(gerr);
+ return reply;
+ }
+
+ watcher = g_new0(struct watcher, 1);
+ watcher->name = g_strdup(sender);
+ watcher->prim = prim;
+ watcher->path = g_strdup(path);
+ watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit,
+ watcher, watcher_free);
+
+ prim->watchers = g_slist_append(prim->watchers, watcher);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_watcher(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *sender = dbus_message_get_sender(msg);
+ struct primary *prim = data;
+ struct watcher *watcher, *match;
+ GSList *l;
+ char *path;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ match = g_new0(struct watcher, 1);
+ match->name = g_strdup(sender);
+ match->path = g_strdup(path);
+ l = g_slist_find_custom(prim->watchers, match, watcher_cmp);
+ watcher_free(match);
+ if (!l)
+ return btd_error_not_authorized(msg);
+
+ watcher = l->data;
+ g_dbus_remove_watch(conn, watcher->id);
+ prim->watchers = g_slist_remove(prim->watchers, watcher);
+ watcher_free(watcher);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg,
+ DBusMessageIter *iter, struct characteristic *chr)
+{
+ struct gatt_service *gatt = chr->prim->gatt;
+ DBusMessageIter sub;
+ GError *gerr = NULL;
+ uint8_t *value;
+ int len;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(iter) != DBUS_TYPE_BYTE)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_recurse(iter, &sub);
+
+ dbus_message_iter_get_fixed_array(&sub, &value, &len);
+
+ if (l2cap_connect(gatt, &gerr, FALSE) < 0) {
+ DBusMessage *reply = btd_error_failed(msg, gerr->message);
+ g_error_free(gerr);
+ return reply;
+ }
+
+ gatt_write_cmd(gatt->attrib, chr->handle, value, len, NULL, NULL);
+
+ characteristic_set_value(chr, value, len);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct characteristic *chr = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ append_char_dict(&iter, chr);
+
+ return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct characteristic *chr = data;
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ const char *property;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &property);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ if (g_str_equal("Value", property))
+ return set_value(conn, msg, &sub, chr);
+
+ return btd_error_invalid_args(msg);
+}
+
+static GDBusMethodTable char_methods[] = {
+ { "GetProperties", "", "a{sv}", get_properties },
+ { "SetProperty", "sv", "", set_property,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { }
+};
+
+static char *characteristic_list_to_string(GSList *chars)
+{
+ GString *characteristics;
+ GSList *l;
+
+ characteristics = g_string_new(NULL);
+
+ for (l = chars; l; l = l->next) {
+ struct characteristic *chr = l->data;
+ char chr_str[64];
+
+ memset(chr_str, 0, sizeof(chr_str));
+
+ snprintf(chr_str, sizeof(chr_str), "%04X#%02X#%04X#%s ",
+ chr->handle, chr->perm, chr->end, chr->type);
+
+ characteristics = g_string_append(characteristics, chr_str);
+ }
+
+ return g_string_free(characteristics, FALSE);
+}
+
+static void store_characteristics(struct gatt_service *gatt,
+ struct primary *prim)
+{
+ char *characteristics;
+ struct att_primary *att = prim->att;
+
+ characteristics = characteristic_list_to_string(prim->chars);
+
+ write_device_characteristics(&gatt->sba, &gatt->dba, att->start,
+ characteristics);
+
+ g_free(characteristics);
+}
+
+static void register_characteristics(struct primary *prim)
+{
+ GSList *lc;
+
+ for (lc = prim->chars; lc; lc = lc->next) {
+ struct characteristic *chr = lc->data;
+ g_dbus_register_interface(connection, chr->path,
+ CHAR_INTERFACE, char_methods,
+ NULL, NULL, chr, NULL);
+ DBG("Registered: %s", chr->path);
+ }
+}
+
+static GSList *string_to_characteristic_list(struct primary *prim,
+ const char *str)
+{
+ GSList *l = NULL;
+ char **chars;
+ int i;
+
+ if (str == NULL)
+ return NULL;
+
+ chars = g_strsplit(str, " ", 0);
+ if (chars == NULL)
+ return NULL;
+
+ for (i = 0; chars[i]; i++) {
+ struct characteristic *chr;
+ int ret;
+
+ chr = g_new0(struct characteristic, 1);
+
+ ret = sscanf(chars[i], "%04hX#%02hhX#%04hX#%s", &chr->handle,
+ &chr->perm, &chr->end, chr->type);
+ if (ret < 4) {
+ g_free(chr);
+ continue;
+ }
+
+ chr->prim = prim;
+ chr->path = g_strdup_printf("%s/characteristic%04x",
+ prim->path, chr->handle);
+
+ l = g_slist_append(l, chr);
+ }
+
+ g_strfreev(chars);
+
+ return l;
+}
+
+static void load_characteristics(gpointer data, gpointer user_data)
+{
+ struct primary *prim = data;
+ struct att_primary *att = prim->att;
+ struct gatt_service *gatt = user_data;
+ GSList *chrs_list;
+ char *str;
+
+ if (prim->chars) {
+ DBG("Characteristics already loaded");
+ return;
+ }
+
+ str = read_device_characteristics(&gatt->sba, &gatt->dba, att->start);
+ if (str == NULL)
+ return;
+
+ chrs_list = string_to_characteristic_list(prim, str);
+
+ free(str);
+
+ if (chrs_list == NULL)
+ return;
+
+ prim->chars = chrs_list;
+ register_characteristics(prim);
+
+ return;
+}
+
+static void store_attribute(struct gatt_service *gatt, uint16_t handle,
+ uint16_t type, uint8_t *value, gsize len)
+{
+ bt_uuid_t uuid;
+ char *str, *tmp;
+ guint i;
+
+ str = g_malloc0(MAX_LEN_UUID_STR + len * 2 + 1);
+
+ bt_uuid16_create(&uuid, type);
+ bt_uuid_to_string(&uuid, str, MAX_LEN_UUID_STR);
+
+ str[MAX_LEN_UUID_STR - 1] = '#';
+
+ for (i = 0, tmp = str + MAX_LEN_UUID_STR; i < len; i++, tmp += 2)
+ sprintf(tmp, "%02X", value[i]);
+
+ write_device_attribute(&gatt->sba, &gatt->dba, handle, str);
+ g_free(str);
+}
+
+static void update_char_desc(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct query_data *current = user_data;
+ struct gatt_service *gatt = current->prim->gatt;
+ struct characteristic *chr = current->chr;
+
+ if (status == 0) {
+
+ g_free(chr->desc);
+
+ chr->desc = g_malloc(len);
+ memcpy(chr->desc, pdu + 1, len - 1);
+ chr->desc[len - 1] = '\0';
+
+ store_attribute(gatt, current->handle,
+ GATT_CHARAC_USER_DESC_UUID,
+ (void *) chr->desc, len);
+ } else if (status == ATT_ECODE_INSUFF_ENC) {
+ GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+ if (bt_io_set(io, BT_IO_L2CAP, NULL,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+ BT_IO_OPT_INVALID)) {
+ gatt_read_char(gatt->attrib, current->handle, 0,
+ update_char_desc, current);
+ return;
+ }
+ }
+
+ g_attrib_unref(gatt->attrib);
+ g_free(current);
+}
+
+static void update_char_format(guint8 status, const guint8 *pdu, guint16 len,
+ gpointer user_data)
+{
+ struct query_data *current = user_data;
+ struct gatt_service *gatt = current->prim->gatt;
+ struct characteristic *chr = current->chr;
+
+ if (status != 0)
+ goto done;
+
+ if (len < 8)
+ goto done;
+
+ g_free(chr->format);
+
+ chr->format = g_new0(struct format, 1);
+ memcpy(chr->format, pdu + 1, 7);
+
+ store_attribute(gatt, current->handle, GATT_CHARAC_FMT_UUID,
+ (void *) chr->format, sizeof(*chr->format));
+
+done:
+ g_attrib_unref(gatt->attrib);
+ g_free(current);
+}
+
+static void update_char_value(guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data)
+{
+ struct query_data *current = user_data;
+ struct gatt_service *gatt = current->prim->gatt;
+ struct characteristic *chr = current->chr;
+
+ if (status == 0)
+ characteristic_set_value(chr, pdu + 1, len - 1);
+ else if (status == ATT_ECODE_INSUFF_ENC) {
+ GIOChannel *io = g_attrib_get_channel(gatt->attrib);
+
+ if (bt_io_set(io, BT_IO_L2CAP, NULL,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH,
+ BT_IO_OPT_INVALID)) {
+ gatt_read_char(gatt->attrib, chr->handle, 0,
+ update_char_value, current);
+ return;
+ }
+ }
+
+ g_attrib_unref(gatt->attrib);
+ g_free(current);
+}
+
+static int uuid_desc16_cmp(bt_uuid_t *uuid, guint16 desc)
+{
+ bt_uuid_t u16;
+
+ bt_uuid16_create(&u16, desc);
+
+ return bt_uuid_cmp(uuid, &u16);
+}
+
+static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct query_data *current = user_data;
+ struct gatt_service *gatt = current->prim->gatt;
+ struct att_data_list *list;
+ guint8 format;
+ int i;
+
+ if (status != 0)
+ goto done;
+
+ DBG("Find Information Response received");
+
+ list = dec_find_info_resp(pdu, plen, &format);
+ if (list == NULL)
+ goto done;
+
+ for (i = 0; i < list->num; i++) {
+ guint16 handle;
+ bt_uuid_t uuid;
+ uint8_t *info = list->data[i];
+ struct query_data *qfmt;
+
+ handle = att_get_u16(info);
+
+ if (format == 0x01) {
+ uuid = att_get_uuid16(&info[2]);
+ } else {
+ /* Currently, only "user description" and "presentation
+ * format" descriptors are used, and both have 16-bit
+ * UUIDs. Therefore there is no need to support format
+ * 0x02 yet. */
+ continue;
+ }
+ qfmt = g_new0(struct query_data, 1);
+ qfmt->prim = current->prim;
+ qfmt->chr = current->chr;
+ qfmt->handle = handle;
+
+ if (uuid_desc16_cmp(&uuid, GATT_CHARAC_USER_DESC_UUID) == 0) {
+ gatt->attrib = g_attrib_ref(gatt->attrib);
+ gatt_read_char(gatt->attrib, handle, 0, update_char_desc,
+ qfmt);
+ } else if (uuid_desc16_cmp(&uuid, GATT_CHARAC_FMT_UUID) == 0) {
+ gatt->attrib = g_attrib_ref(gatt->attrib);
+ gatt_read_char(gatt->attrib, handle, 0,
+ update_char_format, qfmt);
+ } else
+ g_free(qfmt);
+ }
+
+ att_data_list_free(list);
+done:
+ g_attrib_unref(gatt->attrib);
+ g_free(current);
+}
+
+static void update_all_chars(gpointer data, gpointer user_data)
+{
+ struct query_data *qdesc, *qvalue;
+ struct characteristic *chr = data;
+ struct primary *prim = user_data;
+ struct gatt_service *gatt = prim->gatt;
+
+ qdesc = g_new0(struct query_data, 1);
+ qdesc->prim = prim;
+ qdesc->chr = chr;
+
+ gatt->attrib = g_attrib_ref(gatt->attrib);
+ gatt_find_info(gatt->attrib, chr->handle + 1, chr->end, descriptor_cb,
+ qdesc);
+
+ qvalue = g_new0(struct query_data, 1);
+ qvalue->prim = prim;
+ qvalue->chr = chr;
+
+ gatt->attrib = g_attrib_ref(gatt->attrib);
+ gatt_read_char(gatt->attrib, chr->handle, 0, update_char_value, qvalue);
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+ gpointer user_data)
+{
+ DBusMessage *reply;
+ DBusMessageIter iter, array_iter;
+ struct query_data *current = user_data;
+ struct primary *prim = current->prim;
+ struct att_primary *att = prim->att;
+ struct gatt_service *gatt = prim->gatt;
+ uint16_t *previous_end = NULL;
+ GSList *l;
+
+ if (status != 0) {
+ const char *str = att_ecode2str(status);
+
+ DBG("Discover all characteristics failed: %s", str);
+ reply = btd_error_failed(current->msg, str);
+ goto fail;
+ }
+
+ for (l = characteristics; l; l = l->next) {
+ struct att_char *current_chr = l->data;
+ struct characteristic *chr;
+ guint handle = current_chr->value_handle;
+ GSList *lchr;
+
+ lchr = g_slist_find_custom(prim->chars,
+ GUINT_TO_POINTER(handle), characteristic_handle_cmp);
+ if (lchr)
+ continue;
+
+ chr = g_new0(struct characteristic, 1);
+ chr->prim = prim;
+ chr->perm = current_chr->properties;
+ chr->handle = current_chr->value_handle;
+ chr->path = g_strdup_printf("%s/characteristic%04x",
+ prim->path, chr->handle);
+ strncpy(chr->type, current_chr->uuid, sizeof(chr->type));
+
+ if (previous_end)
+ *previous_end = current_chr->handle;
+
+ previous_end = &chr->end;
+
+ prim->chars = g_slist_append(prim->chars, chr);
+ }
+
+ if (previous_end)
+ *previous_end = att->end;
+
+ store_characteristics(gatt, prim);
+ register_characteristics(prim);
+
+ reply = dbus_message_new_method_return(current->msg);
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
+
+ for (l = prim->chars; l; l = l->next) {
+ struct characteristic *chr = l->data;
+
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_OBJECT_PATH, &chr->path);
+ }
+
+ dbus_message_iter_close_container(&iter, &array_iter);
+
+ g_slist_foreach(prim->chars, update_all_chars, prim);
+
+fail:
+ g_dbus_send_message(connection, reply);
+ g_attrib_unref(gatt->attrib);
+ g_free(current);
+}
+
+static DBusMessage *discover_char(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct primary *prim = data;
+ struct att_primary *att = prim->att;
+ struct gatt_service *gatt = prim->gatt;
+ struct query_data *qchr;
+ GError *gerr = NULL;
+
+ if (l2cap_connect(prim->gatt, &gerr, FALSE) < 0) {
+ DBusMessage *reply = btd_error_failed(msg, gerr->message);
+ g_error_free(gerr);
+ return reply;
+ }
+
+ qchr = g_new0(struct query_data, 1);
+ qchr->prim = prim;
+ qchr->msg = dbus_message_ref(msg);
+
+ gatt_discover_char(gatt->attrib, att->start, att->end, NULL,
+ char_discovered_cb, qchr);
+
+ return NULL;
+}
+
+static DBusMessage *prim_get_properties(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct primary *prim = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ GSList *l;
+ char **chars;
+ const char *uuid;
+ int i;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ chars = g_new0(char *, g_slist_length(prim->chars) + 1);
+
+ for (i = 0, l = prim->chars; l; l = l->next, i++) {
+ struct characteristic *chr = l->data;
+ chars[i] = chr->path;
+ }
+
+ dict_append_array(&dict, "Characteristics", DBUS_TYPE_OBJECT_PATH,
+ &chars, i);
+ uuid = prim->att->uuid;
+ dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid);
+
+ g_free(chars);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable prim_methods[] = {
+ { "DiscoverCharacteristics", "", "ao", discover_char,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "RegisterCharacteristicsWatcher", "o", "",
+ register_watcher },
+ { "UnregisterCharacteristicsWatcher", "o", "",
+ unregister_watcher },
+ { "GetProperties", "", "a{sv}",prim_get_properties },
+ { }
+};
+
+static void register_primaries(struct gatt_service *gatt, GSList *primaries)
+{
+ GSList *l;
+
+ for (l = primaries; l; l = l->next) {
+ struct att_primary *att = l->data;
+ struct primary *prim;
+
+ prim = g_new0(struct primary, 1);
+ prim->att = att;
+ prim->gatt = gatt;
+ prim->path = g_strdup_printf("%s/service%04x", gatt->path,
+ att->start);
+
+ g_dbus_register_interface(connection, prim->path,
+ CHAR_INTERFACE, prim_methods,
+ NULL, NULL, prim, NULL);
+ DBG("Registered: %s", prim->path);
+
+ gatt->primary = g_slist_append(gatt->primary, prim);
+ btd_device_add_service(gatt->dev, prim->path);
+ load_characteristics(prim, gatt);
+ }
+}
+
+int attrib_client_register(struct btd_device *device, int psm)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ const char *path = device_get_path(device);
+ struct gatt_service *gatt;
+ GSList *primaries = btd_device_get_primaries(device);
+ bdaddr_t sba, dba;
+
+ adapter_get_address(adapter, &sba);
+ device_get_address(device, &dba);
+
+ gatt = g_new0(struct gatt_service, 1);
+ gatt->dev = btd_device_ref(device);
+ gatt->listen = FALSE;
+ gatt->path = g_strdup(path);
+ bacpy(&gatt->sba, &sba);
+ bacpy(&gatt->dba, &dba);
+ gatt->psm = psm;
+
+ register_primaries(gatt, primaries);
+
+ gatt_services = g_slist_append(gatt_services, gatt);
+
+ return 0;
+}
+
+void attrib_client_unregister(struct btd_device *device)
+{
+ struct gatt_service *gatt;
+ GSList *l, *lp, *lc;
+
+ l = g_slist_find_custom(gatt_services, device, gatt_dev_cmp);
+ if (!l)
+ return;
+
+ gatt = l->data;
+ gatt_services = g_slist_remove(gatt_services, gatt);
+
+ for (lp = gatt->primary; lp; lp = lp->next) {
+ struct primary *prim = lp->data;
+ for (lc = prim->chars; lc; lc = lc->next) {
+ struct characteristic *chr = lc->data;
+ g_dbus_unregister_interface(connection, chr->path,
+ CHAR_INTERFACE);
+ }
+ g_dbus_unregister_interface(connection, prim->path,
+ CHAR_INTERFACE);
+ }
+
+ gatt_service_free(gatt);
+}
+
+int attrib_client_init(DBusConnection *conn)
+{
+
+ connection = dbus_connection_ref(conn);
+
+ /*
+ * FIXME: if the adapter supports BLE start scanning. Temporary
+ * solution, this approach doesn't allow to control scanning based
+ * on the discoverable property.
+ */
+
+ return 0;
+}
+
+void attrib_client_exit(void)
+{
+ dbus_connection_unref(connection);
+}
diff --git a/attrib/client.h b/attrib/client.h
new file mode 100644
index 0000000..50e2b5f
--- /dev/null
+++ b/attrib/client.h
@@ -0,0 +1,28 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+int attrib_client_init(DBusConnection *conn);
+void attrib_client_exit(void);
+int attrib_client_register(struct btd_device *device, int psm);
+void attrib_client_unregister(struct btd_device *device);
diff --git a/attrib/example.c b/attrib/example.c
new file mode 100644
index 0000000..fae288c
--- /dev/null
+++ b/attrib/example.c
@@ -0,0 +1,341 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 <arpa/inet.h>
+
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include "log.h"
+#include "attrib-server.h"
+
+#include "att.h"
+#include "example.h"
+
+/* FIXME: Not defined by SIG? UUID128? */
+#define OPCODES_SUPPORTED_UUID 0xA001
+#define BATTERY_STATE_SVC_UUID 0xA002
+#define BATTERY_STATE_UUID 0xA003
+#define THERM_HUMIDITY_SVC_UUID 0xA004
+#define MANUFACTURER_SVC_UUID 0xA005
+#define TEMPERATURE_UUID 0xA006
+#define FMT_CELSIUS_UUID 0xA007
+#define FMT_OUTSIDE_UUID 0xA008
+#define RELATIVE_HUMIDITY_UUID 0xA009
+#define FMT_PERCENT_UUID 0xA00A
+#define BLUETOOTH_SIG_UUID 0xA00B
+#define MANUFACTURER_NAME_UUID 0xA00C
+#define MANUFACTURER_SERIAL_UUID 0xA00D
+#define VENDOR_SPECIFIC_SVC_UUID 0xA00E
+#define VENDOR_SPECIFIC_TYPE_UUID 0xA00F
+#define FMT_KILOGRAM_UUID 0xA010
+#define FMT_HANGING_UUID 0xA011
+
+static GSList *sdp_handles = NULL;
+
+static int register_attributes(void)
+{
+ const char *desc_out_temp = "Outside Temperature";
+ const char *desc_out_hum = "Outside Relative Humidity";
+ const char *desc_weight = "Rucksack Weight";
+ const char *manufacturer_name1 = "ACME Temperature Sensor";
+ const char *manufacturer_name2 = "ACME Weighing Scales";
+ const char *serial1 = "237495-3282-A";
+ const char *serial2 = "11267-2327A00239";
+
+ const uint128_t char_weight_uuid_btorder = {
+ .data = { 0x80, 0x88, 0xF2, 0x18, 0x90, 0x2C, 0x45, 0x0B,
+ 0xB6, 0xC4, 0x62, 0x89, 0x1E, 0x8C, 0x25, 0xE9 } };
+ const uint128_t prim_weight_uuid_btorder = {
+ .data = { 0x4F, 0x0A, 0xC0, 0x96, 0x35, 0xD4, 0x49, 0x11,
+ 0x96, 0x31, 0xDE, 0xA8, 0xDC, 0x74, 0xEE, 0xFE } };
+
+ uint128_t char_weight_uuid;
+ uint8_t atval[256];
+ uint32_t handle;
+ bt_uuid_t uuid;
+ int len;
+
+ btoh128(&char_weight_uuid_btorder, &char_weight_uuid);
+
+ /* Battery state service: primary service definition */
+ bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+ att_put_u16(BATTERY_STATE_SVC_UUID, &atval[0]);
+ attrib_db_add(0x0100, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Battery: battery state characteristic */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0110, &atval[1]);
+ att_put_u16(BATTERY_STATE_UUID, &atval[3]);
+ attrib_db_add(0x0106, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Battery: battery state attribute */
+ bt_uuid16_create(&uuid, BATTERY_STATE_UUID);
+ atval[0] = 0x04;
+ attrib_db_add(0x0110, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+ /* Battery: Client Characteristic Configuration */
+ bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+ atval[0] = 0x00;
+ atval[1] = 0x00;
+ attrib_db_add(0x0111, &uuid, ATT_NONE, ATT_AUTHENTICATION, atval, 2);
+
+ /* Add an SDP record for the above service */
+ handle = attrib_create_sdp(0x0100, "Battery State Service");
+ if (handle)
+ sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+ /* Thermometer: primary service definition */
+ bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+ att_put_u16(THERM_HUMIDITY_SVC_UUID, &atval[0]);
+ attrib_db_add(0x0200, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Thermometer: Include */
+ bt_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+ att_put_u16(0x0500, &atval[0]);
+ att_put_u16(0x0504, &atval[2]);
+ att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+ attrib_db_add(0x0201, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+ /* Thermometer: Include */
+ att_put_u16(0x0550, &atval[0]);
+ att_put_u16(0x0568, &atval[2]);
+ att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[4]);
+ attrib_db_add(0x0202, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+ /* Thermometer: temperature characteristic */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0204, &atval[1]);
+ att_put_u16(TEMPERATURE_UUID, &atval[3]);
+ attrib_db_add(0x0203, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Thermometer: temperature characteristic value */
+ bt_uuid16_create(&uuid, TEMPERATURE_UUID);
+ atval[0] = 0x8A;
+ atval[1] = 0x02;
+ attrib_db_add(0x0204, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Thermometer: temperature characteristic format */
+ bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+ atval[0] = 0x0E;
+ atval[1] = 0xFE;
+ att_put_u16(FMT_CELSIUS_UUID, &atval[2]);
+ atval[4] = 0x01;
+ att_put_u16(FMT_OUTSIDE_UUID, &atval[5]);
+ attrib_db_add(0x0205, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 7);
+
+ /* Thermometer: characteristic user description */
+ bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+ len = strlen(desc_out_temp);
+ strncpy((char *) atval, desc_out_temp, len);
+ attrib_db_add(0x0206, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Thermometer: relative humidity characteristic */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0212, &atval[1]);
+ att_put_u16(RELATIVE_HUMIDITY_UUID, &atval[3]);
+ attrib_db_add(0x0210, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Thermometer: relative humidity value */
+ bt_uuid16_create(&uuid, RELATIVE_HUMIDITY_UUID);
+ atval[0] = 0x27;
+ attrib_db_add(0x0212, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1);
+
+ /* Thermometer: relative humidity characteristic format */
+ bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+ atval[0] = 0x04;
+ atval[1] = 0x00;
+ att_put_u16(FMT_PERCENT_UUID, &atval[2]);
+ att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+ att_put_u16(FMT_OUTSIDE_UUID, &atval[6]);
+ attrib_db_add(0x0213, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+ /* Thermometer: characteristic user description */
+ bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+ len = strlen(desc_out_hum);
+ strncpy((char *) atval, desc_out_hum, len);
+ attrib_db_add(0x0214, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Add an SDP record for the above service */
+ handle = attrib_create_sdp(0x0200, "Thermometer");
+ if (handle)
+ sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+ /* Secondary Service: Manufacturer Service */
+ bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+ att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+ attrib_db_add(0x0500, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Manufacturer name characteristic definition */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0502, &atval[1]);
+ att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+ attrib_db_add(0x0501, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Manufacturer name characteristic value */
+ bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+ len = strlen(manufacturer_name1);
+ strncpy((char *) atval, manufacturer_name1, len);
+ attrib_db_add(0x0502, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Manufacturer serial number characteristic */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0504, &atval[1]);
+ att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+ attrib_db_add(0x0503, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Manufacturer serial number characteristic value */
+ bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+ len = strlen(serial1);
+ strncpy((char *) atval, serial1, len);
+ attrib_db_add(0x0504, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Secondary Service: Manufacturer Service */
+ bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+ att_put_u16(MANUFACTURER_SVC_UUID, &atval[0]);
+ attrib_db_add(0x0505, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Manufacturer name characteristic definition */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0507, &atval[1]);
+ att_put_u16(MANUFACTURER_NAME_UUID, &atval[3]);
+ attrib_db_add(0x0506, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Secondary Service: Vendor Specific Service */
+ bt_uuid16_create(&uuid, GATT_SND_SVC_UUID);
+ att_put_u16(VENDOR_SPECIFIC_SVC_UUID, &atval[0]);
+ attrib_db_add(0x0550, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2);
+
+ /* Vendor Specific Type characteristic definition */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0568, &atval[1]);
+ att_put_u16(VENDOR_SPECIFIC_TYPE_UUID, &atval[3]);
+ attrib_db_add(0x0560, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Vendor Specific Type characteristic value */
+ bt_uuid16_create(&uuid, VENDOR_SPECIFIC_TYPE_UUID);
+ atval[0] = 0x56;
+ atval[1] = 0x65;
+ atval[2] = 0x6E;
+ atval[3] = 0x64;
+ atval[4] = 0x6F;
+ atval[5] = 0x72;
+ attrib_db_add(0x0568, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+ /* Manufacturer name attribute */
+ bt_uuid16_create(&uuid, MANUFACTURER_NAME_UUID);
+ len = strlen(manufacturer_name2);
+ strncpy((char *) atval, manufacturer_name2, len);
+ attrib_db_add(0x0507, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Characteristic: serial number */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0509, &atval[1]);
+ att_put_u16(MANUFACTURER_SERIAL_UUID, &atval[3]);
+ attrib_db_add(0x0508, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5);
+
+ /* Serial number characteristic value */
+ bt_uuid16_create(&uuid, MANUFACTURER_SERIAL_UUID);
+ len = strlen(serial2);
+ strncpy((char *) atval, serial2, len);
+ attrib_db_add(0x0509, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Weight service: primary service definition */
+ bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID);
+ memcpy(atval, &prim_weight_uuid_btorder, 16);
+ attrib_db_add(0x0680, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 16);
+
+ /* Weight: include */
+ bt_uuid16_create(&uuid, GATT_INCLUDE_UUID);
+ att_put_u16(0x0505, &atval[0]);
+ att_put_u16(0x0509, &atval[2]);
+ att_put_u16(MANUFACTURER_SVC_UUID, &atval[4]);
+ attrib_db_add(0x0681, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 6);
+
+ /* Weight: characteristic */
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+ atval[0] = ATT_CHAR_PROPER_READ;
+ att_put_u16(0x0683, &atval[1]);
+ memcpy(&atval[3], &char_weight_uuid_btorder, 16);
+ attrib_db_add(0x0682, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 19);
+
+ /* Weight: characteristic value */
+ bt_uuid128_create(&uuid, char_weight_uuid);
+ atval[0] = 0x82;
+ atval[1] = 0x55;
+ atval[2] = 0x00;
+ atval[3] = 0x00;
+ attrib_db_add(0x0683, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 4);
+
+ /* Weight: characteristic format */
+ bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
+ atval[0] = 0x08;
+ atval[1] = 0xFD;
+ att_put_u16(FMT_KILOGRAM_UUID, &atval[2]);
+ att_put_u16(BLUETOOTH_SIG_UUID, &atval[4]);
+ att_put_u16(FMT_HANGING_UUID, &atval[6]);
+ attrib_db_add(0x0684, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 8);
+
+ /* Weight: characteristic user description */
+ bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
+ len = strlen(desc_weight);
+ strncpy((char *) atval, desc_weight, len);
+ attrib_db_add(0x0685, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, len);
+
+ /* Add an SDP record for the above service */
+ handle = attrib_create_sdp(0x0680, "Weight Service");
+ if (handle)
+ sdp_handles = g_slist_prepend(sdp_handles, GUINT_TO_POINTER(handle));
+
+ return 0;
+}
+
+int server_example_init(void)
+{
+ return register_attributes();
+}
+
+void server_example_exit(void)
+{
+ while (sdp_handles) {
+ uint32_t handle = GPOINTER_TO_UINT(sdp_handles->data);
+
+ attrib_free_sdp(handle);
+ sdp_handles = g_slist_remove(sdp_handles, sdp_handles->data);
+ }
+}
diff --git a/attrib/example.h b/attrib/example.h
new file mode 100644
index 0000000..a2b07fe
--- /dev/null
+++ b/attrib/example.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+int server_example_init(void);
+void server_example_exit(void);
diff --git a/attrib/gatt.c b/attrib/gatt.c
new file mode 100644
index 0000000..0b69daf
--- /dev/null
+++ b/attrib/gatt.c
@@ -0,0 +1,577 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+#include <stdint.h>
+#include <glib.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "gattrib.h"
+#include "gatt.h"
+
+struct discover_primary {
+ GAttrib *attrib;
+ bt_uuid_t uuid;
+ GSList *primaries;
+ gatt_cb_t cb;
+ void *user_data;
+};
+
+struct discover_char {
+ GAttrib *attrib;
+ bt_uuid_t *uuid;
+ uint16_t end;
+ GSList *characteristics;
+ gatt_cb_t cb;
+ void *user_data;
+};
+
+static void discover_primary_free(struct discover_primary *dp)
+{
+ g_slist_free(dp->primaries);
+ g_attrib_unref(dp->attrib);
+ g_free(dp);
+}
+
+static void discover_char_free(struct discover_char *dc)
+{
+ g_slist_foreach(dc->characteristics, (GFunc) g_free, NULL);
+ g_slist_free(dc->characteristics);
+ g_attrib_unref(dc->attrib);
+ g_free(dc->uuid);
+ g_free(dc);
+}
+
+static guint16 encode_discover_primary(uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, uint8_t *pdu, size_t len)
+{
+ bt_uuid_t prim;
+ guint16 plen;
+ uint8_t op;
+
+ bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID);
+
+ if (uuid == NULL) {
+ /* Discover all primary services */
+ op = ATT_OP_READ_BY_GROUP_REQ;
+ plen = enc_read_by_grp_req(start, end, &prim, pdu, len);
+ } else {
+ uint16_t u16;
+ uint128_t u128;
+ const void *value;
+ int vlen;
+
+ /* Discover primary service by service UUID */
+ op = ATT_OP_FIND_BY_TYPE_REQ;
+
+ if (uuid->type == BT_UUID16) {
+ u16 = htobs(uuid->value.u16);
+ value = &u16;
+ vlen = sizeof(u16);
+ } else {
+ htob128(&uuid->value.u128, &u128);
+ value = &u128;
+ vlen = sizeof(u128);
+ }
+
+ plen = enc_find_by_type_req(start, end, &prim, value, vlen,
+ pdu, len);
+ }
+
+ return plen;
+}
+
+static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu,
+ guint16 iplen, gpointer user_data)
+
+{
+ struct discover_primary *dp = user_data;
+ GSList *ranges, *last;
+ struct att_range *range;
+ uint8_t *buf;
+ guint16 oplen;
+ int err = 0, buflen;
+
+ if (status) {
+ err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+ goto done;
+ }
+
+ ranges = dec_find_by_type_resp(ipdu, iplen);
+ if (ranges == NULL)
+ goto done;
+
+ dp->primaries = g_slist_concat(dp->primaries, ranges);
+
+ last = g_slist_last(ranges);
+ range = last->data;
+
+ if (range->end == 0xffff)
+ goto done;
+
+ buf = g_attrib_get_buffer(dp->attrib, &buflen);
+ oplen = encode_discover_primary(range->end + 1, 0xffff, &dp->uuid,
+ buf, buflen);
+
+ if (oplen == 0)
+ goto done;
+
+ g_attrib_send(dp->attrib, 0, buf[0], buf, oplen, primary_by_uuid_cb,
+ dp, NULL);
+ return;
+
+done:
+ dp->cb(dp->primaries, err, dp->user_data);
+ discover_primary_free(dp);
+}
+
+static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+ gpointer user_data)
+{
+ struct discover_primary *dp = user_data;
+ struct att_data_list *list;
+ unsigned int i, err;
+ uint16_t start, end;
+
+ if (status) {
+ err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+ goto done;
+ }
+
+ list = dec_read_by_grp_resp(ipdu, iplen);
+ if (list == NULL) {
+ err = ATT_ECODE_IO;
+ goto done;
+ }
+
+ for (i = 0, end = 0; i < list->num; i++) {
+ const uint8_t *data = list->data[i];
+ struct att_primary *primary;
+ bt_uuid_t uuid;
+
+ start = att_get_u16(&data[0]);
+ end = att_get_u16(&data[2]);
+
+ if (list->len == 6) {
+ bt_uuid_t uuid16 = att_get_uuid16(&data[4]);
+ bt_uuid_to_uuid128(&uuid16, &uuid);
+ } else if (list->len == 20) {
+ uuid = att_get_uuid128(&data[4]);
+ } else {
+ /* Skipping invalid data */
+ continue;
+ }
+
+ primary = g_try_new0(struct att_primary, 1);
+ if (!primary) {
+ err = ATT_ECODE_INSUFF_RESOURCES;
+ goto done;
+ }
+ primary->start = start;
+ primary->end = end;
+ bt_uuid_to_string(&uuid, primary->uuid, sizeof(primary->uuid));
+ dp->primaries = g_slist_append(dp->primaries, primary);
+ }
+
+ att_data_list_free(list);
+ err = 0;
+
+ if (end != 0xffff) {
+ int buflen;
+ uint8_t *buf = g_attrib_get_buffer(dp->attrib, &buflen);
+ guint16 oplen = encode_discover_primary(end + 1, 0xffff, NULL,
+ buf, buflen);
+
+ g_attrib_send(dp->attrib, 0, buf[0], buf, oplen, primary_all_cb,
+ dp, NULL);
+
+ return;
+ }
+
+done:
+ dp->cb(dp->primaries, err, dp->user_data);
+ discover_primary_free(dp);
+}
+
+guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data)
+{
+ struct discover_primary *dp;
+ int buflen;
+ uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
+ GAttribResultFunc cb;
+ guint16 plen;
+
+ plen = encode_discover_primary(0x0001, 0xffff, uuid, buf, buflen);
+ if (plen == 0)
+ return 0;
+
+ dp = g_try_new0(struct discover_primary, 1);
+ if (dp == NULL)
+ return 0;
+
+ dp->attrib = g_attrib_ref(attrib);
+ dp->cb = func;
+ dp->user_data = user_data;
+
+ if (uuid) {
+ memcpy(&dp->uuid, uuid, sizeof(bt_uuid_t));
+ cb = primary_by_uuid_cb;
+ } else
+ cb = primary_all_cb;
+
+ return g_attrib_send(attrib, 0, buf[0], buf, plen, cb, dp, NULL);
+}
+
+static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen,
+ gpointer user_data)
+{
+ struct discover_char *dc = user_data;
+ struct att_data_list *list;
+ unsigned int i, err;
+ int buflen;
+ uint8_t *buf;
+ guint16 oplen;
+ bt_uuid_t uuid;
+ uint16_t last = 0;
+
+ if (status) {
+ err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status;
+ goto done;
+ }
+
+ list = dec_read_by_type_resp(ipdu, iplen);
+ if (list == NULL) {
+ err = ATT_ECODE_IO;
+ goto done;
+ }
+
+ for (i = 0; i < list->num; i++) {
+ uint8_t *value = list->data[i];
+ struct att_char *chars;
+ bt_uuid_t uuid;
+
+ last = att_get_u16(value);
+
+ if (list->len == 7) {
+ bt_uuid_t uuid16 = att_get_uuid16(&value[5]);
+ bt_uuid_to_uuid128(&uuid16, &uuid);
+ } else
+ uuid = att_get_uuid128(&value[5]);
+
+ chars = g_try_new0(struct att_char, 1);
+ if (!chars) {
+ err = ATT_ECODE_INSUFF_RESOURCES;
+ goto done;
+ }
+
+ if (dc->uuid && bt_uuid_cmp(dc->uuid, &uuid))
+ break;
+
+ chars->handle = last;
+ chars->properties = value[2];
+ chars->value_handle = att_get_u16(&value[3]);
+ bt_uuid_to_string(&uuid, chars->uuid, sizeof(chars->uuid));
+ dc->characteristics = g_slist_append(dc->characteristics,
+ chars);
+ }
+
+ att_data_list_free(list);
+ err = 0;
+
+ if (last != 0) {
+ buf = g_attrib_get_buffer(dc->attrib, &buflen);
+
+ bt_uuid16_create(&uuid, GATT_CHARAC_UUID);
+
+ oplen = enc_read_by_type_req(last + 1, dc->end, &uuid, buf,
+ buflen);
+
+ if (oplen == 0)
+ return;
+
+ g_attrib_send(dc->attrib, 0, buf[0], buf, oplen,
+ char_discovered_cb, dc, NULL);
+
+ return;
+ }
+
+done:
+ dc->cb(dc->characteristics, err, dc->user_data);
+ discover_char_free(dc);
+}
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data)
+{
+ int buflen;
+ uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
+ struct discover_char *dc;
+ bt_uuid_t type_uuid;
+ guint16 plen;
+
+ bt_uuid16_create(&type_uuid, GATT_CHARAC_UUID);
+
+ plen = enc_read_by_type_req(start, end, &type_uuid, buf, buflen);
+ if (plen == 0)
+ return 0;
+
+ dc = g_try_new0(struct discover_char, 1);
+ if (dc == NULL)
+ return 0;
+
+ dc->attrib = g_attrib_ref(attrib);
+ dc->cb = func;
+ dc->user_data = user_data;
+ dc->end = end;
+ dc->uuid = g_memdup(uuid, sizeof(bt_uuid_t));
+
+ return g_attrib_send(attrib, 0, buf[0], buf, plen, char_discovered_cb,
+ dc, NULL);
+}
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, GAttribResultFunc func,
+ gpointer user_data)
+{
+ int buflen;
+ uint8_t *buf = g_attrib_get_buffer(attrib, &buflen);
+ guint16 plen;
+
+ plen = enc_read_by_type_req(start, end, uuid, buf, buflen);
+ if (plen == 0)
+ return 0;
+
+ return g_attrib_send(attrib, 0, ATT_OP_READ_BY_TYPE_REQ,
+ buf, plen, func, user_data, NULL);
+}
+
+struct read_long_data {
+ GAttrib *attrib;
+ GAttribResultFunc func;
+ gpointer user_data;
+ guint8 *buffer;
+ guint16 size;
+ guint16 handle;
+ guint id;
+ gint ref;
+};
+
+static void read_long_destroy(gpointer user_data)
+{
+ struct read_long_data *long_read = user_data;
+
+ if (g_atomic_int_dec_and_test(&long_read->ref) == FALSE)
+ return;
+
+ if (long_read->buffer != NULL)
+ g_free(long_read->buffer);
+
+ g_free(long_read);
+}
+
+static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen,
+ gpointer user_data)
+{
+ struct read_long_data *long_read = user_data;
+ uint8_t *buf;
+ int buflen;
+ guint8 *tmp;
+ guint16 plen;
+ guint id;
+
+ if (status != 0 || rlen == 1) {
+ status = 0;
+ goto done;
+ }
+
+ tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1);
+
+ if (tmp == NULL) {
+ status = ATT_ECODE_INSUFF_RESOURCES;
+ goto done;
+ }
+
+ memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1);
+ long_read->buffer = tmp;
+ long_read->size += rlen - 1;
+
+ buf = g_attrib_get_buffer(long_read->attrib, &buflen);
+ if (rlen < buflen)
+ goto done;
+
+ plen = enc_read_blob_req(long_read->handle, long_read->size - 1,
+ buf, buflen);
+ id = g_attrib_send(long_read->attrib, long_read->id,
+ ATT_OP_READ_BLOB_REQ, buf, plen,
+ read_blob_helper, long_read, read_long_destroy);
+
+ if (id != 0) {
+ g_atomic_int_inc(&long_read->ref);
+ return;
+ }
+
+ status = ATT_ECODE_IO;
+
+done:
+ long_read->func(status, long_read->buffer, long_read->size,
+ long_read->user_data);
+}
+
+static void read_char_helper(guint8 status, const guint8 *rpdu,
+ guint16 rlen, gpointer user_data)
+{
+ struct read_long_data *long_read = user_data;
+ int buflen;
+ uint8_t *buf = g_attrib_get_buffer(long_read->attrib, &buflen);
+ guint16 plen;
+ guint id;
+
+ if (status != 0 || rlen < buflen)
+ goto done;
+
+ long_read->buffer = g_malloc(rlen);
+
+ if (long_read->buffer == NULL)
+ goto done;
+
+ memcpy(long_read->buffer, rpdu, rlen);
+ long_read->size = rlen;
+
+ plen = enc_read_blob_req(long_read->handle, rlen - 1, buf, buflen);
+ id = g_attrib_send(long_read->attrib, long_read->id,
+ ATT_OP_READ_BLOB_REQ, buf, plen, read_blob_helper,
+ long_read, read_long_destroy);
+
+ if (id != 0) {
+ g_atomic_int_inc(&long_read->ref);
+ return;
+ }
+
+ status = ATT_ECODE_IO;
+
+done:
+ long_read->func(status, rpdu, rlen, long_read->user_data);
+}
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset,
+ GAttribResultFunc func, gpointer user_data)
+{
+ uint8_t *buf;
+ int buflen;
+ guint16 plen;
+ guint id;
+ struct read_long_data *long_read;
+
+ long_read = g_try_new0(struct read_long_data, 1);
+
+ if (long_read == NULL)
+ return 0;
+
+ long_read->attrib = attrib;
+ long_read->func = func;
+ long_read->user_data = user_data;
+ long_read->handle = handle;
+
+ buf = g_attrib_get_buffer(attrib, &buflen);
+ if (offset > 0) {
+ plen = enc_read_blob_req(long_read->handle, offset, buf,
+ buflen);
+ id = g_attrib_send(attrib, 0, ATT_OP_READ_BLOB_REQ, buf, plen,
+ read_blob_helper, long_read, read_long_destroy);
+ } else {
+ plen = enc_read_req(handle, buf, buflen);
+ id = g_attrib_send(attrib, 0, ATT_OP_READ_REQ, buf, plen,
+ read_char_helper, long_read, read_long_destroy);
+ }
+
+ if (id == 0)
+ g_free(long_read);
+ else {
+ g_atomic_int_inc(&long_read->ref);
+ long_read->id = id;
+ }
+
+ return id;
+}
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+ int vlen, GAttribResultFunc func, gpointer user_data)
+{
+ uint8_t *buf;
+ int buflen;
+ guint16 plen;
+
+ buf = g_attrib_get_buffer(attrib, &buflen);
+ if (func)
+ plen = enc_write_req(handle, value, vlen, buf, buflen);
+ else
+ plen = enc_write_cmd(handle, value, vlen, buf, buflen);
+
+ return g_attrib_send(attrib, 0, buf[0], buf, plen, func,
+ user_data, NULL);
+}
+
+guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func,
+ gpointer user_data)
+{
+ uint8_t *buf;
+ int buflen;
+ guint16 plen;
+
+ buf = g_attrib_get_buffer(attrib, &buflen);
+ plen = enc_mtu_req(mtu, buf, buflen);
+ return g_attrib_send(attrib, 0, ATT_OP_MTU_REQ, buf, plen, func,
+ user_data, NULL);
+}
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+ GAttribResultFunc func, gpointer user_data)
+{
+ uint8_t *buf;
+ int buflen;
+ guint16 plen;
+
+ buf = g_attrib_get_buffer(attrib, &buflen);
+ plen = enc_find_info_req(start, end, buf, buflen);
+ if (plen == 0)
+ return 0;
+
+ return g_attrib_send(attrib, 0, ATT_OP_FIND_INFO_REQ, buf, plen, func,
+ user_data, NULL);
+}
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+ GDestroyNotify notify, gpointer user_data)
+{
+ uint8_t *buf;
+ int buflen;
+ guint16 plen;
+
+ buf = g_attrib_get_buffer(attrib, &buflen);
+ plen = enc_write_cmd(handle, value, vlen, buf, buflen);
+ return g_attrib_send(attrib, 0, ATT_OP_WRITE_CMD, buf, plen, NULL,
+ user_data, notify);
+}
diff --git a/attrib/gatt.h b/attrib/gatt.h
new file mode 100644
index 0000000..221d94d
--- /dev/null
+++ b/attrib/gatt.h
@@ -0,0 +1,53 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+#define GATT_CID 4
+
+typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
+
+guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data);
+
+guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, gatt_cb_t func,
+ gpointer user_data);
+
+guint gatt_read_char(GAttrib *attrib, uint16_t handle, uint16_t offset,
+ GAttribResultFunc func, gpointer user_data);
+
+guint gatt_write_char(GAttrib *attrib, uint16_t handle, uint8_t *value,
+ int vlen, GAttribResultFunc func, gpointer user_data);
+
+guint gatt_find_info(GAttrib *attrib, uint16_t start, uint16_t end,
+ GAttribResultFunc func, gpointer user_data);
+
+guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, uint8_t *value, int vlen,
+ GDestroyNotify notify, gpointer user_data);
+
+guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end,
+ bt_uuid_t *uuid, GAttribResultFunc func,
+ gpointer user_data);
+
+guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func,
+ gpointer user_data);
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
new file mode 100644
index 0000000..290cd96
--- /dev/null
+++ b/attrib/gattrib.c
@@ -0,0 +1,636 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <glib.h>
+
+#include <stdio.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+
+#define GATT_TIMEOUT 30
+
+struct _GAttrib {
+ GIOChannel *io;
+ gint refs;
+ uint8_t *buf;
+ int buflen;
+ guint read_watch;
+ guint write_watch;
+ guint timeout_watch;
+ GQueue *queue;
+ GSList *events;
+ guint next_cmd_id;
+ guint next_evt_id;
+ GDestroyNotify destroy;
+ GAttribDisconnectFunc disconnect;
+ gpointer destroy_user_data;
+ gpointer disc_user_data;
+};
+
+struct command {
+ guint id;
+ guint8 opcode;
+ guint8 *pdu;
+ guint16 len;
+ guint8 expected;
+ gboolean sent;
+ GAttribResultFunc func;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+struct event {
+ guint id;
+ guint8 expected;
+ GAttribNotifyFunc func;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+static guint8 opcode2expected(guint8 opcode)
+{
+ switch (opcode) {
+ case ATT_OP_MTU_REQ:
+ return ATT_OP_MTU_RESP;
+
+ case ATT_OP_FIND_INFO_REQ:
+ return ATT_OP_FIND_INFO_RESP;
+
+ case ATT_OP_FIND_BY_TYPE_REQ:
+ return ATT_OP_FIND_BY_TYPE_RESP;
+
+ case ATT_OP_READ_BY_TYPE_REQ:
+ return ATT_OP_READ_BY_TYPE_RESP;
+
+ case ATT_OP_READ_REQ:
+ return ATT_OP_READ_RESP;
+
+ case ATT_OP_READ_BLOB_REQ:
+ return ATT_OP_READ_BLOB_RESP;
+
+ case ATT_OP_READ_MULTI_REQ:
+ return ATT_OP_READ_MULTI_RESP;
+
+ case ATT_OP_READ_BY_GROUP_REQ:
+ return ATT_OP_READ_BY_GROUP_RESP;
+
+ case ATT_OP_WRITE_REQ:
+ return ATT_OP_WRITE_RESP;
+
+ case ATT_OP_PREP_WRITE_REQ:
+ return ATT_OP_PREP_WRITE_RESP;
+
+ case ATT_OP_EXEC_WRITE_REQ:
+ return ATT_OP_EXEC_WRITE_RESP;
+
+ case ATT_OP_HANDLE_IND:
+ return ATT_OP_HANDLE_CNF;
+ }
+
+ return 0;
+}
+
+static gboolean is_response(guint8 opcode)
+{
+ switch (opcode) {
+ case ATT_OP_ERROR:
+ case ATT_OP_MTU_RESP:
+ case ATT_OP_FIND_INFO_RESP:
+ case ATT_OP_FIND_BY_TYPE_RESP:
+ case ATT_OP_READ_BY_TYPE_RESP:
+ case ATT_OP_READ_RESP:
+ case ATT_OP_READ_BLOB_RESP:
+ case ATT_OP_READ_MULTI_RESP:
+ case ATT_OP_READ_BY_GROUP_RESP:
+ case ATT_OP_WRITE_RESP:
+ case ATT_OP_PREP_WRITE_RESP:
+ case ATT_OP_EXEC_WRITE_RESP:
+ case ATT_OP_HANDLE_CNF:
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GAttrib *g_attrib_ref(GAttrib *attrib)
+{
+ if (!attrib)
+ return NULL;
+
+ g_atomic_int_inc(&attrib->refs);
+
+ return attrib;
+}
+
+static void command_destroy(struct command *cmd)
+{
+ if (cmd->notify)
+ cmd->notify(cmd->user_data);
+
+ g_free(cmd->pdu);
+ g_free(cmd);
+}
+
+static void event_destroy(struct event *evt)
+{
+ if (evt->notify)
+ evt->notify(evt->user_data);
+
+ g_free(evt);
+}
+
+static void attrib_destroy(GAttrib *attrib)
+{
+ GSList *l;
+ struct command *c;
+
+ while ((c = g_queue_pop_head(attrib->queue)))
+ command_destroy(c);
+
+ g_queue_free(attrib->queue);
+ attrib->queue = NULL;
+
+ for (l = attrib->events; l; l = l->next)
+ event_destroy(l->data);
+
+ g_slist_free(attrib->events);
+ attrib->events = NULL;
+
+ if (attrib->timeout_watch > 0)
+ g_source_remove(attrib->timeout_watch);
+
+ if (attrib->write_watch > 0)
+ g_source_remove(attrib->write_watch);
+
+ if (attrib->read_watch > 0) {
+ g_source_remove(attrib->read_watch);
+ g_io_channel_unref(attrib->io);
+ }
+
+ g_free(attrib->buf);
+
+ if (attrib->destroy)
+ attrib->destroy(attrib->destroy_user_data);
+
+ g_free(attrib);
+}
+
+void g_attrib_unref(GAttrib *attrib)
+{
+ if (!attrib)
+ return;
+
+ if (g_atomic_int_dec_and_test(&attrib->refs) == FALSE)
+ return;
+
+ attrib_destroy(attrib);
+}
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib)
+{
+ if (!attrib)
+ return NULL;
+
+ return attrib->io;
+}
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+ GAttribDisconnectFunc disconnect, gpointer user_data)
+{
+ if (attrib == NULL)
+ return FALSE;
+
+ attrib->disconnect = disconnect;
+ attrib->disc_user_data = user_data;
+
+ return TRUE;
+}
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+ GDestroyNotify destroy, gpointer user_data)
+{
+ if (attrib == NULL)
+ return FALSE;
+
+ attrib->destroy = destroy;
+ attrib->destroy_user_data = user_data;
+
+ return TRUE;
+}
+
+static gboolean disconnect_timeout(gpointer data)
+{
+ struct _GAttrib *attrib = data;
+
+ attrib_destroy(attrib);
+
+ return FALSE;
+}
+
+static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
+ gpointer data)
+{
+ struct _GAttrib *attrib = data;
+ struct command *cmd;
+ GError *gerr = NULL;
+ gsize len;
+ GIOStatus iostat;
+
+ if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+ if (attrib->disconnect)
+ attrib->disconnect(attrib->disc_user_data);
+
+ return FALSE;
+ }
+
+ cmd = g_queue_peek_head(attrib->queue);
+ if (cmd == NULL)
+ return FALSE;
+
+ iostat = g_io_channel_write_chars(io, (gchar *) cmd->pdu, cmd->len,
+ &len, &gerr);
+ if (iostat != G_IO_STATUS_NORMAL)
+ return FALSE;
+
+ if (cmd->expected == 0) {
+ g_queue_pop_head(attrib->queue);
+ command_destroy(cmd);
+
+ return TRUE;
+ }
+
+ cmd->sent = TRUE;
+
+ if (attrib->timeout_watch == 0)
+ attrib->timeout_watch = g_timeout_add_seconds(GATT_TIMEOUT,
+ disconnect_timeout, attrib);
+
+ return FALSE;
+}
+
+static void destroy_sender(gpointer data)
+{
+ struct _GAttrib *attrib = data;
+
+ attrib->write_watch = 0;
+}
+
+static void wake_up_sender(struct _GAttrib *attrib)
+{
+ if (attrib->write_watch == 0)
+ attrib->write_watch = g_io_add_watch_full(attrib->io,
+ G_PRIORITY_DEFAULT, G_IO_OUT, can_write_data,
+ attrib, destroy_sender);
+}
+
+static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
+{
+ struct _GAttrib *attrib = data;
+ struct command *cmd = NULL;
+ GSList *l;
+ uint8_t buf[512], status;
+ gsize len;
+ GIOStatus iostat;
+ gboolean qempty;
+
+ if (attrib->timeout_watch > 0) {
+ g_source_remove(attrib->timeout_watch);
+ attrib->timeout_watch = 0;
+ }
+
+ if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+ attrib->read_watch = 0;
+ if (attrib->disconnect)
+ attrib->disconnect(attrib->disc_user_data);
+ return FALSE;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ iostat = g_io_channel_read_chars(io, (gchar *) buf, sizeof(buf),
+ &len, NULL);
+ if (iostat != G_IO_STATUS_NORMAL) {
+ status = ATT_ECODE_IO;
+ goto done;
+ }
+
+ for (l = attrib->events; l; l = l->next) {
+ struct event *evt = l->data;
+
+ if (evt->expected == buf[0] ||
+ evt->expected == GATTRIB_ALL_EVENTS)
+ evt->func(buf, len, evt->user_data);
+ }
+
+ if (is_response(buf[0]) == FALSE)
+ return TRUE;
+
+ cmd = g_queue_pop_head(attrib->queue);
+ if (cmd == NULL) {
+ /* Keep the watch if we have events to report */
+ return attrib->events != NULL;
+ }
+
+ if (buf[0] == ATT_OP_ERROR) {
+ status = buf[4];
+ goto done;
+ }
+
+ if (cmd->expected != buf[0]) {
+ status = ATT_ECODE_IO;
+ goto done;
+ }
+
+ status = 0;
+
+done:
+ qempty = attrib->queue == NULL || g_queue_is_empty(attrib->queue);
+
+ if (cmd) {
+ if (cmd->func)
+ cmd->func(status, buf, len, cmd->user_data);
+
+ command_destroy(cmd);
+ }
+
+ if (!qempty)
+ wake_up_sender(attrib);
+
+ return TRUE;
+}
+
+GAttrib *g_attrib_new(GIOChannel *io)
+{
+ struct _GAttrib *attrib;
+ uint16_t omtu;
+
+ g_io_channel_set_encoding(io, NULL, NULL);
+ g_io_channel_set_buffered(io, FALSE);
+
+ attrib = g_try_new0(struct _GAttrib, 1);
+ if (attrib == NULL)
+ return NULL;
+
+ attrib->io = g_io_channel_ref(io);
+ attrib->queue = g_queue_new();
+
+ attrib->read_watch = g_io_add_watch(attrib->io,
+ G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ received_data, attrib);
+
+ if (bt_io_get(attrib->io, BT_IO_L2CAP, NULL,
+ BT_IO_OPT_OMTU, &omtu,
+ BT_IO_OPT_INVALID)) {
+ if (omtu > ATT_MAX_MTU)
+ omtu = ATT_MAX_MTU;
+ } else
+ omtu = ATT_DEFAULT_LE_MTU;
+
+ attrib->buf = g_malloc0(omtu);
+ attrib->buflen = omtu;
+
+ return g_attrib_ref(attrib);
+}
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+ const guint8 *pdu, guint16 len, GAttribResultFunc func,
+ gpointer user_data, GDestroyNotify notify)
+{
+ struct command *c;
+
+ c = g_try_new0(struct command, 1);
+ if (c == NULL)
+ return 0;
+
+ c->opcode = opcode;
+ c->expected = opcode2expected(opcode);
+ c->pdu = g_malloc(len);
+ memcpy(c->pdu, pdu, len);
+ c->len = len;
+ c->func = func;
+ c->user_data = user_data;
+ c->notify = notify;
+
+ if (id) {
+ c->id = id;
+ g_queue_push_head(attrib->queue, c);
+ } else {
+ c->id = ++attrib->next_cmd_id;
+ g_queue_push_tail(attrib->queue, c);
+ }
+
+ if (g_queue_get_length(attrib->queue) == 1)
+ wake_up_sender(attrib);
+
+ return c->id;
+}
+
+static gint command_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+ const struct command *cmd = a;
+ guint id = GPOINTER_TO_UINT(b);
+
+ return cmd->id - id;
+}
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id)
+{
+ GList *l;
+ struct command *cmd;
+
+ if (attrib == NULL || attrib->queue == NULL)
+ return FALSE;
+
+ l = g_queue_find_custom(attrib->queue, GUINT_TO_POINTER(id),
+ command_cmp_by_id);
+ if (l == NULL)
+ return FALSE;
+
+ cmd = l->data;
+
+ if (cmd == g_queue_peek_head(attrib->queue) && cmd->sent)
+ cmd->func = NULL;
+ else {
+ g_queue_remove(attrib->queue, cmd);
+ command_destroy(cmd);
+ }
+
+ return TRUE;
+}
+
+gboolean g_attrib_cancel_all(GAttrib *attrib)
+{
+ struct command *c, *head = NULL;
+ gboolean first = TRUE;
+
+ if (attrib == NULL || attrib->queue == NULL)
+ return FALSE;
+
+ while ((c = g_queue_pop_head(attrib->queue))) {
+ if (first && c->sent) {
+ /* If the command was sent ignore its callback ... */
+ c->func = NULL;
+ head = c;
+ continue;
+ }
+
+ first = FALSE;
+ command_destroy(c);
+ }
+
+ if (head) {
+ /* ... and put it back in the queue */
+ g_queue_push_head(attrib->queue, head);
+ }
+
+ return TRUE;
+}
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+ GAttribDebugFunc func, gpointer user_data)
+{
+ return TRUE;
+}
+
+uint8_t *g_attrib_get_buffer(GAttrib *attrib, int *len)
+{
+ if (len == NULL)
+ return NULL;
+
+ *len = attrib->buflen;
+
+ return attrib->buf;
+}
+
+gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu)
+{
+ if (mtu < ATT_DEFAULT_LE_MTU)
+ mtu = ATT_DEFAULT_LE_MTU;
+
+ if (mtu > ATT_MAX_MTU)
+ mtu = ATT_MAX_MTU;
+
+ if (!bt_io_set(attrib->io, BT_IO_L2CAP, NULL,
+ BT_IO_OPT_OMTU, mtu,
+ BT_IO_OPT_INVALID))
+ return FALSE;
+
+ attrib->buf = g_realloc(attrib->buf, mtu);
+
+ attrib->buflen = mtu;
+
+ return TRUE;
+}
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+ GAttribNotifyFunc func, gpointer user_data,
+ GDestroyNotify notify)
+{
+ struct event *event;
+
+ event = g_try_new0(struct event, 1);
+ if (event == NULL)
+ return 0;
+
+ event->expected = opcode;
+ event->func = func;
+ event->user_data = user_data;
+ event->notify = notify;
+ event->id = ++attrib->next_evt_id;
+
+ attrib->events = g_slist_append(attrib->events, event);
+
+ return event->id;
+}
+
+static gint event_cmp_by_id(gconstpointer a, gconstpointer b)
+{
+ const struct event *evt = a;
+ guint id = GPOINTER_TO_UINT(b);
+
+ return evt->id - id;
+}
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib)
+{
+ BtIOSecLevel sec_level;
+
+ if (!bt_io_get(attrib->io, BT_IO_L2CAP, NULL,
+ BT_IO_OPT_SEC_LEVEL, &sec_level,
+ BT_IO_OPT_INVALID))
+ return FALSE;
+
+ return sec_level > BT_IO_SEC_LOW;
+}
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id)
+{
+ struct event *evt;
+ GSList *l;
+
+ l = g_slist_find_custom(attrib->events, GUINT_TO_POINTER(id),
+ event_cmp_by_id);
+ if (l == NULL)
+ return FALSE;
+
+ evt = l->data;
+
+ attrib->events = g_slist_remove(attrib->events, evt);
+
+ if (evt->notify)
+ evt->notify(evt->user_data);
+
+ g_free(evt);
+
+ return TRUE;
+}
+
+gboolean g_attrib_unregister_all(GAttrib *attrib)
+{
+ GSList *l;
+
+ if (attrib->events == NULL)
+ return FALSE;
+
+ for (l = attrib->events; l; l = l->next) {
+ struct event *evt = l->data;
+
+ if (evt->notify)
+ evt->notify(evt->user_data);
+
+ g_free(evt);
+ }
+
+ g_slist_free(attrib->events);
+ attrib->events = NULL;
+
+ return TRUE;
+}
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
new file mode 100644
index 0000000..4c49879
--- /dev/null
+++ b/attrib/gattrib.h
@@ -0,0 +1,80 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 __GATTRIB_H
+#define __GATTRIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GATTRIB_ALL_EVENTS 0xFF
+
+struct _GAttrib;
+typedef struct _GAttrib GAttrib;
+
+typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data);
+typedef void (*GAttribDisconnectFunc)(gpointer user_data);
+typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
+typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,
+ gpointer user_data);
+
+GAttrib *g_attrib_new(GIOChannel *io);
+GAttrib *g_attrib_ref(GAttrib *attrib);
+void g_attrib_unref(GAttrib *attrib);
+
+GIOChannel *g_attrib_get_channel(GAttrib *attrib);
+
+gboolean g_attrib_set_disconnect_function(GAttrib *attrib,
+ GAttribDisconnectFunc disconnect, gpointer user_data);
+
+gboolean g_attrib_set_destroy_function(GAttrib *attrib,
+ GDestroyNotify destroy, gpointer user_data);
+
+guint g_attrib_send(GAttrib *attrib, guint id, guint8 opcode,
+ const guint8 *pdu, guint16 len, GAttribResultFunc func,
+ gpointer user_data, GDestroyNotify notify);
+
+gboolean g_attrib_cancel(GAttrib *attrib, guint id);
+gboolean g_attrib_cancel_all(GAttrib *attrib);
+
+gboolean g_attrib_set_debug(GAttrib *attrib,
+ GAttribDebugFunc func, gpointer user_data);
+
+guint g_attrib_register(GAttrib *attrib, guint8 opcode,
+ GAttribNotifyFunc func, gpointer user_data,
+ GDestroyNotify notify);
+
+gboolean g_attrib_is_encrypted(GAttrib *attrib);
+
+uint8_t *g_attrib_get_buffer(GAttrib *attrib, int *len);
+gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu);
+
+gboolean g_attrib_unregister(GAttrib *attrib, guint id);
+gboolean g_attrib_unregister_all(GAttrib *attrib);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/attrib/gatttool.c b/attrib/gatttool.c
new file mode 100644
index 0000000..0dfbc04
--- /dev/null
+++ b/attrib/gatttool.c
@@ -0,0 +1,634 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 <glib.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/uuid.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_value = NULL;
+static gchar *opt_sec_level = NULL;
+static bt_uuid_t *opt_uuid = NULL;
+static int opt_start = 0x0001;
+static int opt_end = 0xffff;
+static int opt_handle = -1;
+static int opt_mtu = 0;
+static int opt_psm = 0;
+static int opt_offset = 0;
+static gboolean opt_primary = FALSE;
+static gboolean opt_characteristics = FALSE;
+static gboolean opt_char_read = FALSE;
+static gboolean opt_listen = FALSE;
+static gboolean opt_char_desc = FALSE;
+static gboolean opt_char_write = FALSE;
+static gboolean opt_char_write_req = FALSE;
+static gboolean opt_interactive = FALSE;
+static GMainLoop *event_loop;
+static gboolean got_error = FALSE;
+
+struct characteristic_data {
+ GAttrib *attrib;
+ uint16_t start;
+ uint16_t end;
+};
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+ if (err) {
+ g_printerr("%s\n", err->message);
+ got_error = TRUE;
+ g_main_loop_quit(event_loop);
+ }
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+ GSList *l;
+
+ if (status) {
+ g_printerr("Discover all primary services failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+
+ for (l = services; l; l = l->next) {
+ struct att_primary *prim = l->data;
+ g_print("attr handle = 0x%04x, end grp handle = 0x%04x "
+ "uuid: %s\n", prim->start, prim->end, prim->uuid);
+ }
+
+done:
+ g_main_loop_quit(event_loop);
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+ gpointer user_data)
+{
+ GSList *l;
+
+ if (status != 0) {
+ g_printerr("Discover primary services by UUID failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+
+ for (l = ranges; l; l = l->next) {
+ struct att_range *range = l->data;
+ g_print("Starting handle: %04x Ending handle: %04x\n",
+ range->start, range->end);
+ }
+
+done:
+ g_main_loop_quit(event_loop);
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+ uint8_t opdu[ATT_MAX_MTU];
+ uint16_t handle, i, olen = 0;
+
+ handle = att_get_u16(&pdu[1]);
+
+ switch (pdu[0]) {
+ case ATT_OP_HANDLE_NOTIFY:
+ g_print("Notification handle = 0x%04x value: ", handle);
+ break;
+ case ATT_OP_HANDLE_IND:
+ g_print("Indication handle = 0x%04x value: ", handle);
+ break;
+ default:
+ g_print("Invalid opcode\n");
+ return;
+ }
+
+ for (i = 3; i < len; i++)
+ g_print("%02x ", pdu[i]);
+
+ g_print("\n");
+
+ if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+ return;
+
+ olen = enc_confirmation(opdu, sizeof(opdu));
+
+ if (olen > 0)
+ g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
+}
+
+static gboolean listen_start(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+
+ g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler,
+ attrib, NULL);
+ g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler,
+ attrib, NULL);
+
+ return FALSE;
+}
+
+static gboolean primary(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+
+ if (opt_uuid)
+ gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb,
+ NULL);
+ else
+ gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+
+ return FALSE;
+}
+
+static void char_discovered_cb(GSList *characteristics, guint8 status,
+ gpointer user_data)
+{
+ GSList *l;
+
+ if (status) {
+ g_printerr("Discover all characteristics failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+
+ for (l = characteristics; l; l = l->next) {
+ struct att_char *chars = l->data;
+
+ g_print("handle = 0x%04x, char properties = 0x%02x, char value "
+ "handle = 0x%04x, uuid = %s\n", chars->handle,
+ chars->properties, chars->value_handle, chars->uuid);
+ }
+
+done:
+ g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+
+ gatt_discover_char(attrib, opt_start, opt_end, opt_uuid,
+ char_discovered_cb, NULL);
+
+ return FALSE;
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ uint8_t value[ATT_MAX_MTU];
+ int i, vlen;
+
+ if (status != 0) {
+ g_printerr("Characteristic value/descriptor read failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+ if (!dec_read_resp(pdu, plen, value, &vlen)) {
+ g_printerr("Protocol error\n");
+ goto done;
+ }
+ g_print("Characteristic value/descriptor: ");
+ for (i = 0; i < vlen; i++)
+ g_print("%02x ", value[i]);
+ g_print("\n");
+
+done:
+ if (opt_listen == FALSE)
+ g_main_loop_quit(event_loop);
+}
+
+static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct characteristic_data *char_data = user_data;
+ struct att_data_list *list;
+ int i;
+
+ if (status == ATT_ECODE_ATTR_NOT_FOUND &&
+ char_data->start != opt_start)
+ goto done;
+
+ if (status != 0) {
+ g_printerr("Read characteristics by UUID failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+
+ list = dec_read_by_type_resp(pdu, plen);
+ if (list == NULL)
+ goto done;
+
+ for (i = 0; i < list->num; i++) {
+ uint8_t *value = list->data[i];
+ int j;
+
+ char_data->start = att_get_u16(value) + 1;
+
+ g_print("handle: 0x%04x \t value: ", att_get_u16(value));
+ value += 2;
+ for (j = 0; j < list->len - 2; j++, value++)
+ g_print("%02x ", *value);
+ g_print("\n");
+ }
+
+ att_data_list_free(list);
+
+ gatt_read_char_by_uuid(char_data->attrib, char_data->start,
+ char_data->end, opt_uuid,
+ char_read_by_uuid_cb,
+ char_data);
+
+ return;
+done:
+ g_free(char_data);
+ g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_read(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+
+ if (opt_uuid != NULL) {
+ struct characteristic_data *char_data;
+
+ char_data = g_new(struct characteristic_data, 1);
+ char_data->attrib = attrib;
+ char_data->start = opt_start;
+ char_data->end = opt_end;
+
+ gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid,
+ char_read_by_uuid_cb, char_data);
+
+ return FALSE;
+ }
+
+ if (opt_handle <= 0) {
+ g_printerr("A valid handle is required\n");
+ g_main_loop_quit(event_loop);
+ return FALSE;
+ }
+
+ gatt_read_char(attrib, opt_handle, opt_offset, char_read_cb, attrib);
+
+ return FALSE;
+}
+
+static void mainloop_quit(gpointer user_data)
+{
+ uint8_t *value = user_data;
+
+ g_free(value);
+ g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+ uint8_t *value;
+ size_t len;
+
+ if (opt_handle <= 0) {
+ g_printerr("A valid handle is required\n");
+ goto error;
+ }
+
+ if (opt_value == NULL || opt_value[0] == '\0') {
+ g_printerr("A value is required\n");
+ goto error;
+ }
+
+ len = gatt_attr_data_from_string(opt_value, &value);
+ if (len == 0) {
+ g_printerr("Invalid value\n");
+ goto error;
+ }
+
+ gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value);
+
+ return FALSE;
+
+error:
+ g_main_loop_quit(event_loop);
+ return FALSE;
+}
+
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ if (status != 0) {
+ g_printerr("Characteristic Write Request failed: "
+ "%s\n", att_ecode2str(status));
+ goto done;
+ }
+
+ if (!dec_write_resp(pdu, plen)) {
+ g_printerr("Protocol error\n");
+ goto done;
+ }
+
+ g_print("Characteristic value was written sucessfully\n");
+
+done:
+ if (opt_listen == FALSE)
+ g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_write_req(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+ uint8_t *value;
+ size_t len;
+
+ if (opt_handle <= 0) {
+ g_printerr("A valid handle is required\n");
+ goto error;
+ }
+
+ if (opt_value == NULL || opt_value[0] == '\0') {
+ g_printerr("A value is required\n");
+ goto error;
+ }
+
+ len = gatt_attr_data_from_string(opt_value, &value);
+ if (len == 0) {
+ g_printerr("Invalid value\n");
+ goto error;
+ }
+
+ gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb,
+ NULL);
+
+ return FALSE;
+
+error:
+ g_main_loop_quit(event_loop);
+ return FALSE;
+}
+
+static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct att_data_list *list;
+ guint8 format;
+ int i;
+
+ if (status != 0) {
+ g_printerr("Discover all characteristic descriptors failed: "
+ "%s\n", att_ecode2str(status));
+ goto done;
+ }
+
+ list = dec_find_info_resp(pdu, plen, &format);
+ if (list == NULL)
+ goto done;
+
+ for (i = 0; i < list->num; i++) {
+ char uuidstr[MAX_LEN_UUID_STR];
+ uint16_t handle;
+ uint8_t *value;
+ bt_uuid_t uuid;
+
+ value = list->data[i];
+ handle = att_get_u16(value);
+
+ if (format == 0x01)
+ uuid = att_get_uuid16(&value[2]);
+ else
+ uuid = att_get_uuid128(&value[2]);
+
+ bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR);
+ g_print("handle = 0x%04x, uuid = %s\n", handle, uuidstr);
+ }
+
+ att_data_list_free(list);
+
+done:
+ if (opt_listen == FALSE)
+ g_main_loop_quit(event_loop);
+}
+
+static gboolean characteristics_desc(gpointer user_data)
+{
+ GAttrib *attrib = user_data;
+
+ gatt_find_info(attrib, opt_start, opt_end, char_desc_cb, NULL);
+
+ return FALSE;
+}
+
+static gboolean parse_uuid(const char *key, const char *value,
+ gpointer user_data, GError **error)
+{
+ if (!value)
+ return FALSE;
+
+ opt_uuid = g_try_malloc(sizeof(bt_uuid_t));
+ if (opt_uuid == NULL)
+ return FALSE;
+
+ if (bt_string_to_uuid(opt_uuid, value) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static GOptionEntry primary_char_options[] = {
+ { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start,
+ "Starting handle(optional)", "0x0001" },
+ { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end,
+ "Ending handle(optional)", "0xffff" },
+ { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
+ parse_uuid, "UUID16 or UUID128(optional)", "0x1801"},
+ { NULL },
+};
+
+static GOptionEntry char_rw_options[] = {
+ { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle,
+ "Read/Write characteristic by handle(required)", "0x0001" },
+ { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value,
+ "Write characteristic value (required for write operation)",
+ "0x0001" },
+ { "offset", 'o', 0, G_OPTION_ARG_INT, &opt_offset,
+ "Offset to long read characteristic by handle", "N"},
+ {NULL},
+};
+
+static GOptionEntry gatt_options[] = {
+ { "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary,
+ "Primary Service Discovery", NULL },
+ { "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics,
+ "Characteristics Discovery", NULL },
+ { "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read,
+ "Characteristics Value/Descriptor Read", NULL },
+ { "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write,
+ "Characteristics Value Write Without Response (Write Command)",
+ NULL },
+ { "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req,
+ "Characteristics Value Write (Write Request)", NULL },
+ { "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc,
+ "Characteristics Descriptor Discovery", NULL },
+ { "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen,
+ "Listen for notifications and indications", NULL },
+ { "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,
+ &opt_interactive, "Use interactive mode", NULL },
+ { NULL },
+};
+
+static GOptionEntry options[] = {
+ { "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src,
+ "Specify local adapter interface", "hciX" },
+ { "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst,
+ "Specify remote Bluetooth address", "MAC" },
+ { "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu,
+ "Specify the MTU size", "MTU" },
+ { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
+ "Specify the PSM for GATT/ATT over BR/EDR", "PSM" },
+ { "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level,
+ "Set security level. Default: low", "[low | medium | high]"},
+ { NULL },
+};
+
+int main(int argc, char *argv[])
+{
+ GOptionContext *context;
+ GOptionGroup *gatt_group, *params_group, *char_rw_group;
+ GError *gerr = NULL;
+ GAttrib *attrib;
+ GIOChannel *chan;
+ GSourceFunc callback;
+
+ opt_sec_level = g_strdup("low");
+
+ context = g_option_context_new(NULL);
+ g_option_context_add_main_entries(context, options, NULL);
+
+ /* GATT commands */
+ gatt_group = g_option_group_new("gatt", "GATT commands",
+ "Show all GATT commands", NULL, NULL);
+ g_option_context_add_group(context, gatt_group);
+ g_option_group_add_entries(gatt_group, gatt_options);
+
+ /* Primary Services and Characteristics arguments */
+ params_group = g_option_group_new("params",
+ "Primary Services/Characteristics arguments",
+ "Show all Primary Services/Characteristics arguments",
+ NULL, NULL);
+ g_option_context_add_group(context, params_group);
+ g_option_group_add_entries(params_group, primary_char_options);
+
+ /* Characteristics value/descriptor read/write arguments */
+ char_rw_group = g_option_group_new("char-read-write",
+ "Characteristics Value/Descriptor Read/Write arguments",
+ "Show all Characteristics Value/Descriptor Read/Write "
+ "arguments",
+ NULL, NULL);
+ g_option_context_add_group(context, char_rw_group);
+ g_option_group_add_entries(char_rw_group, char_rw_options);
+
+ if (g_option_context_parse(context, &argc, &argv, &gerr) == FALSE) {
+ g_printerr("%s\n", gerr->message);
+ g_error_free(gerr);
+ }
+
+ if (opt_interactive) {
+ interactive(opt_src, opt_dst, opt_psm);
+ goto done;
+ }
+
+ if (opt_primary)
+ callback = primary;
+ else if (opt_characteristics)
+ callback = characteristics;
+ else if (opt_char_read)
+ callback = characteristics_read;
+ else if (opt_char_write)
+ callback = characteristics_write;
+ else if (opt_char_write_req)
+ callback = characteristics_write_req;
+ else if (opt_char_desc)
+ callback = characteristics_desc;
+ else {
+ gchar *help = g_option_context_get_help(context, TRUE, NULL);
+ g_print("%s\n", help);
+ g_free(help);
+ got_error = TRUE;
+ goto done;
+ }
+
+ chan = gatt_connect(opt_src, opt_dst, opt_sec_level,
+ opt_psm, opt_mtu, connect_cb);
+ if (chan == NULL) {
+ got_error = TRUE;
+ goto done;
+ }
+
+ attrib = g_attrib_new(chan);
+ g_io_channel_unref(chan);
+
+ event_loop = g_main_loop_new(NULL, FALSE);
+
+ if (opt_listen)
+ g_idle_add(listen_start, attrib);
+
+ g_idle_add(callback, attrib);
+
+ g_main_loop_run(event_loop);
+
+ g_attrib_unregister_all(attrib);
+
+ g_main_loop_unref(event_loop);
+
+ g_attrib_unref(attrib);
+
+done:
+ g_option_context_free(context);
+ g_free(opt_src);
+ g_free(opt_dst);
+ g_free(opt_uuid);
+ g_free(opt_sec_level);
+
+ if (got_error)
+ exit(EXIT_FAILURE);
+ else
+ exit(EXIT_SUCCESS);
+}
diff --git a/attrib/gatttool.h b/attrib/gatttool.h
new file mode 100644
index 0000000..89ac282
--- /dev/null
+++ b/attrib/gatttool.h
@@ -0,0 +1,28 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ *
+ * 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
+ *
+ */
+
+int interactive(const gchar *src, const gchar *dst, gboolean le);
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+ const gchar *sec_level, int psm, int mtu,
+ BtIOConnect connect_cb);
+size_t gatt_attr_data_from_string(const char *str, uint8_t **data);
diff --git a/attrib/interactive.c b/attrib/interactive.c
new file mode 100644
index 0000000..3fafb1e
--- /dev/null
+++ b/attrib/interactive.c
@@ -0,0 +1,842 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ *
+ * 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
+ *
+ */
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include <bluetooth/uuid.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "att.h"
+#include "btio.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "gatttool.h"
+
+static GIOChannel *iochannel = NULL;
+static GAttrib *attrib = NULL;
+static GMainLoop *event_loop;
+static GString *prompt;
+
+static gchar *opt_src = NULL;
+static gchar *opt_dst = NULL;
+static gchar *opt_sec_level = NULL;
+static int opt_psm = 0;
+static int opt_mtu = 0;
+
+struct characteristic_data {
+ uint16_t orig_start;
+ uint16_t start;
+ uint16_t end;
+ bt_uuid_t uuid;
+};
+
+static void cmd_help(int argcp, char **argvp);
+
+enum state {
+ STATE_DISCONNECTED,
+ STATE_CONNECTING,
+ STATE_CONNECTED
+} conn_state;
+
+static char *get_prompt(void)
+{
+ if (conn_state == STATE_CONNECTING) {
+ g_string_assign(prompt, "Connecting... ");
+ return prompt->str;
+ }
+
+ if (conn_state == STATE_CONNECTED)
+ g_string_assign(prompt, "[CON]");
+ else
+ g_string_assign(prompt, "[ ]");
+
+ if (opt_dst)
+ g_string_append_printf(prompt, "[%17s]", opt_dst);
+ else
+ g_string_append_printf(prompt, "[%17s]", "");
+
+ if (opt_psm)
+ g_string_append(prompt, "[BR]");
+ else
+ g_string_append(prompt, "[LE]");
+
+ g_string_append(prompt, "> ");
+
+ return prompt->str;
+}
+
+
+static void set_state(enum state st)
+{
+ conn_state = st;
+ rl_set_prompt(get_prompt());
+ rl_redisplay();
+}
+
+static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ uint8_t opdu[ATT_MAX_MTU];
+ uint16_t handle, i, olen;
+
+ handle = att_get_u16(&pdu[1]);
+
+ printf("\n");
+ switch (pdu[0]) {
+ case ATT_OP_HANDLE_NOTIFY:
+ printf("Notification handle = 0x%04x value: ", handle);
+ break;
+ case ATT_OP_HANDLE_IND:
+ printf("Indication handle = 0x%04x value: ", handle);
+ break;
+ default:
+ printf("Invalid opcode\n");
+ return;
+ }
+
+ for (i = 3; i < len; i++)
+ printf("%02x ", pdu[i]);
+
+ printf("\n");
+ rl_forced_update_display();
+
+ if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+ return;
+
+ olen = enc_confirmation(opdu, sizeof(opdu));
+
+ if (olen > 0)
+ g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
+}
+
+static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
+{
+ if (err) {
+ printf("connect error: %s\n", err->message);
+ set_state(STATE_DISCONNECTED);
+ return;
+ }
+
+ attrib = g_attrib_new(iochannel);
+ g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, events_handler,
+ attrib, NULL);
+ g_attrib_register(attrib, ATT_OP_HANDLE_IND, events_handler,
+ attrib, NULL);
+ set_state(STATE_CONNECTED);
+}
+
+static void primary_all_cb(GSList *services, guint8 status, gpointer user_data)
+{
+ GSList *l;
+
+ if (status) {
+ printf("Discover all primary services failed: %s\n",
+ att_ecode2str(status));
+ return;
+ }
+
+ printf("\n");
+ for (l = services; l; l = l->next) {
+ struct att_primary *prim = l->data;
+ printf("attr handle: 0x%04x, end grp handle: 0x%04x "
+ "uuid: %s\n", prim->start, prim->end, prim->uuid);
+ }
+
+ rl_forced_update_display();
+}
+
+static void primary_by_uuid_cb(GSList *ranges, guint8 status,
+ gpointer user_data)
+{
+ GSList *l;
+
+ if (status) {
+ printf("Discover primary services by UUID failed: %s\n",
+ att_ecode2str(status));
+ return;
+ }
+
+ printf("\n");
+ for (l = ranges; l; l = l->next) {
+ struct att_range *range = l->data;
+ g_print("Starting handle: 0x%04x Ending handle: 0x%04x\n",
+ range->start, range->end);
+ }
+
+ rl_forced_update_display();
+}
+
+static void char_cb(GSList *characteristics, guint8 status, gpointer user_data)
+{
+ GSList *l;
+
+ if (status) {
+ printf("Discover all characteristics failed: %s\n",
+ att_ecode2str(status));
+ return;
+ }
+
+ printf("\n");
+ for (l = characteristics; l; l = l->next) {
+ struct att_char *chars = l->data;
+
+ printf("handle: 0x%04x, char properties: 0x%02x, char value "
+ "handle: 0x%04x, uuid: %s\n", chars->handle,
+ chars->properties, chars->value_handle,
+ chars->uuid);
+ }
+
+ rl_forced_update_display();
+}
+
+static void char_desc_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct att_data_list *list;
+ guint8 format;
+ int i;
+
+ if (status != 0) {
+ printf("Discover all characteristic descriptors failed: "
+ "%s\n", att_ecode2str(status));
+ return;
+ }
+
+ list = dec_find_info_resp(pdu, plen, &format);
+ if (list == NULL)
+ return;
+
+ printf("\n");
+ for (i = 0; i < list->num; i++) {
+ char uuidstr[MAX_LEN_UUID_STR];
+ uint16_t handle;
+ uint8_t *value;
+ bt_uuid_t uuid;
+
+ value = list->data[i];
+ handle = att_get_u16(value);
+
+ if (format == 0x01)
+ uuid = att_get_uuid16(&value[2]);
+ else
+ uuid = att_get_uuid128(&value[2]);
+
+ bt_uuid_to_string(&uuid, uuidstr, MAX_LEN_UUID_STR);
+ printf("handle: 0x%04x, uuid: %s\n", handle, uuidstr);
+ }
+
+ att_data_list_free(list);
+
+ rl_forced_update_display();
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ uint8_t value[ATT_MAX_MTU];
+ int i, vlen;
+
+ if (status != 0) {
+ printf("Characteristic value/descriptor read failed: %s\n",
+ att_ecode2str(status));
+ return;
+ }
+
+ if (!dec_read_resp(pdu, plen, value, &vlen)) {
+ printf("Protocol error\n");
+ return;
+ }
+
+ printf("\nCharacteristic value/descriptor: ");
+ for (i = 0; i < vlen; i++)
+ printf("%02x ", value[i]);
+ printf("\n");
+
+ rl_forced_update_display();
+}
+
+static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ struct characteristic_data *char_data = user_data;
+ struct att_data_list *list;
+ int i;
+
+ if (status == ATT_ECODE_ATTR_NOT_FOUND &&
+ char_data->start != char_data->orig_start)
+ goto done;
+
+ if (status != 0) {
+ printf("Read characteristics by UUID failed: %s\n",
+ att_ecode2str(status));
+ goto done;
+ }
+
+ list = dec_read_by_type_resp(pdu, plen);
+ if (list == NULL)
+ goto done;
+
+ for (i = 0; i < list->num; i++) {
+ uint8_t *value = list->data[i];
+ int j;
+
+ char_data->start = att_get_u16(value) + 1;
+
+ printf("\nhandle: 0x%04x \t value: ", att_get_u16(value));
+ value += 2;
+ for (j = 0; j < list->len - 2; j++, value++)
+ printf("%02x ", *value);
+ printf("\n");
+ }
+
+ att_data_list_free(list);
+
+ gatt_read_char_by_uuid(attrib, char_data->start, char_data->end,
+ &char_data->uuid, char_read_by_uuid_cb,
+ char_data);
+
+ rl_forced_update_display();
+
+ return;
+
+done:
+ g_free(char_data);
+}
+
+static void cmd_exit(int argcp, char **argvp)
+{
+ rl_callback_handler_remove();
+ g_main_loop_quit(event_loop);
+}
+
+static void cmd_connect(int argcp, char **argvp)
+{
+ if (conn_state != STATE_DISCONNECTED)
+ return;
+
+ if (argcp > 1) {
+ g_free(opt_dst);
+ opt_dst = g_strdup(argvp[1]);
+ }
+
+ if (opt_dst == NULL) {
+ printf("Remote Bluetooth address required\n");
+ return;
+ }
+
+ set_state(STATE_CONNECTING);
+ iochannel = gatt_connect(opt_src, opt_dst, opt_sec_level, opt_psm,
+ opt_mtu, connect_cb);
+ if (iochannel == NULL)
+ set_state(STATE_DISCONNECTED);
+}
+
+static void cmd_disconnect(int argcp, char **argvp)
+{
+ if (conn_state == STATE_DISCONNECTED)
+ return;
+
+ g_attrib_unref(attrib);
+ attrib = NULL;
+ opt_mtu = 0;
+
+ g_io_channel_shutdown(iochannel, FALSE, NULL);
+ g_io_channel_unref(iochannel);
+ iochannel = NULL;
+
+ set_state(STATE_DISCONNECTED);
+}
+
+static void cmd_primary(int argcp, char **argvp)
+{
+ bt_uuid_t uuid;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp == 1) {
+ gatt_discover_primary(attrib, NULL, primary_all_cb, NULL);
+ return;
+ }
+
+ if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
+ printf("Invalid UUID\n");
+ return;
+ }
+
+ gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL);
+}
+
+static int strtohandle(const char *src)
+{
+ char *e;
+ int dst;
+
+ errno = 0;
+ dst = strtoll(src, &e, 16);
+ if (errno != 0 || *e != '\0')
+ return -EINVAL;
+
+ return dst;
+}
+
+static void cmd_char(int argcp, char **argvp)
+{
+ int start = 0x0001;
+ int end = 0xffff;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp > 1) {
+ start = strtohandle(argvp[1]);
+ if (start < 0) {
+ printf("Invalid start handle: %s\n", argvp[1]);
+ return;
+ }
+ }
+
+ if (argcp > 2) {
+ end = strtohandle(argvp[2]);
+ if (end < 0) {
+ printf("Invalid end handle: %s\n", argvp[2]);
+ return;
+ }
+ }
+
+ if (argcp > 3) {
+ bt_uuid_t uuid;
+
+ if (bt_string_to_uuid(&uuid, argvp[3]) < 0) {
+ printf("Invalid UUID\n");
+ return;
+ }
+
+ gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL);
+ return;
+ }
+
+ gatt_discover_char(attrib, start, end, NULL, char_cb, NULL);
+}
+
+static void cmd_char_desc(int argcp, char **argvp)
+{
+ int start = 0x0001;
+ int end = 0xffff;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp > 1) {
+ start = strtohandle(argvp[1]);
+ if (start < 0) {
+ printf("Invalid start handle: %s\n", argvp[1]);
+ return;
+ }
+ }
+
+ if (argcp > 2) {
+ end = strtohandle(argvp[2]);
+ if (end < 0) {
+ printf("Invalid end handle: %s\n", argvp[2]);
+ return;
+ }
+ }
+
+ gatt_find_info(attrib, start, end, char_desc_cb, NULL);
+}
+
+static void cmd_read_hnd(int argcp, char **argvp)
+{
+ int handle;
+ int offset = 0;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp < 2) {
+ printf("Missing argument: handle\n");
+ return;
+ }
+
+ handle = strtohandle(argvp[1]);
+ if (handle < 0) {
+ printf("Invalid handle: %s\n", argvp[1]);
+ return;
+ }
+
+ if (argcp > 2) {
+ char *e;
+
+ errno = 0;
+ offset = strtol(argvp[2], &e, 0);
+ if (errno != 0 || *e != '\0') {
+ printf("Invalid offset: %s\n", argvp[2]);
+ return;
+ }
+ }
+
+ gatt_read_char(attrib, handle, offset, char_read_cb, attrib);
+}
+
+static void cmd_read_uuid(int argcp, char **argvp)
+{
+ struct characteristic_data *char_data;
+ int start = 0x0001;
+ int end = 0xffff;
+ bt_uuid_t uuid;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp < 2) {
+ printf("Missing argument: UUID\n");
+ return;
+ }
+
+ if (bt_string_to_uuid(&uuid, argvp[1]) < 0) {
+ printf("Invalid UUID\n");
+ return;
+ }
+
+ if (argcp > 2) {
+ start = strtohandle(argvp[2]);
+ if (start < 0) {
+ printf("Invalid start handle: %s\n", argvp[1]);
+ return;
+ }
+ }
+
+ if (argcp > 3) {
+ end = strtohandle(argvp[3]);
+ if (end < 0) {
+ printf("Invalid end handle: %s\n", argvp[2]);
+ return;
+ }
+ }
+
+ char_data = g_new(struct characteristic_data, 1);
+ char_data->orig_start = start;
+ char_data->start = start;
+ char_data->end = end;
+ char_data->uuid = uuid;
+
+ gatt_read_char_by_uuid(attrib, start, end, &char_data->uuid,
+ char_read_by_uuid_cb, char_data);
+}
+
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ if (status != 0) {
+ printf("Characteristic Write Request failed: "
+ "%s\n", att_ecode2str(status));
+ return;
+ }
+
+ if (!dec_write_resp(pdu, plen)) {
+ printf("Protocol error\n");
+ return;
+ }
+
+ printf("Characteristic value was written successfully\n");
+}
+
+static void cmd_char_write(int argcp, char **argvp)
+{
+ uint8_t *value;
+ size_t plen;
+ int handle;
+
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: disconnected\n");
+ return;
+ }
+
+ if (argcp < 3) {
+ printf("Usage: %s <handle> <new value>\n", argvp[0]);
+ return;
+ }
+
+ handle = strtoll(argvp[1], NULL, 16);
+ if (errno != 0 || handle <= 0) {
+ printf("A valid handle is required\n");
+ return;
+ }
+
+ plen = gatt_attr_data_from_string(argvp[2], &value);
+ if (plen == 0) {
+ g_printerr("Invalid value\n");
+ return;
+ }
+
+ if (g_strcmp0("char-write-req", argvp[0]) == 0)
+ gatt_write_char(attrib, handle, value, plen,
+ char_write_req_cb, NULL);
+ else
+ gatt_write_char(attrib, handle, value, plen, NULL, NULL);
+
+ g_free(value);
+}
+
+static void cmd_sec_level(int argcp, char **argvp)
+{
+ GError *gerr = NULL;
+ BtIOSecLevel sec_level;
+
+ if (argcp < 2) {
+ printf("sec-level: %s\n", opt_sec_level);
+ return;
+ }
+
+ if (strcasecmp(argvp[1], "medium") == 0)
+ sec_level = BT_IO_SEC_MEDIUM;
+ else if (strcasecmp(argvp[1], "high") == 0)
+ sec_level = BT_IO_SEC_HIGH;
+ else if (strcasecmp(argvp[1], "low") == 0)
+ sec_level = BT_IO_SEC_LOW;
+ else {
+ printf("Allowed values: low | medium | high\n");
+ return;
+ }
+
+ g_free(opt_sec_level);
+ opt_sec_level = g_strdup(argvp[1]);
+
+ if (conn_state != STATE_CONNECTED)
+ return;
+
+ if (opt_psm) {
+ printf("It must be reconnected to this change take effect\n");
+ return;
+ }
+
+ bt_io_set(iochannel, BT_IO_L2CAP, &gerr,
+ BT_IO_OPT_SEC_LEVEL, sec_level,
+ BT_IO_OPT_INVALID);
+
+ if (gerr) {
+ printf("Error: %s\n", gerr->message);
+ g_error_free(gerr);
+ }
+}
+
+static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ uint16_t mtu;
+
+ if (status != 0) {
+ printf("Exchange MTU Request failed: %s\n",
+ att_ecode2str(status));
+ return;
+ }
+
+ if (!dec_mtu_resp(pdu, plen, &mtu)) {
+ printf("Protocol error\n");
+ return;
+ }
+
+ mtu = MIN(mtu, opt_mtu);
+ /* Set new value for MTU in client */
+ if (g_attrib_set_mtu(attrib, mtu))
+ printf("MTU was exchanged successfully: %d\n", mtu);
+ else
+ printf("Error exchanging MTU\n");
+}
+
+static void cmd_mtu(int argcp, char **argvp)
+{
+ if (conn_state != STATE_CONNECTED) {
+ printf("Command failed: not connected.\n");
+ return;
+ }
+
+ if (opt_psm) {
+ printf("Command failed: operation is only available for LE"
+ " transport.\n");
+ return;
+ }
+
+ if (argcp < 2) {
+ printf("Usage: mtu <value>\n");
+ return;
+ }
+
+ if (opt_mtu) {
+ printf("Command failed: MTU exchange can only occur once per"
+ " connection.\n");
+ return;
+ }
+
+ errno = 0;
+ opt_mtu = strtoll(argvp[1], NULL, 0);
+ if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) {
+ printf("Invalid value. Minimum MTU size is %d\n",
+ ATT_DEFAULT_LE_MTU);
+ return;
+ }
+
+ gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL);
+}
+
+static struct {
+ const char *cmd;
+ void (*func)(int argcp, char **argvp);
+ const char *params;
+ const char *desc;
+} commands[] = {
+ { "help", cmd_help, "",
+ "Show this help"},
+ { "exit", cmd_exit, "",
+ "Exit interactive mode" },
+ { "connect", cmd_connect, "[address]",
+ "Connect to a remote device" },
+ { "disconnect", cmd_disconnect, "",
+ "Disconnect from a remote device" },
+ { "primary", cmd_primary, "[UUID]",
+ "Primary Service Discovery" },
+ { "characteristics", cmd_char, "[start hnd [end hnd [UUID]]]",
+ "Characteristics Discovery" },
+ { "char-desc", cmd_char_desc, "[start hnd] [end hnd]",
+ "Characteristics Descriptor Discovery" },
+ { "char-read-hnd", cmd_read_hnd, "<handle> [offset]",
+ "Characteristics Value/Descriptor Read by handle" },
+ { "char-read-uuid", cmd_read_uuid, "<UUID> [start hnd] [end hnd]",
+ "Characteristics Value/Descriptor Read by UUID" },
+ { "char-write-req", cmd_char_write, "<handle> <new value>",
+ "Characteristic Value Write (Write Request)" },
+ { "char-write-cmd", cmd_char_write, "<handle> <new value>",
+ "Characteristic Value Write (No response)" },
+ { "sec-level", cmd_sec_level, "[low | medium | high]",
+ "Set security level. Default: low" },
+ { "mtu", cmd_mtu, "<value>",
+ "Exchange MTU for GATT/ATT" },
+ { NULL, NULL, NULL}
+};
+
+static void cmd_help(int argcp, char **argvp)
+{
+ int i;
+
+ for (i = 0; commands[i].cmd; i++)
+ printf("%-15s %-30s %s\n", commands[i].cmd,
+ commands[i].params, commands[i].desc);
+}
+
+static void parse_line(char *line_read)
+{
+ gchar **argvp;
+ int argcp;
+ int i;
+
+ if (line_read == NULL) {
+ printf("\n");
+ cmd_exit(0, NULL);
+ return;
+ }
+
+ line_read = g_strstrip(line_read);
+
+ if (*line_read == '\0')
+ return;
+
+ add_history(line_read);
+
+ g_shell_parse_argv(line_read, &argcp, &argvp, NULL);
+
+ for (i = 0; commands[i].cmd; i++)
+ if (strcasecmp(commands[i].cmd, argvp[0]) == 0)
+ break;
+
+ if (commands[i].cmd)
+ commands[i].func(argcp, argvp);
+ else
+ printf("%s: command not found\n", argvp[0]);
+
+ g_strfreev(argvp);
+}
+
+static gboolean prompt_read(GIOChannel *chan, GIOCondition cond,
+ gpointer user_data)
+{
+ if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ rl_callback_read_char();
+
+ return TRUE;
+}
+
+int interactive(const gchar *src, const gchar *dst, int psm)
+{
+ GIOChannel *pchan;
+ gint events;
+
+ opt_sec_level = g_strdup("low");
+
+ opt_src = g_strdup(src);
+ opt_dst = g_strdup(dst);
+ opt_psm = psm;
+
+ prompt = g_string_new(NULL);
+
+ event_loop = g_main_loop_new(NULL, FALSE);
+
+ pchan = g_io_channel_unix_new(fileno(stdin));
+ g_io_channel_set_close_on_unref(pchan, TRUE);
+ events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+ g_io_add_watch(pchan, events, prompt_read, NULL);
+
+ rl_callback_handler_install(get_prompt(), parse_line);
+
+ g_main_loop_run(event_loop);
+
+ rl_callback_handler_remove();
+ cmd_disconnect(0, NULL);
+ g_io_channel_unref(pchan);
+ g_main_loop_unref(event_loop);
+ g_string_free(prompt, TRUE);
+
+ g_free(opt_src);
+ g_free(opt_dst);
+ g_free(opt_sec_level);
+
+ return 0;
+}
diff --git a/attrib/main.c b/attrib/main.c
new file mode 100644
index 0000000..6c946be
--- /dev/null
+++ b/attrib/main.c
@@ -0,0 +1,60 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 <gdbus.h>
+
+#include "plugin.h"
+#include "manager.h"
+
+static DBusConnection *connection;
+
+static int attrib_init(void)
+{
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (connection == NULL)
+ return -EIO;
+
+ if (attrib_manager_init(connection) < 0) {
+ dbus_connection_unref(connection);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void attrib_exit(void)
+{
+ attrib_manager_exit();
+
+ dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(attrib, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, attrib_init, attrib_exit)
diff --git a/attrib/manager.c b/attrib/manager.c
new file mode 100644
index 0000000..a5a7de4
--- /dev/null
+++ b/attrib/manager.c
@@ -0,0 +1,105 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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 <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+#include "hcid.h"
+
+#include "manager.h"
+#include "client.h"
+#include "example.h"
+
+#define GATT_UUID "00001801-0000-1000-8000-00805f9b34fb"
+
+static DBusConnection *connection;
+
+static int client_probe(struct btd_device *device, GSList *uuids)
+{
+ const sdp_record_t *rec;
+ int psm = -1;
+
+ rec = btd_device_get_record(device, GATT_UUID);
+ if (rec) {
+ sdp_list_t *list;
+ if (sdp_get_access_protos(rec, &list) < 0)
+ return -1;
+
+ psm = sdp_get_proto_port(list, L2CAP_UUID);
+
+ sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(list, NULL);
+
+ if (psm < 0)
+ return -1;
+ }
+
+ return attrib_client_register(device, psm);
+}
+
+static void client_remove(struct btd_device *device)
+{
+ attrib_client_unregister(device);
+}
+
+static struct btd_device_driver client_driver = {
+ .name = "gatt-client",
+ .uuids = BTD_UUIDS(GATT_UUID),
+ .probe = client_probe,
+ .remove = client_remove,
+};
+
+int attrib_manager_init(DBusConnection *conn)
+{
+ connection = dbus_connection_ref(conn);
+
+ attrib_client_init(connection);
+
+ btd_register_device_driver(&client_driver);
+
+
+ if (main_opts.attrib_server)
+ return server_example_init();
+
+ return 0;
+}
+
+void attrib_manager_exit(void)
+{
+ btd_unregister_device_driver(&client_driver);
+
+ if (main_opts.attrib_server)
+ server_example_exit();
+
+ attrib_client_exit();
+
+ dbus_connection_unref(connection);
+}
diff --git a/attrib/manager.h b/attrib/manager.h
new file mode 100644
index 0000000..fabf342
--- /dev/null
+++ b/attrib/manager.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 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
+ *
+ */
+
+int attrib_manager_init(DBusConnection *conn);
+void attrib_manager_exit(void);
diff --git a/attrib/utils.c b/attrib/utils.c
new file mode 100644
index 0000000..5f4444a
--- /dev/null
+++ b/attrib/utils.c
@@ -0,0 +1,126 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2011 Nokia Corporation
+ *
+ *
+ * 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
+ *
+ */
+
+#include <stdlib.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+
+#include "gattrib.h"
+#include "gatt.h"
+#include "btio.h"
+#include "gatttool.h"
+
+/* Minimum MTU for ATT connections */
+#define ATT_MIN_MTU_LE 23
+#define ATT_MIN_MTU_L2CAP 48
+
+GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
+ const gchar *sec_level, int psm, int mtu,
+ BtIOConnect connect_cb)
+{
+ GIOChannel *chan;
+ bdaddr_t sba, dba;
+ GError *err = NULL;
+ BtIOSecLevel sec;
+ int minimum_mtu;
+
+ /* This check is required because currently setsockopt() returns no
+ * errors for MTU values smaller than the allowed minimum. */
+ minimum_mtu = psm ? ATT_MIN_MTU_L2CAP : ATT_MIN_MTU_LE;
+ if (mtu != 0 && mtu < minimum_mtu) {
+ g_printerr("MTU cannot be smaller than %d\n", minimum_mtu);
+ return NULL;
+ }
+
+ /* Remote device */
+ if (dst == NULL) {
+ g_printerr("Remote Bluetooth address required\n");
+ return NULL;
+ }
+ str2ba(dst, &dba);
+
+ /* Local adapter */
+ if (src != NULL) {
+ if (!strncmp(src, "hci", 3))
+ hci_devba(atoi(src + 3), &sba);
+ else
+ str2ba(src, &sba);
+ } else
+ bacpy(&sba, BDADDR_ANY);
+
+ if (strcmp(sec_level, "medium") == 0)
+ sec = BT_IO_SEC_MEDIUM;
+ else if (strcmp(sec_level, "high") == 0)
+ sec = BT_IO_SEC_HIGH;
+ else
+ sec = BT_IO_SEC_LOW;
+
+ if (psm == 0)
+ chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &sba,
+ BT_IO_OPT_DEST_BDADDR, &dba,
+ BT_IO_OPT_CID, GATT_CID,
+ BT_IO_OPT_OMTU, mtu,
+ BT_IO_OPT_SEC_LEVEL, sec,
+ BT_IO_OPT_INVALID);
+ else
+ chan = bt_io_connect(BT_IO_L2CAP, connect_cb, NULL, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &sba,
+ BT_IO_OPT_DEST_BDADDR, &dba,
+ BT_IO_OPT_PSM, psm,
+ BT_IO_OPT_OMTU, mtu,
+ BT_IO_OPT_SEC_LEVEL, sec,
+ BT_IO_OPT_INVALID);
+
+ if (err) {
+ g_printerr("%s\n", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ return chan;
+}
+
+size_t gatt_attr_data_from_string(const char *str, uint8_t **data)
+{
+ char tmp[3];
+ size_t size, i;
+
+ size = strlen(str) / 2;
+ *data = g_try_malloc0(size);
+ if (*data == NULL)
+ return 0;
+
+ tmp[2] = '\0';
+ for (i = 0; i < size; i++) {
+ memcpy(tmp, str + (i * 2), 2);
+ (*data)[i] = (uint8_t) strtol(tmp, NULL, 16);
+ }
+
+ return size;
+}