blob: 4744f43ffeba65d04e782c24849a69d9ce357285 [file] [log] [blame]
// +build linux,!android
#include "bt.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#define EIR_FLAGS 0x01 /* flags */
#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */
#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */
#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */
#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */
#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */
#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT 0x08 /* shortened local name */
#define EIR_NAME_COMPLETE 0x09 /* complete local name */
#define EIR_TX_POWER 0x0A /* transmit power level */
#define EIR_DEVICE_ID 0x10 /* device ID */
// Timeout for all hci requests, in milliseconds.
static const int kTimeoutMs = 1000;
static const int kMaxAddrStrSize = 18;
const int kMaxLEPayloadSize = 26;
const int kMaxChannel = 30;
const int kMaxDevices = 5;
char* bt_open_device(int dev_id, int* dd, char** name, char** local_address) {
char* err = NULL;
int sock;
struct hci_dev_req dev_req;
struct hci_dev_info di;
bdaddr_t loc_addr;
if (dev_id < 0) {
asprintf(err, "can't pass negative device id for bt_open_device().");
return err;
}
// Open HCI socket.
if ((sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
asprintf(&err, "can't open HCI socket:%d[%s]", errno, strerror(errno));
return err;
}
// Get device's name.
di.dev_id = dev_id;
if (ioctl(sock, HCIGETDEVINFO, (void *)&di) < 0) {
asprintf(&err, "can't get device info:%d[%s]", errno, strerror(errno));
close(sock);
return err;
}
*name = (char*) malloc(strlen(di.name) * sizeof(char));
strcpy(*name, di.name);
// Try to open the specified device.
ioctl(sock, HCIDEVUP, dev_id);
*dd = hci_open_dev(dev_id);
if (*dd < 0) {
asprintf(&err, "can't open device %d:%d[%s]", dev_id, errno, strerror(errno));
close(sock);
return err;
}
// NOTE(spetrovic): We need to enable page scanning on the device for
// RFCOMM connections to work. Since this requires root access, it will
// probably need to be done elsewhere (e.g., 'sudo hciconfig hci0 pscan').
// Get device's local MAC address.
hci_devba(dev_id, &loc_addr);
*local_address = (char*) malloc(kMaxAddrStrSize * sizeof(char));
ba2str(&loc_addr, *local_address);
return NULL;
}
char* bt_bind(int sock, char** local_address, int* channel) {
char* err = NULL;
int dev_id, dd;
struct sockaddr_rc addr = { 0 };
if (*local_address == NULL) {
char* name = NULL;
// Find the first available device.
for (dev_id = 0; dev_id < kMaxDevices; dev_id++) {
if ((err = bt_open_device(dev_id, &dd, &name, local_address)) != NULL) {
free(err);
continue;
}
bt_close_device(dd);
assert(*local_address != NULL);
if ((err = bt_bind(sock, local_address, channel)) != NULL) {
free(err);
free(*local_address);
continue;
}
return NULL;
}
asprintf(&err, "can't find an available bluetooth device");
return err;
} else if (*channel == 0) {
// Find the first available channel.
for (*channel = 1; *channel < kMaxChannel; (*channel)++) {
if ((err = bt_bind(sock, local_address, channel)) != NULL) {
free(err);
continue;
}
return NULL;
}
asprintf(&err, "can't find an available bluetooth channel");
return err;
} else { // *local_address != NULL && *channel > 0
addr.rc_family = AF_BLUETOOTH;
str2ba(*local_address, &addr.rc_bdaddr);
addr.rc_channel = (uint8_t) *channel;
if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
asprintf(&err, "can't bind to socket %d, addr %s, channel %d, error: %d[%s]",
sock, *local_address, *channel, errno, strerror(errno));
return err;
}
return NULL;
}
}
char* bt_accept(int sock, int* fd, char** remote_address) {
char* err = NULL;
struct sockaddr_rc remote_addr;
socklen_t opt = sizeof(remote_addr);
if ((*fd = accept(sock, (struct sockaddr *)&remote_addr, &opt)) < 0) {
asprintf(&err, "error accepting connection on socket %d, error: %d[%s]",
sock, errno, strerror(errno));
return err;
}
*remote_address = (char*) malloc(kMaxAddrStrSize * sizeof(char));
ba2str(&remote_addr.rc_bdaddr, *remote_address);
return NULL;
}
char* bt_connect(int sock, const char* remote_address, int remote_channel) {
char* err = NULL;
struct sockaddr_rc remote_addr = { 0 };
remote_addr.rc_family = AF_BLUETOOTH;
str2ba(remote_address, &remote_addr.rc_bdaddr);
remote_addr.rc_channel = (uint8_t) remote_channel;
if (connect(sock, (struct sockaddr*) &remote_addr, sizeof(remote_addr)) < 0) {
asprintf(&err, "can't connect to remote address %s and channel %d "
"on socket %d: %d[%s]", remote_address, remote_channel,
sock, errno, strerror(errno));
return err;
}
return NULL;
}
char* bt_close_device(int dd) {
char* err = NULL;
if (hci_close_dev(dd) < 0) {
asprintf(&err, "can't close device with dd: %d, error: %d[%s]", dd, errno, strerror(errno));
return err;
}
return NULL;
}
char* bt_enable_le_advertising(int dd, int enable) {
char* err = NULL;
struct hci_request req;
le_set_advertise_enable_cp adv_enable_cp;
uint8_t status;
memset(&adv_enable_cp, 0, sizeof(adv_enable_cp));
adv_enable_cp.enable = enable;
memset(&req, 0, sizeof(req));
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISE_ENABLE;
req.cparam = &adv_enable_cp;
req.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
if (hci_send_req(dd, &req, kTimeoutMs) < 0) {
asprintf(&err,
"can't enable/disable advertising for dd: %d, status: %d, error: %d",
dd, status, errno);
return err;
}
return NULL;
}
char* bt_start_le_advertising(int dd, int adv_interval_ms) {
char* err = NULL;
struct hci_request req;
le_set_advertising_parameters_cp adv_params_cp;
le_set_advertise_enable_cp adv_enable_cp;
uint8_t status;
// Set advertising params.
memset(&adv_params_cp, 0, sizeof(adv_params_cp));
adv_params_cp.min_interval = adv_interval_ms;
adv_params_cp.max_interval = adv_interval_ms;
adv_params_cp.advtype = 0x00; // Connectable undirected advertising.
adv_params_cp.chan_map = 7;
memset(&req, 0, sizeof(req));
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
req.cparam = &adv_params_cp;
req.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
if (hci_send_req(dd, &req, kTimeoutMs) < 0) {
asprintf(&err,
"can't set advertising params for dd: %d, status: %d, error: %d",
dd, status, errno);
return err;
}
// Start advertising.
return bt_enable_le_advertising(dd, 1);
}
char* bt_set_le_advertising_payload(int dd, char* adv_payload) {
char* err = NULL;
int idx;
struct hci_request req;
le_set_advertising_data_cp adv_data_cp;
uint8_t status;
if (strlen(adv_payload) > kMaxLEPayloadSize) {
asprintf(&err, "payload too big");
return err;
}
// Set advertising data.
memset(&adv_data_cp, 0, sizeof(adv_data_cp));
idx = 0;
adv_data_cp.data[idx++] = 2;
adv_data_cp.data[idx++] = EIR_FLAGS;
adv_data_cp.data[idx++] = 0x06; // general discoverable+BR/EDR Not Supported
adv_data_cp.data[idx++] = strlen(adv_payload) + 1;
adv_data_cp.data[idx++] = EIR_NAME_COMPLETE;
memcpy(&adv_data_cp.data[idx], adv_payload, strlen(adv_payload));
idx += strlen(adv_payload);
adv_data_cp.length = idx;
memset(&req, 0, sizeof(req));
req.ogf = OGF_LE_CTL;
req.ocf = OCF_LE_SET_ADVERTISING_DATA;
req.cparam = &adv_data_cp;
req.clen = LE_SET_ADVERTISING_DATA_CP_SIZE;
req.rparam = &status;
req.rlen = 1;
if (hci_send_req(dd, &req, kTimeoutMs) < 0) {
asprintf(&err,
"can't set advertising data for dd: %d, status: %d, error: %d\n",
dd, status, errno);
return err;
}
return NULL;
}
char* bt_stop_le_advertising(int dd) {
return bt_enable_le_advertising(dd, 0);
}
char* bt_parse_le_meta_event(
void* data, char** remote_addr, char** remote_name, int* rssi, int* done) {
char *err, *ptr, *end;
evt_le_meta_event* meta_event;
le_advertising_info* adv_info;
*done = 0;
meta_event = (evt_le_meta_event*) (data + 1 + HCI_EVENT_HDR_SIZE);
if (meta_event->subevent == 0x01 /* LE Connection Complete Event */) {
// This event is triggered when scan is disabled.
*done = 1;
return NULL;
} else if (meta_event->subevent != 0x02 /* LE Advertising Report Event */) {
asprintf(&err, "wrong event type: %d", meta_event->subevent);
return err;
}
adv_info = (le_advertising_info*) (meta_event->data + 1);
*remote_addr = malloc(kMaxAddrStrSize * sizeof(char));
ba2str(&adv_info->bdaddr, *remote_addr);
*rssi = *((int8_t*) adv_info->data + adv_info->length);
// Extract name.
// Max possible advertising data length, as defined by the standard.
// The actual maximum name length was observed to be less than that, but
// this value will do.
const int kMaxNameLength = 31;
*remote_name = malloc(kMaxNameLength * sizeof(char));
memset(*remote_name, 0, kMaxNameLength);
ptr = adv_info->data;
end = adv_info->data + adv_info->length;
// Go through all advertising data packets in the response.
while (ptr < end) {
int adv_data_length = *(ptr++); // includes adv_data_type below.
int adv_data_type;
if (adv_data_length == 0) { // end of response.
break;
} else if ((ptr + adv_data_length) > end) {
// Illegal adv_data length.
break;
}
adv_data_type = *(ptr++);
switch (adv_data_type) {
case EIR_NAME_SHORT:
case EIR_NAME_COMPLETE:
if (adv_data_length - 1 > kMaxNameLength) {
// Illegal name length.
break;
}
memcpy(*remote_name, ptr, adv_data_length - 1);
break;
}
ptr += adv_data_length - 1;
}
return NULL;
}