diff options
Diffstat (limited to 'peripheral/gatt.c')
-rw-r--r-- | peripheral/gatt.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/peripheral/gatt.c b/peripheral/gatt.c new file mode 100644 index 000000000..e1bf8fbff --- /dev/null +++ b/peripheral/gatt.c @@ -0,0 +1,317 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "src/shared/gatt-client.h" +#include "peripheral/gatt.h" + +#define ATT_CID 4 + +#define UUID_GAP 0x1800 + +struct gatt_conn { + struct bt_att *att; + struct bt_gatt_server *gatt; + struct bt_gatt_client *client; +}; + +static int att_fd = -1; +static struct queue *conn_list = NULL; +static struct gatt_db *gatt_db = NULL; +static struct gatt_db *gatt_cache = NULL; + +static uint8_t static_addr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static uint8_t dev_name[20]; +static uint8_t dev_name_len = 0; + +void gatt_set_static_address(uint8_t addr[6]) +{ + memcpy(static_addr, addr, sizeof(static_addr)); +} + +void gatt_set_device_name(uint8_t name[20], uint8_t len) +{ + memcpy(dev_name, name, sizeof(dev_name)); + dev_name_len = len; +} + +static void gatt_conn_destroy(void *data) +{ + struct gatt_conn *conn = data; + + bt_gatt_client_unref(conn->client); + bt_gatt_server_unref(conn->gatt); + bt_att_unref(conn->att); + + free(conn); +} + +static void gatt_conn_disconnect(int err, void *user_data) +{ + struct gatt_conn *conn = user_data; + + printf("Device disconnected: %s\n", strerror(err)); + + queue_remove(conn_list, conn); + gatt_conn_destroy(conn); +} + +static void client_ready_callback(bool success, uint8_t att_ecode, + void *user_data) +{ + printf("GATT client discovery complete\n"); +} + +static void client_service_changed_callback(uint16_t start_handle, + uint16_t end_handle, + void *user_data) +{ + printf("GATT client service changed notification\n"); +} + +static struct gatt_conn *gatt_conn_new(int fd) +{ + struct gatt_conn *conn; + uint16_t mtu = 0; + + conn = new0(struct gatt_conn, 1); + if (!conn) + return NULL; + + conn->att = bt_att_new(fd); + if (!conn->att) { + fprintf(stderr, "Failed to initialze ATT transport layer\n"); + free(conn); + return NULL; + } + + bt_att_set_close_on_unref(conn->att, true); + bt_att_register_disconnect(conn->att, gatt_conn_disconnect, conn, NULL); + + bt_att_set_sec_level(conn->att, BT_SECURITY_MEDIUM); + + conn->gatt = bt_gatt_server_new(gatt_db, conn->att, mtu); + if (!conn->gatt) { + fprintf(stderr, "Failed to create GATT server\n"); + bt_att_unref(conn->att); + free(conn); + return NULL; + } + + conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu); + if (!conn->gatt) { + fprintf(stderr, "Failed to create GATT client\n"); + bt_gatt_server_unref(conn->gatt); + bt_att_unref(conn->att); + free(conn); + return NULL; + } + + bt_gatt_client_set_ready_handler(conn->client, + client_ready_callback, conn, NULL); + bt_gatt_client_set_service_changed(conn->client, + client_service_changed_callback, conn, NULL); + + return conn; +} + +static void att_conn_callback(int fd, uint32_t events, void *user_data) +{ + struct gatt_conn *conn; + struct sockaddr_l2 addr; + socklen_t addrlen; + int new_fd; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + new_fd = accept(att_fd, (struct sockaddr *) &addr, &addrlen); + if (new_fd < 0) { + fprintf(stderr, "Failed to accept new ATT connection: %m\n"); + return; + } + + conn = gatt_conn_new(new_fd); + if (!conn) { + fprintf(stderr, "Failed to create GATT connection\n"); + close(new_fd); + } + + if (!queue_push_tail(conn_list, conn)) { + fprintf(stderr, "Failed to add GATT connection\n"); + gatt_conn_destroy(conn); + close(new_fd); + } + + printf("New device connected\n"); +} + +static void gap_device_name_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t error; + const uint8_t *value; + size_t len; + + if (offset > dev_name_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + value = NULL; + len = dev_name_len; + } else { + error = 0; + len = dev_name_len - offset; + value = len ? &dev_name[offset] : NULL; + } + + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void populate_gap_service(struct gatt_db *db) +{ + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(db, &uuid, true, 6); + + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_device_name_read, NULL, NULL); + + gatt_db_service_set_active(service, true); +} + +static void populate_devinfo_service(struct gatt_db *db) +{ + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, 0x180a); + service = gatt_db_add_service(db, &uuid, true, 17); + + gatt_db_service_set_active(service, true); +} + +void gatt_server_start(void) +{ + struct sockaddr_l2 addr; + + if (att_fd >= 0) + return; + + att_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_CLOEXEC, + BTPROTO_L2CAP); + if (att_fd < 0) { + fprintf(stderr, "Failed to create ATT server socket: %m\n"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(ATT_CID); + memcpy(&addr.l2_bdaddr, static_addr, 6); + addr.l2_bdaddr_type = BDADDR_LE_RANDOM; + + if (bind(att_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Failed to bind ATT server socket: %m\n"); + close(att_fd); + att_fd = -1; + return; + } + + if (listen(att_fd, 1) < 0) { + fprintf(stderr, "Failed to listen on ATT server socket: %m\n"); + close(att_fd); + att_fd = -1; + return; + } + + gatt_db = gatt_db_new(); + if (!gatt_db) { + close(att_fd); + att_fd = -1; + return; + } + + populate_gap_service(gatt_db); + populate_devinfo_service(gatt_db); + + gatt_cache = gatt_db_new(); + + conn_list = queue_new(); + if (!conn_list) { + gatt_db_unref(gatt_db); + gatt_db = NULL; + close(att_fd); + att_fd = -1; + return; + } + + mainloop_add_fd(att_fd, EPOLLIN, att_conn_callback, NULL, NULL); +} + +void gatt_server_stop(void) +{ + if (att_fd < 0) + return; + + mainloop_remove_fd(att_fd); + + queue_destroy(conn_list, gatt_conn_destroy); + + gatt_db_unref(gatt_cache); + gatt_cache = NULL; + + gatt_db_unref(gatt_db); + gatt_db = NULL; + + close(att_fd); + att_fd = -1; +} |