diff options
author | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
---|---|---|
committer | Adrian Bunk <adrian.bunk@movial.com> | 2011-06-03 09:17:04 +0000 |
commit | 799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch) | |
tree | a8c3be85c730de28b012586591b76301033d3d21 /attrib |
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'attrib')
-rw-r--r-- | attrib/att.c | 968 | ||||
-rw-r--r-- | attrib/att.h | 306 | ||||
-rw-r--r-- | attrib/client.c | 1116 | ||||
-rw-r--r-- | attrib/client.h | 28 | ||||
-rw-r--r-- | attrib/example.c | 341 | ||||
-rw-r--r-- | attrib/example.h | 26 | ||||
-rw-r--r-- | attrib/gatt.c | 577 | ||||
-rw-r--r-- | attrib/gatt.h | 53 | ||||
-rw-r--r-- | attrib/gattrib.c | 636 | ||||
-rw-r--r-- | attrib/gattrib.h | 80 | ||||
-rw-r--r-- | attrib/gatttool.c | 634 | ||||
-rw-r--r-- | attrib/gatttool.h | 28 | ||||
-rw-r--r-- | attrib/interactive.c | 842 | ||||
-rw-r--r-- | attrib/main.c | 60 | ||||
-rw-r--r-- | attrib/manager.c | 105 | ||||
-rw-r--r-- | attrib/manager.h | 26 | ||||
-rw-r--r-- | attrib/utils.c | 126 |
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; +} |