bluez: Copy bluez 4.101 GATT files

pull/5/head
Olivier Martin 2016-04-23 14:41:30 +01:00
commit 81ef1df6ca
14 changed files with 6230 additions and 0 deletions

975
bluez/attrib/att.c Normal file
View File

@ -0,0 +1,975 @@
/*
*
* 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 "Attribute 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_AUTHENTICATION:
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_AUTHORIZATION:
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";
case ATT_ECODE_TIMEOUT:
return "A timeout occured";
case ATT_ECODE_ABORTED:
return "The operation was aborted";
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(uint16_t handle, uint8_t *value, int vlen,
uint8_t *pdu, int len)
{
const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
if (pdu == NULL)
return 0;
if (len < (vlen + min_len))
return 0;
pdu[0] = ATT_OP_HANDLE_NOTIFY;
att_put_u16(handle, &pdu[1]);
memcpy(&pdu[3], value, vlen);
return vlen + min_len;
}
uint16_t enc_indication(uint16_t handle, uint8_t *value, int vlen,
uint8_t *pdu, int len)
{
const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
if (pdu == NULL)
return 0;
if (len < (vlen + min_len))
return 0;
pdu[0] = ATT_OP_HANDLE_IND;
att_put_u16(handle, &pdu[1]);
memcpy(&pdu[3], value, vlen);
return vlen + min_len;
}
uint16_t dec_indication(const uint8_t *pdu, int len, uint16_t *handle,
uint8_t *value, int vlen)
{
const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t);
uint16_t dlen;
if (pdu == NULL)
return 0;
if (pdu[0] != ATT_OP_HANDLE_IND)
return 0;
if (len < min_len)
return 0;
dlen = MIN(len - min_len, vlen);
if (handle)
*handle = att_get_u16(&pdu[1]);
memcpy(value, &pdu[3], dlen);
return dlen;
}
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;
}

258
bluez/attrib/att.h Normal file
View File

@ -0,0 +1,258 @@
/*
*
* 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
*
*/
/* 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_AUTHENTICATION 0x05
#define ATT_ECODE_REQ_NOT_SUPP 0x06
#define ATT_ECODE_INVALID_OFFSET 0x07
#define ATT_ECODE_AUTHORIZATION 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 0x80
#define ATT_ECODE_TIMEOUT 0x81
#define ATT_ECODE_ABORTED 0x82
/* 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
#define ATT_CID 4
#define ATT_PSM 31
struct att_data_list {
uint16_t num;
uint16_t len;
uint8_t **data;
};
struct att_range {
uint16_t start;
uint16_t end;
};
/* 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(uint16_t handle, uint8_t *value, int vlen,
uint8_t *pdu, int len);
uint16_t enc_indication(uint16_t handle, uint8_t *value, int vlen,
uint8_t *pdu, int len);
uint16_t dec_indication(const uint8_t *pdu, int len, uint16_t *handle,
uint8_t *value, int vlen);
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);

655
bluez/attrib/gatt.c Normal file
View File

@ -0,0 +1,655 @@
/*
*
* 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 <stdint.h>
#include <stdlib.h>
#include <glib.h>
#include <bluetooth/uuid.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.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_free_full(dc->characteristics, g_free);
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;
bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID);
if (uuid == NULL) {
/* Discover all primary services */
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 */
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 gatt_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 gatt_primary, 1);
if (!primary) {
err = ATT_ECODE_INSUFF_RESOURCES;
goto done;
}
primary->range.start = start;
primary->range.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) {
dp->uuid = *uuid;
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 gatt_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 gatt_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 && (last + 1 < dc->end)) {
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);
}
static sdp_data_t *proto_seq_find(sdp_list_t *proto_list)
{
sdp_list_t *list;
uuid_t proto;
sdp_uuid16_create(&proto, ATT_UUID);
for (list = proto_list; list; list = list->next) {
sdp_list_t *p;
for (p = list->data; p; p = p->next) {
sdp_data_t *seq = p->data;
if (seq && seq->dtd == SDP_UUID16 &&
sdp_uuid16_cmp(&proto, &seq->val.uuid) == 0)
return seq->next;
}
}
return NULL;
}
static gboolean parse_proto_params(sdp_list_t *proto_list, uint16_t *psm,
uint16_t *start, uint16_t *end)
{
sdp_data_t *seq1, *seq2;
if (psm)
*psm = sdp_get_proto_port(proto_list, L2CAP_UUID);
/* Getting start and end handle */
seq1 = proto_seq_find(proto_list);
if (!seq1 || seq1->dtd != SDP_UINT16)
return FALSE;
seq2 = seq1->next;
if (!seq2 || seq2->dtd != SDP_UINT16)
return FALSE;
if (start)
*start = seq1->val.uint16;
if (end)
*end = seq2->val.uint16;
return TRUE;
}
gboolean gatt_parse_record(const sdp_record_t *rec,
uuid_t *prim_uuid, uint16_t *psm,
uint16_t *start, uint16_t *end)
{
sdp_list_t *list;
uuid_t uuid;
gboolean ret;
if (sdp_get_service_classes(rec, &list) < 0)
return FALSE;
memcpy(&uuid, list->data, sizeof(uuid));
sdp_list_free(list, free);
if (sdp_get_access_protos(rec, &list) < 0)
return FALSE;
ret = parse_proto_params(list, psm, start, end);
sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL);
sdp_list_free(list, NULL);
/* FIXME: replace by bt_uuid_t after uuid_t/sdp code cleanup */
if (ret && prim_uuid)
memcpy(prim_uuid, &uuid, sizeof(uuid_t));
return ret;
}

96
bluez/attrib/gatt.h Normal file
View File

@ -0,0 +1,96 @@
/*
*
* 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 <bluetooth/sdp.h>
/* 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
#define GATT_CHARAC_VALID_RANGE_UUID 0x2906
/* Client Characteristic Configuration bit field */
#define GATT_CLIENT_CHARAC_CFG_NOTIF_BIT 0x0001
#define GATT_CLIENT_CHARAC_CFG_IND_BIT 0x0002
typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
struct gatt_primary {
char uuid[MAX_LEN_UUID_STR + 1];
struct att_range range;
};
struct gatt_char {
char uuid[MAX_LEN_UUID_STR + 1];
uint16_t handle;
uint8_t properties;
uint16_t value_handle;
};
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);
gboolean gatt_parse_record(const sdp_record_t *rec,
uuid_t *prim_uuid, uint16_t *psm,
uint16_t *start, uint16_t *end);

726
bluez/attrib/gattrib.c Normal file
View File

@ -0,0 +1,726 @@
/*
*
* 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 "log.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 *requests;
GQueue *responses;
GSList *events;
guint next_cmd_id;
GDestroyNotify destroy;
gpointer destroy_user_data;
gboolean stale;
};
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);
DBG("%p: ref=%d", attrib, 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->requests)))
command_destroy(c);
while ((c = g_queue_pop_head(attrib->responses)))
command_destroy(c);
g_queue_free(attrib->requests);
attrib->requests = NULL;
g_queue_free(attrib->responses);
attrib->responses = 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);
if (attrib->io)
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)
{
gboolean ret;
if (!attrib)
return;
ret = g_atomic_int_dec_and_test(&attrib->refs);
DBG("%p: ref=%d", attrib, attrib->refs);
if (ret == FALSE)
return;
attrib_destroy(attrib);
}
GIOChannel *g_attrib_get_channel(GAttrib *attrib)
{
if (!attrib)
return NULL;
return attrib->io;
}
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;
struct command *c;
g_attrib_ref(attrib);
c = g_queue_pop_head(attrib->requests);
if (c == NULL)
goto done;
if (c->func)
c->func(ATT_ECODE_TIMEOUT, NULL, 0, c->user_data);
command_destroy(c);
while ((c = g_queue_pop_head(attrib->requests))) {
if (c->func)
c->func(ATT_ECODE_ABORTED, NULL, 0, c->user_data);
command_destroy(c);
}
done:
attrib->stale = TRUE;
g_attrib_unref(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;
GQueue *queue;
if (attrib->stale)
return FALSE;
if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL))
return FALSE;
queue = attrib->responses;
cmd = g_queue_peek_head(queue);
if (cmd == NULL) {
queue = attrib->requests;
cmd = g_queue_peek_head(queue);
}
if (cmd == NULL)
return FALSE;
/*
* Verify that we didn't already send this command. This can only
* happen with elementes from attrib->requests.
*/
if (cmd->sent)
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(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;
g_attrib_unref(attrib);
}
static void wake_up_sender(struct _GAttrib *attrib)
{
if (attrib->write_watch > 0)
return;
attrib = g_attrib_ref(attrib);
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 norequests, noresponses;
if (attrib->stale)
return FALSE;
if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
attrib->read_watch = 0;
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 ||
(is_response(buf[0]) == FALSE &&
evt->expected == GATTRIB_ALL_REQS))
evt->func(buf, len, evt->user_data);
}
if (is_response(buf[0]) == FALSE)
return TRUE;
if (attrib->timeout_watch > 0) {
g_source_remove(attrib->timeout_watch);
attrib->timeout_watch = 0;
}
cmd = g_queue_pop_head(attrib->requests);
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:
norequests = attrib->requests == NULL ||
g_queue_is_empty(attrib->requests);
noresponses = attrib->responses == NULL ||
g_queue_is_empty(attrib->responses);
if (cmd) {
if (cmd->func)
cmd->func(status, buf, len, cmd->user_data);
command_destroy(cmd);
}
if (!norequests || !noresponses)
wake_up_sender(attrib);
return TRUE;
}
GAttrib *g_attrib_new(GIOChannel *io)
{
struct _GAttrib *attrib;
uint16_t imtu;
uint16_t att_mtu;
uint16_t cid;
GError *gerr = NULL;
g_io_channel_set_encoding(io, NULL, NULL);
g_io_channel_set_buffered(io, FALSE);
bt_io_get(io, BT_IO_L2CAP, &gerr,
BT_IO_OPT_IMTU, &imtu,
BT_IO_OPT_CID, &cid,
BT_IO_OPT_INVALID);
if (gerr) {
error("%s", gerr->message);
g_error_free(gerr);
return NULL;
}
attrib = g_try_new0(struct _GAttrib, 1);
if (attrib == NULL)
return NULL;
att_mtu = (cid == ATT_CID) ? ATT_DEFAULT_LE_MTU : imtu;
attrib->buf = g_malloc0(att_mtu);
attrib->buflen = att_mtu;
attrib->io = g_io_channel_ref(io);
attrib->requests = g_queue_new();
attrib->responses = 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);
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;
GQueue *queue;
if (attrib->stale)
return 0;
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 (is_response(opcode))
queue = attrib->responses;
else
queue = attrib->requests;
if (id) {
c->id = id;
if (!is_response(opcode))
g_queue_push_head(queue, c);
else
/* Don't re-order responses even if an ID is given */
g_queue_push_tail(queue, c);
} else {
c->id = ++attrib->next_cmd_id;
g_queue_push_tail(queue, c);
}
/*
* If a command was added to the queue and it was empty before, wake up
* the sender. If the sender was already woken up by the second queue,
* wake_up_sender will just return.
*/
if (g_queue_get_length(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 = NULL;
struct command *cmd;
GQueue *queue;
if (attrib == NULL)
return FALSE;
queue = attrib->requests;
if (queue)
l = g_queue_find_custom(queue, GUINT_TO_POINTER(id),
command_cmp_by_id);
if (l == NULL) {
queue = attrib->responses;
if (!queue)
return FALSE;
l = g_queue_find_custom(queue, GUINT_TO_POINTER(id),
command_cmp_by_id);
}
if (l == NULL)
return FALSE;
cmd = l->data;
if (cmd == g_queue_peek_head(queue) && cmd->sent)
cmd->func = NULL;
else {
g_queue_remove(queue, cmd);
command_destroy(cmd);
}
return TRUE;
}
static gboolean cancel_all_per_queue(GQueue *queue)
{
struct command *c, *head = NULL;
gboolean first = TRUE;
if (queue == NULL)
return FALSE;
while ((c = g_queue_pop_head(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(queue, head);
}
return TRUE;
}
gboolean g_attrib_cancel_all(GAttrib *attrib)
{
gboolean ret;
if (attrib == NULL)
return FALSE;
ret = cancel_all_per_queue(attrib->requests);
ret = cancel_all_per_queue(attrib->responses) && ret;
return ret;
}
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)
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)
{
static guint next_evt_id = 0;
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 = ++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;
}

78
bluez/attrib/gattrib.h Normal file
View File

@ -0,0 +1,78 @@
/*
*
* 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
#define GATTRIB_ALL_REQS 0xFE
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_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

1460
bluez/btio/btio.c Normal file

File diff suppressed because it is too large Load Diff

111
bluez/btio/btio.h Normal file
View File

@ -0,0 +1,111 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2009-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2009-2010 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
*
*/
#ifndef BT_IO_H
#define BT_IO_H
#include <glib.h>
typedef enum {
BT_IO_ERROR_DISCONNECTED,
BT_IO_ERROR_CONNECT_FAILED,
BT_IO_ERROR_FAILED,
BT_IO_ERROR_INVALID_ARGS,
} BtIOError;
#define BT_IO_ERROR bt_io_error_quark()
GQuark bt_io_error_quark(void);
typedef enum {
BT_IO_L2RAW,
BT_IO_L2CAP,
BT_IO_L2ERTM,
BT_IO_RFCOMM,
BT_IO_SCO,
} BtIOType;
typedef enum {
BT_IO_OPT_INVALID = 0,
BT_IO_OPT_SOURCE,
BT_IO_OPT_SOURCE_BDADDR,
BT_IO_OPT_DEST,
BT_IO_OPT_DEST_BDADDR,
BT_IO_OPT_DEST_TYPE,
BT_IO_OPT_DEFER_TIMEOUT,
BT_IO_OPT_SEC_LEVEL,
BT_IO_OPT_KEY_SIZE,
BT_IO_OPT_CHANNEL,
BT_IO_OPT_SOURCE_CHANNEL,
BT_IO_OPT_DEST_CHANNEL,
BT_IO_OPT_PSM,
BT_IO_OPT_CID,
BT_IO_OPT_MTU,
BT_IO_OPT_OMTU,
BT_IO_OPT_IMTU,
BT_IO_OPT_MASTER,
BT_IO_OPT_HANDLE,
BT_IO_OPT_CLASS,
BT_IO_OPT_MODE,
BT_IO_OPT_FLUSHABLE,
BT_IO_OPT_PRIORITY,
} BtIOOption;
typedef enum {
BT_IO_SEC_SDP = 0,
BT_IO_SEC_LOW,
BT_IO_SEC_MEDIUM,
BT_IO_SEC_HIGH,
} BtIOSecLevel;
typedef enum {
BT_IO_MODE_BASIC = 0,
BT_IO_MODE_RETRANS,
BT_IO_MODE_FLOWCTL,
BT_IO_MODE_ERTM,
BT_IO_MODE_STREAMING
} BtIOMode;
typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data);
typedef void (*BtIOConnect)(GIOChannel *io, GError *err, gpointer user_data);
gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data,
GDestroyNotify destroy, GError **err);
gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err,
BtIOOption opt1, ...);
gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err,
BtIOOption opt1, ...);
GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect,
gpointer user_data, GDestroyNotify destroy,
GError **err, BtIOOption opt1, ...);
GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect,
BtIOConfirm confirm, gpointer user_data,
GDestroyNotify destroy, GError **err,
BtIOOption opt1, ...);
#endif

144
bluez/src/log.c Normal file
View File

@ -0,0 +1,144 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-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 <stdio.h>
#include <stdarg.h>
#include <syslog.h>
#include <glib.h>
#include "log.h"
void info(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsyslog(LOG_INFO, format, ap);
va_end(ap);
}
void warn(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsyslog(LOG_WARNING, format, ap);
va_end(ap);
}
void error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsyslog(LOG_ERR, format, ap);
va_end(ap);
}
void btd_debug(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsyslog(LOG_DEBUG, format, ap);
va_end(ap);
}
extern struct btd_debug_desc __start___debug[];
extern struct btd_debug_desc __stop___debug[];
static gchar **enabled = NULL;
static gboolean is_enabled(struct btd_debug_desc *desc)
{
int i;
if (enabled == NULL)
return 0;
for (i = 0; enabled[i] != NULL; i++)
if (desc->file != NULL && g_pattern_match_simple(enabled[i],
desc->file) == TRUE)
return 1;
return 0;
}
void __btd_enable_debug(struct btd_debug_desc *start,
struct btd_debug_desc *stop)
{
struct btd_debug_desc *desc;
if (start == NULL || stop == NULL)
return;
for (desc = start; desc < stop; desc++) {
if (is_enabled(desc))
desc->flags |= BTD_DEBUG_FLAG_PRINT;
}
}
void __btd_toggle_debug(void)
{
struct btd_debug_desc *desc;
for (desc = __start___debug; desc < __stop___debug; desc++)
desc->flags |= BTD_DEBUG_FLAG_PRINT;
}
void __btd_log_init(const char *debug, int detach)
{
int option = LOG_NDELAY | LOG_PID;
if (debug != NULL)
enabled = g_strsplit_set(debug, ":, ", 0);
__btd_enable_debug(__start___debug, __stop___debug);
if (!detach)
option |= LOG_PERROR;
openlog("bluetoothd", option, LOG_DAEMON);
syslog(LOG_INFO, "Bluetooth daemon %s", VERSION);
}
void __btd_log_cleanup(void)
{
closelog();
g_strfreev(enabled);
}

59
bluez/src/log.h Normal file
View File

@ -0,0 +1,59 @@
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-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
*
*/
void info(const char *format, ...) __attribute__((format(printf, 1, 2)));
void warn(const char *format, ...) __attribute__((format(printf, 1, 2)));
void error(const char *format, ...) __attribute__((format(printf, 1, 2)));
void btd_debug(const char *format, ...) __attribute__((format(printf, 1, 2)));
void __btd_log_init(const char *debug, int detach);
void __btd_log_cleanup(void);
void __btd_toggle_debug(void);
struct btd_debug_desc {
const char *file;
#define BTD_DEBUG_FLAG_DEFAULT (0)
#define BTD_DEBUG_FLAG_PRINT (1 << 0)
unsigned int flags;
} __attribute__((aligned(8)));
void __btd_enable_debug(struct btd_debug_desc *start,
struct btd_debug_desc *stop);
/**
* DBG:
* @fmt: format string
* @arg...: list of arguments
*
* Simple macro around btd_debug() which also include the function
* name it is called in.
*/
#define DBG(fmt, arg...) do { \
static struct btd_debug_desc __btd_debug_desc \
__attribute__((used, section("__debug"), aligned(8))) = { \
.file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \
}; \
if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \
btd_debug("%s:%s() " fmt, __FILE__, __FUNCTION__ , ## arg); \
} while (0)

View File

@ -0,0 +1,628 @@
/*
*
* 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_dst_type = 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;
static GSourceFunc operation;
struct characteristic_data {
GAttrib *attrib;
uint16_t start;
uint16_t end;
};
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 void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
{
GAttrib *attrib;
if (err) {
g_printerr("%s\n", err->message);
got_error = TRUE;
g_main_loop_quit(event_loop);
}
attrib = g_attrib_new(io);
if (opt_listen)
g_idle_add(listen_start, attrib);
operation(attrib);
}
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 gatt_primary *prim = l->data;
g_print("attr handle = 0x%04x, end grp handle = 0x%04x "
"uuid: %s\n", prim->range.start, prim->range.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 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 gatt_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);
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 successfully\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" },
{ "addr-type", 't', 0, G_OPTION_ARG_STRING, &opt_dst_type,
"Set LE address type. Default: public", "[public | random]"},
{ "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;
GIOChannel *chan;
opt_dst_type = g_strdup("public");
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_dst_type, opt_psm);
goto done;
}
if (opt_primary)
operation = primary;
else if (opt_characteristics)
operation = characteristics;
else if (opt_char_read)
operation = characteristics_read;
else if (opt_char_write)
operation = characteristics_write;
else if (opt_char_write_req)
operation = characteristics_write_req;
else if (opt_char_desc)
operation = 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_dst_type, opt_sec_level,
opt_psm, opt_mtu, connect_cb);
if (chan == NULL) {
got_error = TRUE;
goto done;
}
event_loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(event_loop);
g_main_loop_unref(event_loop);
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);
}

View File

@ -0,0 +1,29 @@
/*
*
* 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, const gchar *dst_type,
gboolean le);
GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
const gchar *dst_type, const gchar *sec_level,
int psm, int mtu, BtIOConnect connect_cb);
size_t gatt_attr_data_from_string(const char *str, uint8_t **data);

View File

@ -0,0 +1,890 @@
/*
*
* 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_dst_type = 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 disconnect_io()
{
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 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 gatt_primary *prim = l->data;
printf("attr handle: 0x%04x, end grp handle: 0x%04x "
"uuid: %s\n", prim->range.start, prim->range.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 gatt_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);
rl_forced_update_display();
done:
g_free(char_data);
}
static void cmd_exit(int argcp, char **argvp)
{
rl_callback_handler_remove();
g_main_loop_quit(event_loop);
}
static gboolean channel_watcher(GIOChannel *chan, GIOCondition cond,
gpointer user_data)
{
disconnect_io();
return FALSE;
}
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]);
g_free(opt_dst_type);
if (argcp > 2)
opt_dst_type = g_strdup(argvp[2]);
else
opt_dst_type = g_strdup("public");
}
if (opt_dst == NULL) {
printf("Remote Bluetooth address required\n");
return;
}
set_state(STATE_CONNECTING);
iochannel = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level,
opt_psm, opt_mtu, connect_cb);
if (iochannel == NULL)
set_state(STATE_DISCONNECTED);
else
g_io_add_watch(iochannel, G_IO_HUP, channel_watcher, NULL);
}
static void cmd_disconnect(int argcp, char **argvp)
{
disconnect_io();
}
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 = strtohandle(argvp[1]);
if (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" },
{ "quit", cmd_exit, "",
"Exit interactive mode" },
{ "connect", cmd_connect, "[address [address type]]",
"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;
}
static char *completion_generator(const char *text, int state)
{
static int index = 0, len = 0;
const char *cmd = NULL;
if (state == 0) {
index = 0;
len = strlen(text);
}
while ((cmd = commands[index].cmd) != NULL) {
index++;
if (strncmp(cmd, text, len) == 0)
return strdup(cmd);
}
return NULL;
}
static char **commands_completion(const char *text, int start, int end)
{
if (start == 0)
return rl_completion_matches(text, &completion_generator);
else
return NULL;
}
int interactive(const gchar *src, const gchar *dst,
const gchar *dst_type, int psm)
{
GIOChannel *pchan;
gint events;
opt_sec_level = g_strdup("low");
opt_src = g_strdup(src);
opt_dst = g_strdup(dst);
opt_dst_type = g_strdup(dst_type);
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_attempted_completion_function = commands_completion;
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;
}

121
examples/gatttool/utils.c Normal file
View File

@ -0,0 +1,121 @@
/*
*
* 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 "att.h"
#include "gattrib.h"
#include "gatt.h"
#include "btio.h"
#include "gatttool.h"
GIOChannel *gatt_connect(const gchar *src, const gchar *dst,
const gchar *dst_type, const gchar *sec_level,
int psm, int mtu, BtIOConnect connect_cb)
{
GIOChannel *chan;
bdaddr_t sba, dba;
uint8_t dest_type;
GError *err = NULL;
BtIOSecLevel sec;
/* 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);
/* Not used for BR/EDR */
if (strcmp(dst_type, "random") == 0)
dest_type = BDADDR_LE_RANDOM;
else
dest_type = BDADDR_LE_PUBLIC;
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_DEST_TYPE, dest_type,
BT_IO_OPT_CID, ATT_CID,
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_IMTU, 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;
}