mirror of https://github.com/labapart/gattlib
bluez: Copy bluez 4.101 GATT files
commit
81ef1df6ca
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue