ble-gatt-server-example: add sample application to create a BLE GATT server

https://jira.digi.com/browse/DEL-6649

Signed-off-by: Arturo Buzarra <arturo.buzarra@digi.com>
This commit is contained in:
Arturo Buzarra 2019-07-05 11:03:22 +02:00
parent a821bc2f7c
commit 844d13fec0
6 changed files with 1291 additions and 0 deletions

View File

@ -0,0 +1,39 @@
#
# Copyright (c) 2019 Digi International Inc.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
#
PROGRAM := ble-gatt-server-example
CFLAGS += -Wall
LDLIBS += -lshared-mainloop -lbluetooth-internal
all: $(PROGRAM)
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
$(PROGRAM): $(OBJS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
.PHONY: install
install: $(PROGRAM)
install -d $(DESTDIR)/usr/bin
install -m 0755 $(PROGRAM) $(DESTDIR)/usr/bin/
.PHONY: clean
clean:
-rm -f *.o $(PROGRAM)

View File

@ -0,0 +1,75 @@
BLE GATT Server Example Application
===================================
Example application to create a BLE GATT server.
This application enables a BLE GATT server on the board. After that, the
application starts listening for BLE connections to provide custom BLE
GATT services and characteristics.
Running the application
-----------------------
Once the binary is in the target, you must configure the bluetooth
interface with the following commands:
```
~# btmgmt -i hci0 le on
hci0 Set Low Energy complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
~# btmgmt -i hci0 connectable on
hci0 Set Connectable complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
~# btmgmt -i hci0 pairable on
hci0 Set Bondable complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
~# btmgmt -i hci0 advertising on
hci0 Set Advertising complete, settings: powered connectable bondable ssp br/edr le advertising secure-conn
~#
```
After you initialize the bluetooth interface, you can start the sample application:
```
~# ./ble-gatt-server-example -h
Usage:
ble-gatt-server-app [options]
Options:
-i, --index <id> Specify adapter index, e.g. hci0
-t, --threshold <temp> The temperature threshold to send notification
-m, --mtu <mtu> The ATT MTU to use
-v, --verbose Enable extra logging
-h, --help Display help
~#
~# ./ble-gatt-server-example --index hci0 --threshold 50
Running GATT server
Started listening on ATT channel. Waiting for connections
Connect from XX:XX:XX:XX:XX:XX
```
Compiling the application
-------------------------
This demo can be compiled using a Digi Embedded Yocto based toolchain. Make
sure to source the corresponding toolchain of the platform you are using,
for example, for ConnectCore 8X:
```
~$ . <DEY-toolchain-path>/environment-setup-aarch64-dey-linux
~$ make
```
For more information, see the [Digi Embedded Yocto online documentation](https://github.com/digi-embedded/meta-digi).
License
-------
Copyright 2019, Digi International Inc.
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appears in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@ -0,0 +1,885 @@
/*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2014 Google Inc.
* Copyright (C) 2019 Digi International Inc.
*
*
* 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.
*
*/
#define _GNU_SOURCE /* for asprintf */
#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/wait.h>
#include <unistd.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/hci.h"
#include "bluetooth/hci_lib.h"
#include "bluetooth/l2cap.h"
#include "bluetooth-internal/uuid.h"
#include "bluetooth-internal/mainloop.h"
#include "bluetooth-internal/util.h"
#include "bluetooth-internal/att.h"
#include "bluetooth-internal/queue.h"
#include "bluetooth-internal/timeout.h"
#include "bluetooth-internal/gatt-db.h"
#include "bluetooth-internal/gatt-server.h"
#include "subprocess.h"
#include "ble-gatt-server-app.h"
#define ATT_CID 4
#define NOTIFICATION_TIMEOUT_MS 1000
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static bool verbose = false;
static bool keep_running = true;
static int temp_threshold = 50;
struct server {
int fd;
struct bt_att *att;
struct gatt_db *db;
struct bt_gatt_server *gatt;
bool svc_chngd_enabled;
uint16_t cell_handle;
int *timeout_handlers;
int n_characteristics;
};
struct notify_cb_t {
uint16_t handle;
struct server *server;
};
void destroy_notify_data(void *user_data)
{
free(user_data);
}
static char *get_cmd_output(char *cmd)
{
FILE *in;
char buff[512], *pbuf = NULL;
int ret_value;
in = popen(cmd, "r");
if (!in) {
syslog(LOG_WARNING, "execution of cmd '%s' failed\n", cmd);
return NULL;
}
pbuf = fgets(buff, ARRAY_SIZE(buff), in);
if (pbuf)
buff[strlen(buff) - 1] = '\0';
ret_value = pclose(in);
syslog(LOG_DEBUG, "cmd '%s' returned: '%s' (return value: %d)\n", cmd, buff, ret_value);
if (pbuf)
return strdup(buff);
return pbuf;
}
static void debug_cb(const char *str, void *user_data)
{
const char *prefix = user_data;
syslog(LOG_DEBUG ,"%s%s\n", prefix, str);
}
static void att_disconnect_cb(int err, void *user_data)
{
printf("Device disconnected: %s\n", strerror(err));
mainloop_quit();
}
static void confirm_write(struct gatt_db_attribute *attr, int err,
void *user_data)
{
if (!err)
return;
syslog(LOG_DEBUG, "Error caching attribute %p - err: %d\n", attr, err);
}
/* Generic Access Service (GAP) */
static void gap_device_name_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
uint8_t error = 0;
size_t len = 0;
uint8_t *value = NULL;
char *name = NULL;
name = get_cmd_output("cat /etc/hostname");
if (!name) {
error = BT_ATT_ERROR_INVALID_HANDLE;
goto done;
}
len = strlen(name) + 1;
if (offset > len) {
error = BT_ATT_ERROR_INVALID_HANDLE;
goto done;
}
len -= offset;
value = len ? (uint8_t *)&name[offset] : NULL;
done:
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
gatt_db_attribute_read_result(attrib, id, error, value, len);
free(name);
}
static void populate_gap_service(struct server *server)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service, *tmp;
uint16_t appearance;
/* Add the GAP service */
bt_uuid16_create(&uuid, UUID_GAP_SERVICE);
service = gatt_db_add_service(server->db, &uuid, true, 6);
/*
* Device Name characteristic. Make the value dynamically read and
* written via callbacks.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
gatt_db_service_add_characteristic(service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_EXT_PROP,
gap_device_name_read_cb,
NULL,
server);
bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID);
/*
* Device Appearance characteristic. Reads and writes should obtain the
* value from the database.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
tmp = gatt_db_service_add_characteristic(service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
NULL, NULL, server);
/*
* Write the appearance value to the database, since we're not using a
* callback.
*/
put_le16(128, &appearance);
gatt_db_attribute_write(tmp, 0, (void *) &appearance,
sizeof(appearance),
BT_ATT_OP_WRITE_REQ,
NULL, confirm_write,
NULL);
gatt_db_service_set_active(service, true);
}
/* Generic Attribute Profile Service (GATT) */
static void gatt_service_changed_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
uint8_t error = 0;
syslog(LOG_DEBUG, "Service Changed Read called\n");
gatt_db_attribute_read_result(attrib, id, error, NULL, 0);
}
static void gatt_svc_chngd_ccc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct server *server = user_data;
uint8_t error = 0;
uint8_t value[2];
syslog(LOG_DEBUG, "Service Changed CCC Read called\n");
value[0] = server->svc_chngd_enabled ? 0x02 : 0x00;
value[1] = 0x00;
gatt_db_attribute_read_result(attrib, id, error, value, sizeof(value));
}
static void gatt_svc_chngd_ccc_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct server *server = user_data;
uint8_t error = 0;
syslog(LOG_DEBUG, "Service Changed CCC Write called\n");
if (!value || len != 2) {
error = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto done;
}
if (offset) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
if (value[0] == 0x00)
server->svc_chngd_enabled = false;
else if (value[0] == 0x02)
server->svc_chngd_enabled = true;
else
error = BT_ATT_ERROR_INVALID_HANDLE;
syslog(LOG_DEBUG, "Service Changed Enabled: %s\n",
server->svc_chngd_enabled ? "true" : "false");
done:
gatt_db_attribute_write_result(attrib, id, error);
}
static void populate_gatt_service(struct server *server)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service, *svc_chngd;
/* Add the GATT service */
bt_uuid16_create(&uuid, UUID_GATT_SERVICE);
service = gatt_db_add_service(server->db, &uuid, true, 4);
bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_INDICATE,
gatt_service_changed_cb,
NULL, server);
gatt_db_attribute_get_handle(svc_chngd);
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
gatt_db_service_add_descriptor(service, &uuid,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
gatt_svc_chngd_ccc_read_cb,
gatt_svc_chngd_ccc_write_cb, server);
gatt_db_service_set_active(service, true);
}
/* Device Information Service */
static void temperature_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
uint8_t error = 0;
size_t len = 0;
uint8_t *value = NULL;
int degrees;
char *temp = NULL;
temp = get_cmd_output("cat /sys/devices/virtual/thermal/thermal_zone0/temp");
if (!temp) {
error = BT_ATT_ERROR_INVALID_HANDLE;
goto done;
}
degrees = atoi(temp)/1000;
value = (uint8_t *) &degrees;
len = 1;
done:
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
gatt_db_attribute_read_result(attrib, id, error, value, len);
free(temp);
}
static bool temperature_notify_cb(void *user_data)
{
struct notify_cb_t *notify_data = user_data;
struct server *server = notify_data->server;
char *temp = NULL;
uint8_t notify_value;
int current_temp = 0;
temp = get_cmd_output("cat /sys/devices/virtual/thermal/thermal_zone0/temp");
if (!temp) {
return false;
}
current_temp = atoi(temp)/1000;
notify_value = (current_temp >= temp_threshold) ? 0x01 : 0x00;
free(temp);
/* If the Temperatue is higher than a threshold send notification */
if (notify_value) {
printf("Send Temperature notification: %d degrees\n", current_temp);
bt_gatt_server_send_notification(server->gatt,
notify_data->handle,
&notify_value,
sizeof(notify_value));
}
return true;
}
static void current_time_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
uint8_t error;
uint8_t *value = NULL;
size_t len = 0;
char *timestamp = NULL;
timestamp = get_cmd_output("date +%s");
if (!timestamp) {
error = BT_ATT_ERROR_INVALID_HANDLE;
goto done;
}
len = strlen(timestamp) + 1;
if (offset > len) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
len -= offset;
value = len ? (uint8_t *) &timestamp[offset] : NULL;
error = 0;
done:
syslog(LOG_DEBUG, "%s called, return: %s (error: 0x%x)\n", __func__, value, error);
gatt_db_attribute_read_result(attrib, id, error, value, len);
free(timestamp);
}
static void current_time_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
uint8_t error = 0;
int status = 0;
char *new_timestamp = NULL;
char *cmd = NULL;
/* Do not allow to partially write the Time (offset must be 0) */
if (offset) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
/*
* We need to terminate the string, unfortunately 'value' is read only,
* so copy it all over.
*/
new_timestamp = malloc(len + 1);
if (!new_timestamp) {
error = BT_ATT_ERROR_INVALID_HANDLE;
goto done;
}
memcpy(new_timestamp, value, len);
new_timestamp[len] = '\0';
if (asprintf(&cmd, "echo %s", new_timestamp) < 0 ||
safe_execute(cmd, &status) < 0 || status) {
syslog(LOG_ERR, "Failed to set Time, error(%d)\n", status);
error = BT_ATT_ERROR_INVALID_HANDLE;
}
done:
syslog(LOG_DEBUG, "%s called, received: %s (error: 0x%x)\n", __func__, new_timestamp, error);
gatt_db_attribute_write_result(attrib, id, error);
free(cmd);
free(new_timestamp);
}
struct {
uint16_t uuid;
const char *description;
const uint8_t *format;
gatt_db_read_t read_cb;
gatt_db_write_t write_cb;
timeout_func_t notify_cb;
} const bt_characteristics[] = {
{
UUID_GATT_CHARACTERISTIC_CURRENT_TIME,
"Time in String format",
FORMAT_UTF_8,
current_time_read_cb,
current_time_write_cb,
NULL
},
{
UUID_GATT_CHARACTERISTIC_TEMPERATURE_MEASUREMENT,
"Temperature in Integer format",
FORMAT_8_BIT_INT,
temperature_read_cb,
NULL,
temperature_notify_cb
},
};
/*
* For each characteristic we create 4 handles:
* - Characteristic Declaration
* - Characteristic Value Declaration
* - Descriptor Declaration:
* - Characteristic User Description
* - Characteristic Presentation Format
*/
#define HANDLES_PER_CHARACTERISTIC 4
static void populate_device_information_service(struct server *server)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service;
int i,num_handles;
/* Add Device Information Service */
server->n_characteristics = ARRAY_SIZE(bt_characteristics);
bt_uuid16_create(&uuid, UUID_DEVICE_INFORMATION_SERVICE);
/* Add one handle for the Service Declaration */
num_handles = (server->n_characteristics * HANDLES_PER_CHARACTERISTIC) + 1;
service = gatt_db_add_service(server->db, &uuid, true, num_handles);
server->cell_handle = gatt_db_attribute_get_handle(service);
server->timeout_handlers = calloc(server->n_characteristics, sizeof(server->timeout_handlers[0]));
if (!server->timeout_handlers) {
syslog(LOG_ERR, "Out of memory, calloc failed!");
exit(2);
}
/* Add all characteristics */
for (i = 0; i < server->n_characteristics; i++) {
uint32_t permissions = 0;
uint8_t properties = 0;
struct gatt_db_attribute *desc_att, *gatt_handle;
bt_uuid16_create(&uuid, bt_characteristics[i].uuid);
if (bt_characteristics[i].read_cb)
properties |= BT_GATT_CHRC_PROP_READ;
if (bt_characteristics[i].write_cb) {
properties |= BT_GATT_CHRC_PROP_WRITE;
permissions |= BT_ATT_PERM_WRITE;
}
if (bt_characteristics[i].notify_cb)
properties |= BT_GATT_CHRC_PROP_NOTIFY;
permissions |= BT_ATT_PERM_READ;
/* Characteristic */
gatt_handle = gatt_db_service_add_characteristic(
service,
&uuid,
permissions,
properties,
bt_characteristics[i].read_cb,
bt_characteristics[i].write_cb,
server);
/* Characteristic User Description */
bt_uuid16_create(&uuid, GATT_CHARAC_USER_DESC_UUID);
desc_att = gatt_db_service_add_descriptor(service,
&uuid,
BT_ATT_PERM_READ,
NULL,
NULL,
server);
gatt_db_attribute_write(desc_att,
0,
(const uint8_t *) bt_characteristics[i].description,
strlen(bt_characteristics[i].description) + 1,
BT_ATT_OP_READ_REQ,
NULL,
confirm_write,
NULL);
/* Charateristic Presentation Format */
bt_uuid16_create(&uuid, GATT_CHARAC_FMT_UUID);
desc_att = gatt_db_service_add_descriptor(service,
&uuid,
BT_ATT_PERM_READ,
NULL,
NULL,
server);
gatt_db_attribute_write(desc_att,
0,
bt_characteristics[i].format,
FORMAT_BYTES,
BT_ATT_OP_READ_REQ,
NULL,
confirm_write,
NULL);
/* Notification timed function */
if (bt_characteristics[i].notify_cb) {
struct notify_cb_t *notify_data = malloc(sizeof(*notify_data));
if (!notify_data) {
syslog(LOG_ERR, "Out of memory, calloc failed!");
exit(2);
}
notify_data->server = server;
notify_data->handle = gatt_db_attribute_get_handle(gatt_handle);
server->timeout_handlers[i] = timeout_add(NOTIFICATION_TIMEOUT_MS, bt_characteristics[i].notify_cb, notify_data, destroy_notify_data);
/* Manually call the notify once to cache a first value */
bt_characteristics[i].notify_cb(notify_data);
}
}
gatt_db_service_set_active(service, true);
}
/* Main program */
static void populate_db(struct server *server)
{
populate_gap_service(server);
populate_gatt_service(server);
populate_device_information_service(server);
}
static struct server *server_create(int fd, uint16_t mtu)
{
struct server *server;
server = new0(struct server, 1);
if (!server) {
syslog(LOG_ERR, "Failed to allocate memory for server\n");
return NULL;
}
server->att = bt_att_new(fd, false);
if (!server->att) {
syslog(LOG_ERR, "Failed to initialze ATT transport layer\n");
goto fail;
}
if (!bt_att_set_close_on_unref(server->att, true)) {
syslog(LOG_ERR, "Failed to set up ATT transport layer\n");
goto fail;
}
if (!bt_att_register_disconnect(server->att, att_disconnect_cb, NULL,
NULL)) {
syslog(LOG_ERR, "Failed to set ATT disconnect handler\n");
goto fail;
}
server->fd = fd;
server->db = gatt_db_new();
if (!server->db) {
syslog(LOG_ERR, "Failed to create GATT database\n");
goto fail;
}
server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0);
if (!server->gatt) {
syslog(LOG_ERR, "Failed to create GATT server\n");
goto fail;
}
if (verbose) {
bt_att_set_debug(server->att, debug_cb, "att: ", NULL);
bt_gatt_server_set_debug(server->gatt, debug_cb,
"server: ", NULL);
}
/* bt_gatt_server already holds a reference */
populate_db(server);
return server;
fail:
gatt_db_unref(server->db);
bt_att_unref(server->att);
free(server);
return NULL;
}
static void server_destroy(struct server *server)
{
int i;
for (i = 0; i < server->n_characteristics; i++)
if (server->timeout_handlers[i])
timeout_remove(server->timeout_handlers[i]);
free(server->timeout_handlers);
bt_gatt_server_unref(server->gatt);
gatt_db_unref(server->db);
bt_att_unref(server->att);
free(server);
}
static void usage(void)
{
printf("ble-gatt-server-app\n");
printf("Usage:\n\tble-gatt-server-app [options]\n");
printf("Options:\n"
"\t-i, --index <id>\t\tSpecify adapter index, e.g. hci0\n"
"\t-t, --threshold <temp>\t\tThe temperature threshold to send notification\n"
"\t-m, --mtu <mtu>\t\t\tThe ATT MTU to use\n"
"\t-v, --verbose\t\t\tEnable extra logging\n"
"\t-h, --help\t\t\tDisplay help\n");
}
static struct option main_options[] = {
{ "index", 1, 0, 'i' },
{ "threshold", 1, 0, 't' },
{ "mtu", 1, 0, 'm' },
{ "verbose", 0, 0, 'v' },
{ "help", 0, 0, 'h' },
{ }
};
static int l2cap_le_att_listen_and_accept(bdaddr_t *src, int sec,
uint8_t src_type)
{
int sk, nsk;
struct sockaddr_l2 srcaddr, addr;
socklen_t optlen;
struct bt_security btsec;
char ba[18];
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (sk < 0) {
syslog(LOG_ERR, "Failed to create L2CAP socket");
return -1;
}
/* Set up source address */
memset(&srcaddr, 0, sizeof(srcaddr));
srcaddr.l2_family = AF_BLUETOOTH;
srcaddr.l2_cid = htobs(ATT_CID);
srcaddr.l2_bdaddr_type = src_type;
bacpy(&srcaddr.l2_bdaddr, src);
if (bind(sk, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) {
syslog(LOG_ERR, "Failed to bind L2CAP socket");
goto fail;
}
/* Set the security level */
memset(&btsec, 0, sizeof(btsec));
btsec.level = sec;
if (setsockopt(sk, SOL_BLUETOOTH, BT_SECURITY, &btsec,
sizeof(btsec)) != 0) {
syslog(LOG_ERR, "Failed to set L2CAP security level\n");
goto fail;
}
if (listen(sk, 10) < 0) {
syslog(LOG_ERR, "Listening on socket failed");
goto fail;
}
printf("Started listening on ATT channel. Waiting for connections\n");
memset(&addr, 0, sizeof(addr));
optlen = sizeof(addr);
nsk = accept(sk, (struct sockaddr *) &addr, &optlen);
if (nsk < 0) {
syslog(LOG_ERR, "Accept failed");
goto fail;
}
ba2str(&addr.l2_bdaddr, ba);
printf("Connect from %s\n", ba);
close(sk);
return nsk;
fail:
close(sk);
return -1;
}
static void signal_cb(int signum, void *user_data)
{
switch (signum) {
case SIGINT:
case SIGTERM:
keep_running = false;
mainloop_quit();
break;
default:
break;
}
}
int main(int argc, char *argv[])
{
const int sec = BT_SECURITY_LOW;
const uint8_t src_type = BDADDR_LE_PUBLIC;
int opt;
bdaddr_t src_addr;
int dev_id = -1;
uint16_t mtu = 0;
sigset_t mask;
setlogmask (LOG_UPTO (LOG_NOTICE));
while ((opt = getopt_long(argc, argv, "+hvrm:t:i:",
main_options, NULL)) != -1) {
switch (opt) {
case 'h':
usage();
return EXIT_SUCCESS;
case 'v':
verbose = true;
setlogmask (LOG_UPTO (LOG_DEBUG));
break;
case 'm': {
int arg;
arg = atoi(optarg);
if (arg <= 0) {
syslog(LOG_ERR, "Invalid MTU: %d\n", arg);
return EXIT_FAILURE;
}
if (arg > UINT16_MAX) {
syslog(LOG_ERR, "MTU too large: %d\n", arg);
return EXIT_FAILURE;
}
mtu = (uint16_t) arg;
break;
}
case 't': {
int arg;
arg = atoi(optarg);
if (arg <= 0) {
syslog(LOG_ERR, "Invalid Temperature threshold: %d\n", arg);
return EXIT_FAILURE;
}
if (arg > UINT8_MAX) {
syslog(LOG_ERR, "Temperature threshold too large: %d\n", arg);
return EXIT_FAILURE;
}
temp_threshold = arg;
break;
}
case 'i':
dev_id = hci_devid(optarg);
if (dev_id < 0) {
syslog(LOG_ERR, "Invalid adapter");
return EXIT_FAILURE;
}
break;
default:
syslog(LOG_ERR, "Invalid option: %c\n", opt);
return EXIT_FAILURE;
}
}
argc -= optind;
argv -= optind;
optind = 0;
openlog("ble-gatt-server-app", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
printf("Running GATT server\n");
if (argc) {
usage();
return EXIT_SUCCESS;
}
while (keep_running == true) {
int fd;
struct server *server;
if (dev_id == -1) {
bacpy(&src_addr, BDADDR_ANY);
}
else if (hci_devba(dev_id, &src_addr) < 0) {
syslog(LOG_ERR, "Adapter not available");
return EXIT_FAILURE;
}
fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type);
if (fd < 0) {
syslog(LOG_ERR, "Failed to accept L2CAP ATT connection\n");
return EXIT_FAILURE;
}
mainloop_init();
server = server_create(fd, mtu);
if (!server) {
close(fd);
return EXIT_FAILURE;
}
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
mainloop_set_signal(&mask, signal_cb, NULL, NULL);
mainloop_run();
/* remove the application signals */
sigdelset(&mask, SIGTERM);
sigdelset(&mask, SIGINT);
sigemptyset(&mask);
server_destroy(server);
close(fd);
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,82 @@
/*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2014 Google Inc.
* Copyright (C) 2019 Digi International Inc.
*
*
* 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.
*
*/
/*
* A full list of officially adopted BLE services can be seen on
* https://www.bluetooth.com/specifications/gatt/services/
*/
#define UUID_GAP_SERVICE 0x1800 /* Generic Access Service (GAP) */
#define UUID_GATT_SERVICE 0x1801 /* Generic Attribute Profile Service (GATT) */
#define UUID_DEVICE_INFORMATION_SERVICE 0x180A /* Device Information Service */
/*
* See BT specification to list all official characteristics UUID
* from https://www.bluetooth.com/specifications/gatt/characteristics/
*/
#define UUID_GATT_CHARACTERISTIC_CURRENT_TIME 0x2A2B
#define UUID_GATT_CHARACTERISTIC_TEMPERATURE_MEASUREMENT 0x2A1C
/*
* See BT specification to list all GATT Bluetooth Format types
* from https://www.bluetooth.com/specifications/assigned-numbers/format-types/
*/
#define BLE_GATT_CPF_FORMAT_UINT8 0x04 /* Unsigned 8-bit integer */
#define BLE_GATT_CPF_FORMAT_UTF8S 0x19 /* UTF-8 string */
/*
* See BT specification to list all GATT Bluetooth Units
* from https://www.bluetooth.com/specifications/assigned-numbers/units/
*/
#define BLE_GATT_CPF_UNIT_DEGREE_CELSIUS 0x27,0x2F /* Celsius temperature (degree Celsius) */
#define BLE_GATT_CPF_UNIT_TIME_SECONDS 0x27,0x03 /* Time (second) */
/*
* See BT specification to list all GATT Bluetooth Namespace Descriptions
* from https://www.bluetooth.com/specifications/assigned-numbers/gatt-namespace-descriptors/
*/
#define BLE_GATT_SIG_NAMESPACE 0x01
#define BLE_GATT_SIG_DESCRIPTOR_UNKNOWN 0x00,0x00
#define BLE_GATT_SIG_DESCRIPTOR_INTERNAL 0x01,0x0F
/*
* See BT specification 4.2, section 3.3.3.5
* for Characteristic Presentation Format descriptor:
* - Format (1 octet)
* - Exponent (1 octet)
* - Unit (2 octet)
* - Name Space (1 octet)
* - Description (2 octet)
*/
#define FORMAT_BYTES 7
const uint8_t FORMAT_8_BIT_INT[] = {
BLE_GATT_CPF_FORMAT_UINT8,
0x00,
BLE_GATT_CPF_UNIT_DEGREE_CELSIUS,
BLE_GATT_SIG_NAMESPACE,
BLE_GATT_SIG_DESCRIPTOR_INTERNAL
};
const uint8_t FORMAT_UTF_8[] = {
BLE_GATT_CPF_FORMAT_UTF8S,
0x00,
BLE_GATT_CPF_UNIT_TIME_SECONDS,
BLE_GATT_SIG_NAMESPACE,
BLE_GATT_SIG_DESCRIPTOR_UNKNOWN
};

View File

@ -0,0 +1,173 @@
/*
* Copyright (c) 2017 Digi International Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/wait.h>
#include <unistd.h>
#include "subprocess.h"
/**
* Convert a command string to an array of pointers to null-terminated strings
*
* @param command the command to tokenize
* @return strings array on sucess, NULL on error
*/
static char **tokenize_command(char *command)
{
const char *delim = " ";
char **argv;
char *token, *buf;
int c = 1;
argv = calloc(c, sizeof(char *));
if (!argv)
goto done;
/* 'strtok' changes the original string, so use a copy we can free */
buf = strdup(command);
token = strtok(buf, delim);
argv[c - 1] = token ? strdup(token) : NULL;
while (argv[c - 1]) {
void *tmp;
c++;
tmp = realloc(argv, c * sizeof(char *));
if (!tmp)
free(argv);
argv = tmp;
if (!argv)
break;
token = strtok(NULL, delim);
argv[c - 1] = token ? strdup(token) : NULL;
}
free(buf);
done:
return argv;
}
/**
* Safely fork a child process and execute the given command with PATH lookup.
*
* @param command the command to execute
* @param child_status return child status of executable
* @return 0 on success, -1 on error with errno set
*/
int safe_execute(char *command, int *const child_status)
{
int status = 0, i;
int sub_status = 0;
pid_t pid = 0;
pid_t sub_pid = 0;
struct sigaction const action_ignore = {.sa_handler = SIG_IGN };
char **argv = tokenize_command(command);
if ((NULL == command) || (NULL == argv) || (NULL == child_status)) {
status = EINVAL;
goto done;
}
/* Initialize child_status */
*child_status = -1;
/*
* Fork a child process to do the execvp() call because that call will not
* return if it works. We're doing it this way to try to make this
* function as "nice" to the calling application as possible by not
* inhibiting interrupts as can happen if the system() call is used (plus
* that utility can have weird side-effects if the calling application has
* suid or sgid privileges set).
*/
pid = fork();
if (pid < 0) {
status = errno;
syslog(LOG_ERR, "[%s:%d] could not fork process: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
goto done;
}
/*
* If we're now the child process, do the execvp(), replacing our process
* with the called process. SIGINT and SIGTERM are ignored in child process
* as we don't have any good ways of cleaning up the subprocess after a
* signal.
*/
if (0 == pid) {
if (sigaction(SIGINT, &action_ignore, NULL) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error ignoring SIGINT: %s (%d)\n\n",
__func__, __LINE__, strerror(errno), errno);
}
if (sigaction(SIGTERM, &action_ignore, NULL) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error ignoring SIGTERM: %s (%d)\n\n",
__func__, __LINE__, strerror(errno), errno);
}
if (execvp(argv[0], (char *const *)argv) < 0) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error executing child process: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
}
_exit(127);
}
/*
* Otherwise, if we're the parent, wait for the child process to terminate.
* If waitpid returns due to a signal (EINTR), ignore the error and
* continue to wait.
*/
do {
errno = 0;
sub_pid = waitpid(pid, &sub_status, 0);
if ((sub_pid < 0) && (EINTR != errno)) {
status = errno;
syslog
(LOG_ERR, "[%s:%d] error returned by waitpid: %s (%d)\n",
__func__, __LINE__, strerror(errno), errno);
goto done;
}
if (WIFEXITED(sub_status)) {
*child_status = WEXITSTATUS(sub_status);
}
} while ((errno == EINTR) || (!WIFEXITED(sub_status)));
done:
/* Free resources */
i = 0;
while (argv && argv[i]) {
free(argv[i]);
i++;
}
free(argv);
errno = status;
return (status == 0) ? 0 : -1;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2017 Digi International Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef SUBPROCESS_H_
#define SUBPROCESS_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* Safely fork a child process and execute the given command with PATH lookup.
*
* @param command the command to execute
* @param child_status return child status of executable
* @return 0 on success, -1 on error with errno set
*/
int safe_execute(char *command, int *const child_status);
#ifdef __cplusplus
}
#endif
#endif /* SUBPROCESS_H_ */