blob: 40a25e5ac1a2befb0b552e6085e18a10a39e27bc [file] [log] [blame]
//todo: only one LE connection attempt can exist at a time
//our "extra" param for event filtering canhandle other events to as long as we're willing to have a giant switch-case... and check against it in all structs
//todo: always request remote versions & features before using them (eg: le encrypt support) and before telling anyone a link is up!
//todo: always set flush timout to infinite
//todo: since only one LE connection attempt can exist at a time, enqueue others...
//todo: when link up do not upcall to l2cap until we have all features and versions from the other side gotten already!
//todo: The LE_Create_Connection_Cancel command is used to cancel the LE_Create_Connection command. This command shall only be issued after the LE_Create_Connection command has been issued, a Command Status event has been received for the LE Create Connection command and before the LE Connection Complete event
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "workQueue.h"
#include "vendorLib.h"
#include "hci_int.h"
#include "persist.h"
#include "config.h"
#include "l2cap.h"
#include "aapi.h"
#include "util.h"
#include "uniq.h"
#include "hci.h"
#include "log.h"
#include "mt.h"
#define MAX_FTR_PAGES 4 /* we'll not get any more than this */
#define HCI_STAT_GOING_DOWN 0xFF
struct hciCmdHdr {
uint16_t opcode;
uint8_t paramLen;
} __packed;
#define CMD_MAKE_OPCODE(ogf, ocf) ((((uint16_t)((ogf) & 0x3f)) << 10) | ((ocf) & 0x03ff))
#define CMD_GET_OGF(opcode) (((opcode) >> 10) & 0x3f)
#define CMD_GET_OCF(opcode) ((opcode) & 0x03ff)
struct hciAclHdr {
uint16_t hdr;
uint16_t len;
} __packed;
#define ACL_HDR_MASK_CONN_ID 0x0FFF
#define ACL_HDR_MASK_PB 0x3000
#define ACL_HDR_MASK_BC 0xC000
#define ACL_HDR_PB_FIRST_NONAUTO 0x0000
#define ACL_HDR_PB_CONINUED 0x1000
#define ACL_HDR_PB_FIRST_AUTO 0x2000
#define ACL_HDR_PB_COMPLETE 0x3000
struct hciScoHdr {
uint16_t hdr;
uint8_t len;
} __packed;
#define SCO_HDR_MASK_CONN_ID 0x0FFF
#define SCO_HDR_MASK_STATUS 0x3000
#define SCO_STATUS_ALL_OK 0x0000
#define SCO_STATUS_UNKNOWN 0x1000
#define SCO_STATUS_NO_DATA 0x2000
#define SCO_STATUS_SOME_DATA 0x3000
struct hciEvtHdr {
uint8_t code;
uint8_t len;
} __packed;
#define ACL_CONN_ID_INVALID (ACL_HDR_MASK_CONN_ID + 2)
/* returns true if this is the event we wanted, false else. Do not block! If evt is passed as NULL, stack is shutting down. Do not try to enqueue/dequeue handlers here either */
typedef bool (*hciEvtCbk)(const struct hciEvtHdr *evt, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID);
typedef void (*hciSimpleCbk)(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt);
struct hciEvtWaitState {
uniq_t evtWaitStateID; /* used to cancel THIS waiter */
uniq_t forCmdID; /* used to cancel ALL waiters for this CMD */
struct hciEvtWaitState *next;
struct hciEvtWaitState *prev;
hciEvtCbk cbk;
void *cbkData;
uint16_t extra; /* cmd for cmd complete/statue, subever for LE */
uint8_t evtType;
bool persistent; /* do not delete when it fires */
};
struct hciEvtWaitDescr {
uint8_t evtType; /* == 0 -> not valid/not enabled */
uint16_t extra;
hciEvtCbk cbk;
void *cbkData;
bool persistent;
};
struct hciEvtWaitDescrWithID {
struct hciEvtWaitDescr descr;
uniq_t id;
};
struct hciCmdSubmitWithCompleteSyncData {
sem_t *sem;
void *evtBuf;
uint8_t evtBufSz;
bool goingDown;
};
struct hciCmdSendWorkItem {
uniq_t cmdID;
uint8_t numEventWaiters;
sg cmd;
/* even handlers */
struct hciEvtWaitDescrWithID ewds[];
};
#define CBK_WORK_ITEM_DONE_CBK 0
#define CBK_WORK_ITEM_SIMPLE_CBK 1
#define CBK_WORK_EDR_DISC_DEV 2
#define CBK_WORK_EDR_DISC_NAME 3
#define CBK_WORK_LE_DISC_DEV 4
#define CBK_WORK_FLUSH 5
#define CBK_WORK_CONN_UP 6
#define CBK_WORK_CONN_DOWN 7
#define CBK_WORK_PARAMS_CHANGE 8
#define CBK_WORK_ENCR_CHANGE 9
#define CBK_WORK_ACL_DATA 10
struct hciCbkWorkItem {
uint8_t cbkWorkItemType;
union {
struct {
hciOpDoneCbk cbk;
void *cbkData;
uint8_t status;
} done;
struct {
hciSimpleCbk cbk;
void *cbkData1;
void *cbkData2;
bool stackGoingDown;
bool haveEvt;
} simple;
struct {
struct bt_addr addr;
uint32_t devCls;
struct hciNameGetInfo fng;
int8_t rssi;
uint8_t eirLen;
uint8_t eir[];
} edrDev;
struct {
struct bt_addr addr;
hciDeviceDiscoveredEdrNameCbk cbk;
void *cbkData;
uint8_t nameReqState;
char name[];
} edrName;
struct {
struct bt_addr addr;
uint8_t advType;
int8_t rssi;
uint8_t advLen;
uint8_t adv[];
} leDev;
struct {
sem_t *sem;
} flush;
struct {
hci_conn_t conn;
struct bt_addr peerAddr;
struct bt_addr selfAddr;
bool isMaster;
bool isEncrypted;
bool isMitmSafe;
} connUp;
struct {
hci_conn_t conn;
} connDown;
struct {
hci_conn_t conn;
bool success;
uint16_t interval;
uint16_t latency;
uint16_t timeout;
} leParamChange;
struct {
hci_conn_t conn;
bool nowEncrypted;
bool isMitmSafe;
} encrChange;
struct {
hci_conn_t conn;
sg packet;
bool first;
} aclDataRx;
};
};
struct hciBacklogItemHdr {
uint32_t len;
uint16_t aclHdr;
} __packed;
struct hciSimpleCbkData {
hciSimpleCbk cbk; /* may be null */
void *cbkData1;
void *cbkData2;
uint8_t expectedParamLenMin;
uint8_t expectedParamLenMax;
uint8_t wantEventItself :1;
uint8_t complete :1;
};
struct hciAclConn {
struct hciAclConn *next;
struct hciAclConn *prev;
struct bt_addr peerAddr;
struct bt_addr selfAddr; /* XXX */
hci_conn_t handle;
uint16_t id;
bool isMaster;
bool encrypted;
bool mitmSafe;
uint8_t state; /* CONN_STATE_* */
bool pinPending;
bool sspPending;
sg rxBacklog; /* data we RXed during configuration stage */
uint32_t outstandingPackets;
union {
struct {
uint64_t ftrs;
uint16_t interval;
uint16_t latency;
uint16_t timeout;
uint8_t mca;
} le;
struct {
uint64_t ftrs[MAX_FTR_PAGES];
uint8_t btVer;
} edr;
};
};
#define CONN_STATE_WAIT 0
#define CONN_STATE_CFG 1
#define CONN_STATE_RUNNING 2
#define CONN_STATE_DELETING 3
#define CONN_CFG_STEP_LE 0x0000
#define CONN_CFG_STEP_EDR 0x1000
struct hciDiscoverNameState {
struct hciDiscoverNameState *next;
hciDeviceDiscoveredEdrNameCbk cbk;
void *cbkData;
struct bt_addr addr;
uniq_t handle;
};
/* useful constants */
static const char *mBtVers[] = {"1.0b", "1.1", "1.2", "2.0", "2.1", "3.0", "4.0", "4.1"};
/* our state */
static pthread_mutex_t mCmdWaitLock = PTHREAD_MUTEX_INITIALIZER;
static struct hciEvtWaitState *mEvtWaitHead = NULL;
static struct hciEvtWaitState *mEvtWaitTail = NULL;
static pthread_mutex_t mCmdLock = PTHREAD_MUTEX_INITIALIZER;
static sem_t mCmdSendSem;
static struct workQueue* mCmdsSendWork;
static pthread_t mCmdsSendThread;
static struct workQueue* mCallbackWork;
static pthread_t mCallbackThread;
static pthread_mutex_t mConnsLock = PTHREAD_MUTEX_INITIALIZER;
static struct hciAclConn *mConns = NULL;
static pthread_mutex_t mDiscoveryStateLock = PTHREAD_MUTEX_INITIALIZER;
static hciDeviceDiscoveredEdrCbk mDiscoverEdrCbk;
static void *mDiscoverEdrData;
static uniq_t mDiscoverEdrHandle = 0;
static hciDeviceDiscoveredLeCbk mDiscoverLeCbk;
static void *mDiscoverLeData;
static uniq_t mDiscoverLeHandle = 0;
static struct hciDiscoverNameState *mDiscoverNames = NULL;
static uint8_t mIoCapability = HCI_DISP_CAP_DISP_YES_NO;
/* read from chip */
static uint8_t mBtVer = 0; /* HCI_VERSION_* */
static uint64_t mLocalFtrs[MAX_FTR_PAGES] = {0, };
static uint64_t mLocalLeFtrs = 0;
static uint16_t mScoBufSz = 0;
static uint16_t mAclBufSzEdr = 0;
static uint16_t mAclBufSzLe = 0;
static uint16_t mScoBufNum = 0;
static uint16_t mAclBufNumEdr = 0;
static uint16_t mAclBufNumLe = 0;
static bool mBtJointBuffers = true; /* use EDR buffers for LE */
static sem_t mAclPacketsEdr;
static sem_t mAclPacketsLe;
static sem_t mScoPackets;
static bool mHaveLe = false;
static bool mHaveEdr = true;
static bool mSimulLe = false;
static uint8_t mInquiryState = 0;
static uint8_t mLocalAddr[BT_MAC_LEN];
/* fwd declarations */
static bool hciBtStackUp(void);
static bool hciScheduleConnUpNotif(hci_conn_t conn, const struct bt_addr *peerAddr, const struct bt_addr *selfAddr, bool isMaster, bool isEncrypted, bool isMitmSafe);
static bool hciScheduleConnDownNotif(hci_conn_t conn);
static bool hciScheduleConnDataRx(hci_conn_t conn, sg packet, bool first);
static bool hciCmdSubmitSimpleWithStatusWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData);
static void hciHandleConnAclDown(uint16_t cid, uint8_t reason);
static bool hciHandleConnAclEdrUp(uint8_t status, uint16_t id, const uint8_t *mac, bool encrypted);
static bool hciHandleConnAclLeUp(uint8_t status, uint16_t id, bool isMaster, const uint8_t *mac, bool addrRandom, uint16_t interval, uint16_t latency, uint16_t timeout, uint8_t mca);
static bool hciHandleConnAclEncr(uint16_t cid, bool encrOn);
static bool hciHandleConnAclAuth(uint16_t cid, bool authOn);
static bool hciHandleConnAclRoleChange(const uint8_t* mac, bool amSlave);
static bool hciHandleConnAclLeUpdate(uint8_t status, uint16_t cid, uint16_t interval, uint16_t latency, uint16_t timeout);
static bool hciConnUpdate(uint16_t cid, uint16_t minInt, uint16_t maxInt, uint16_t latency, uint16_t timeout);
static void hciCmdStatusCbk(void *cbkData, uint8_t status);
static bool hciConnDisconnect(uint16_t cid);
static bool hciConnLeCancel(void);
static void hciConnCfg(struct hciAclConn *c, uint32_t step, uint32_t aux);
static bool hciRxAclForRunningConn(struct hciAclConn* conn, uint16_t hdr, sg packet);
static void hciConnAclStructDel(struct hciAclConn* conn);
/*
* FUNCTION: hciConnFindById
* USE: Find a connection struct given an ID
* PARAMS: id - the id
* RETURN: connection struct or NULL if not found
* NOTES: call with mConnsLock held
*/
static struct hciAclConn* hciConnFindById(uint16_t id)
{
struct hciAclConn *c = mConns;
while (c && c->id != id)
c = c->next;
return c;
}
/*
* FUNCTION: hciConnFindByHandle
* USE: Find a connection struct given a handle
* PARAMS: addr - the address
* RETURN: connection struct or NULL if not found
* NOTES: call with mConnsLock held
*/
static struct hciAclConn* hciConnFindByHandle(hci_conn_t handle)
{
struct hciAclConn *c = mConns;
while (c && c->handle != handle)
c = c->next;
return c;
}
/*
* FUNCTION: hciConnFindByAddr
* USE: Find a connection struct given an address
* PARAMS: addr - the address
* RETURN: connection struct or NULL if not found
* NOTES: call with mConnsLock held
*/
static struct hciAclConn* hciConnFindByAddr(const struct bt_addr *addr)
{
struct hciAclConn *c = mConns;
while (c && memcmp(&c->peerAddr, addr, sizeof(c->peerAddr)))
c = c->next;
return c;
}
/*
* FUNCTION: hciEvtWaitDequeueInt
* USE: Dequeue a handler for an event we expect
* PARAMS: ws - the handler struct
* RETURN: NONE
* NOTES: call with mCmdWaitLock held
*/
static void hciEvtWaitDequeueInt(struct hciEvtWaitState *ws)
{
if (ws->next)
ws->next->prev = ws->prev;
else
mEvtWaitTail = ws->prev;
if (ws->prev)
ws->prev->next = ws->next;
else
mEvtWaitHead = ws->next;
free(ws);
}
/*
* FUNCTION: hciEvtWaitDequeue
* USE: Dequeue a handler for an event we expect
* PARAMS: evtWaitStateID - wait state ID from hciEvtWaitEnqueue()
* RETURN: true if dequeued, false if not found (already fired or never existed)
* NOTES:
*/
static bool hciEvtWaitDequeue(uniq_t evtWaitStateID)
{
struct hciEvtWaitState *ws;
bool found = true;
pthread_mutex_lock(&mCmdWaitLock);
ws = mEvtWaitHead;
while (ws && ws->evtWaitStateID != evtWaitStateID)
ws = ws->next;
if (ws)
hciEvtWaitDequeueInt(ws);
else
found = false;
pthread_mutex_unlock(&mCmdWaitLock);
return found;
}
/*
* FUNCTION: hciEvtWaitDequeueAllForCmd
* USE: Dequeue all handler for a given command ID
* PARAMS: forCmdID - command ID as passed to hciEvtWaitEnqueue();
* RETURN: how many handlers were dequeued
* NOTES:
*/
static uint8_t hciEvtWaitDequeueAllForCmd(uniq_t forCmdID)
{
struct hciEvtWaitState *ws, *t;
uint8_t found = 0;
pthread_mutex_lock(&mCmdWaitLock);
ws = mEvtWaitHead;
while (ws) {
t = ws;
ws = ws->next;
if (t->forCmdID == forCmdID) {
hciEvtWaitDequeueInt(ws);
found++;
}
}
pthread_mutex_unlock(&mCmdWaitLock);
return found;
}
/*
* FUNCTION: hciEvtWaitEnqueue
* USE: Enqueue a handler for an event we expect
* PARAMS: ewd - the event wait descriptor
* persistent - if set, handler will not be removed
* when it fires and must be removed manually
* eventWaitID - the eventWaitID for this waiter. If 0, one will be allocated
* forCmdID - the commandID this is for
* RETURN: uniq_t identifying this event wait object or 0 on failure
* NOTES:
*/
static uniq_t hciEvtWaitEnqueue(const struct hciEvtWaitDescr *ewd, uniq_t forCmdID)
{
struct hciEvtWaitState *ws = (struct hciEvtWaitState*)malloc(sizeof(struct hciEvtWaitState));
uniq_t ret;
if (!ws) {
loge("Failed to enqueue handler for event 0x%02X.%04X\n", ewd->evtType, ewd->extra);
return 0;
}
ret = uniqGetNext();
ws->cbk = ewd->cbk;
ws->cbkData = ewd->cbkData;
ws->evtType = ewd->evtType;
ws->extra = ewd->extra;
ws->persistent = ewd->persistent;
ws->next = NULL;
ws->evtWaitStateID = ret;
ws->forCmdID = forCmdID;
pthread_mutex_lock(&mCmdWaitLock);
ws->prev = mEvtWaitTail;
mEvtWaitTail = ws;
if (ws->prev)
ws->prev->next = ws;
else
mEvtWaitHead = ws;
pthread_mutex_unlock(&mCmdWaitLock);
return ret;
}
/*
* FUNCTION: hciCbkChipHwUp
* USE: Called when the chip hw or fw are up
* PARAMS: cbkData - unused
* fw - true if fw status, false if hw
* up - success of bringing said component up
* RETURN: true on success, false else
* NOTES:
*/
static void hciCbkChipHwUp(void *cbkData, bool fw, bool up)
{
logi("BT %cW %s!\n", fw ? 'F' : 'H', up ? "UP" : "DOWN");
}
/*
* FUNCTION: hciRxSco
* USE: Called with a valid SCO packet
* PARAMS: hdr - packet header
* packet - the packet we got
* RETURN: NONE
* NOTES:
*/
static void hciRxSco(uint16_t hdr, sg packet)
{
uint16_t pb = hdr & ACL_HDR_MASK_PB;
uint16_t bc = hdr & ACL_HDR_MASK_BC;
uint16_t cid = hdr & ACL_HDR_MASK_CONN_ID;
uint8_t loss = 0;
switch (hdr & SCO_HDR_MASK_STATUS){
case SCO_STATUS_ALL_OK:
loss = SCO_LOSS_AMOUNT_NONE;
break;
case SCO_STATUS_UNKNOWN:
loss = SCO_LOSS_AMOUNT_UNKNOWN;
break;
case SCO_STATUS_NO_DATA:
loss = SCO_LOSS_AMOUNT_ALL;
break;
case SCO_STATUS_SOME_DATA:
loss = SCO_LOSS_AMOUNT_SOME;
break;
}
//TODO: do something with the sco data
sgFree(packet);
}
/*
* FUNCTION: hciRxAclForRunningConn
* USE: Called with a valid ACL packet/fragment for a running connection
* PARAMS: conn - the connection this is for
* hdr - packet header
* packet - the packet we got
* RETURN: NONE
* NOTES: call with mConnsLock held
*/
static bool hciRxAclForRunningConn(struct hciAclConn* conn, uint16_t hdr, sg packet)
{
uint16_t pb = hdr & ACL_HDR_MASK_PB;
uint16_t bc = hdr & ACL_HDR_MASK_BC;
if (pb == ACL_HDR_PB_FIRST_NONAUTO || pb == ACL_HDR_PB_COMPLETE) {
logw("Invalid acl.pb flag from chp: 0x%04X. Dropped\n", pb);
return false;
}
return hciScheduleConnDataRx(conn->handle, packet, pb == ACL_HDR_PB_FIRST_AUTO);
}
/*
* FUNCTION: hciRxAcl
* USE: Called with a valid ACL packet/fragment
* PARAMS: hdr - packet header
* packet - the packet we got
* RETURN: NONE
* NOTES:
*/
static void hciRxAcl(uint16_t hdr, sg packet)
{
uint16_t cid = hdr & ACL_HDR_MASK_CONN_ID;
struct hciAclConn* conn;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindById(cid);
if (!conn) {
logi("Dropping packet for unknown acl link %d\n", cid);
sgFree(packet);
} else if (conn->state == CONN_STATE_CFG) {
struct hciBacklogItemHdr itemHdr = {.len = sgLength(packet), .aclHdr = hdr};
if (!conn->rxBacklog)
conn->rxBacklog = sgNew();
if (conn->rxBacklog && sgConcatFrontCopy(packet, &itemHdr, sizeof(itemHdr)) && sgLength(conn->rxBacklog) + sgLength(packet) <= HCI_CONN_CFG_DATA_BACKLOG_MAX) {
sgConcat(conn->rxBacklog, packet);
logd("Added packet to RX backlog\n");
} else {
logi("Dropping connection -> we're unable to save RX in backlog\n");
hciConnDisconnect(conn->id);
sgFree(packet);
}
} else if (conn->state != CONN_STATE_RUNNING) {
logi("Dropping arrived data for connection in state %u\n", conn->state);
sgFree(packet);
} else if (!hciRxAclForRunningConn(conn, hdr, packet)) {
logi("Failed to enqueue packet RX for link %d\n", cid);
sgFree(packet);
}
pthread_mutex_unlock(&mConnsLock);
}
/*
* FUNCTION: hciRxEvt
* USE: Called with a valid EVT packet
* PARAMS: packet - the packet we got
* RETURN: NONE
* NOTES:
*/
static void hciRxEvt(sg packet)
{
struct hciEvtWaitState *ws = NULL, *cur = NULL;
bool handled = false, addCredit = false;
const struct hciEvtCmdComplete *cmdComplete;
const struct hciEvtCmdStatus *cmdStatus;
const struct hciEvtLeMeta *leMeta;
const struct hciEvtHdr *evt;
uint8_t evtType, len;
void* sgiter;
uint32_t i;
/*
* The SG may or may not be contiguous on the inside. If it is not, we'll alloc a new buffer
* and serialize the SG into it. If it is, we can just use the SG's internal buffer. How do
* we check? Iterate SG of course... Why? To save a copy and a malloc() & a free()
*/
sgiter = sgIterStart(packet);
if (sgiter && sgIterCurLen(sgiter) == sgLength(packet))
evt = (const struct hciEvtHdr*)sgIterCurData(sgiter);
else {
evt = malloc(sgLength(packet));
if (!evt) {
loge("Failed to alloc flat event representation\n");
sgFree(packet);
}
sgSerialize(packet, 0, sgLength(packet), (struct hciEvtHdr*)evt);
sgiter = NULL;
sgFree(packet);
}
leMeta = (struct hciEvtLeMeta*)(evt + 1);
cmdComplete = (struct hciEvtCmdComplete*)(evt + 1);
cmdStatus = (struct hciEvtCmdStatus*)(evt + 1);
evtType = utilGetLE8(&evt->code);
len = utilGetLE8(&evt->len);
/* handle the cases of credits: command complete */
if (evtType == HCI_EVT_Command_Complete && utilGetLE8(&cmdComplete->numCmdCredits))
addCredit = true;
/* handle the cases of credits: command status */
if (evtType == HCI_EVT_Command_Status && utilGetLE8(&cmdStatus->numCmdCredits))
addCredit = true;
/* handle data credits */
if (evtType == HCI_EVT_Number_Of_Completed_Packets) {
bool hadLeCreds = false, hadEdrCreds = false;
uint16_t newCreditsEdr = 0, newCreditsLe = 0;
struct hciAclConn* conn;
struct hciEvtNumCompletedPackets *ncp = (struct hciEvtNumCompletedPackets*)(evt + 1);
struct hciEvtNumCompletedPacketsItem *items = ncp->items;
pthread_mutex_lock(&mConnsLock);
for (i = 0; i < utilGetLE8(&ncp->numHandles); i++, items++) {
uint16_t connID = utilGetLE16(&items->conn);
uint16_t numPackets = utilGetLE16(&items->numPackets);
uint8_t acceptCredits = 0;
conn = hciConnFindById(connID);
if (!conn) {
loge("Unknown conn 0x%04X in %d packets completed.\n", connID, numPackets);
} else {
if (conn->outstandingPackets >= numPackets) {
acceptCredits = numPackets;
conn->outstandingPackets -= numPackets;
} else {
loge("Conn %u has %u outstanding packets but got %u credits back.\n", (unsigned)conn->id, conn->outstandingPackets, numPackets);
acceptCredits = conn->outstandingPackets;
conn->outstandingPackets = 0;
}
}
if (HCI_TAKE_CREDS_FOR_UNKNOWN_CONNS && acceptCredits != numPackets) {
logw("Accepting %d credits for unknown packets\n", numPackets - acceptCredits);
acceptCredits = numPackets;
}
if (BT_ADDR_IS_LE(conn->peerAddr) && !mBtJointBuffers) {
hadLeCreds = true;
newCreditsLe += acceptCredits;
} else {
hadEdrCreds = true;
newCreditsEdr += acceptCredits;
}
}
pthread_mutex_unlock(&mConnsLock);
while (newCreditsLe--)
sem_post(&mAclPacketsLe);
while (newCreditsEdr--)
sem_post(&mAclPacketsEdr);
if (hadLeCreds)
l2cAclCreditAvail(true);
if (hadEdrCreds)
l2cAclCreditAvail(false);
handled = true;
}
/* handle command complete with ocf == 0 and ogf == 0 by ignoring it as per spec */
if (evtType == HCI_EVT_Command_Complete && !utilGetLE16(&cmdComplete->opcode))
handled = true;
/* handle HW failure */
if (evtType == HCI_EVT_Hardware_Error) {
struct hciEvtHwError *hwErr = (struct hciEvtHwError*)(evt + 1);
loge("Chip reported HW failure, code %d -> catastropic stack failure\n", utilGetLE8(&hwErr->errCode));
abort();
}
if (!handled) {
pthread_mutex_lock(&mCmdWaitLock);
ws = mEvtWaitHead;
while (ws && !handled) {
cur = ws;
ws = ws->next;
/* type match? If no, do not go on */
if (evtType != cur->evtType)
continue;
/* if LE event, check code and skip handler if wrong */
if (evtType == HCI_EVT_LE_Meta) {
if(len < sizeof(struct hciEvtLeMeta)) {
logw("Got LE meta event of len %ub\n", len);
break;
}
if (utilGetLE8(&leMeta->subevent) != cur->extra)
continue;
}
/* same for command complete */
if (evtType == HCI_EVT_Command_Complete) {
if(len < sizeof(struct hciEvtCmdComplete)) {
logw("Got command complete event of len %ub\n", len);
break;
}
if (utilGetLE16(&cmdComplete->opcode) != cur->extra)
continue;
}
/* same for command status */
if (evtType == HCI_EVT_Command_Status) {
if(len < sizeof(struct hciEvtCmdStatus)) {
logw("Got command status event of len %ub\n", len);
break;
}
if (utilGetLE16(&cmdStatus->opcode) != cur->extra)
continue;
}
/* if we're here, this handler matches - let's see what it says */
handled = cur->cbk(evt, cur->cbkData, cur->evtWaitStateID, cur->forCmdID);
}
if (handled && cur && !cur->persistent)
hciEvtWaitDequeueInt(cur);
pthread_mutex_unlock(&mCmdWaitLock);
}
if (addCredit)
sem_post(&mCmdSendSem);
if (!handled)
logw("Unhandled HCI event code 0x%02X with %ub of params\n", evtType, len);
if (sgiter)
sgFree(packet);
else
free((struct hciEvtHdr*)evt);
}
/*
* FUNCTION: hciRx
* USE: Called when the chip gives us data/events
* PARAMS: cbkData - unused
* typ - type of thing we're getting
* packet - the packet we got
* RETURN: true on success, false else
* NOTES: we must free the "data" buffer at some point in time - we own it now
*/
static void hciRx(void *cbkData, uint8_t typ, sg packet)
{
struct hciAclHdr hdrAcl;
struct hciScoHdr hdrSco;
struct hciEvtHdr hdrEvt;
uint32_t datalen = sgLength(packet);
uint32_t len;
uint16_t hdr;
switch (typ){
case HCI_PKT_TYP_ACL:
if (datalen < sizeof(struct hciAclHdr)) {
loge("Got ACL packet of %ub\n", datalen);
break;
}
datalen -= sizeof(struct hciAclHdr);
sgSerialize(packet, 0, sizeof(struct hciAclHdr), &hdrAcl);
hdr = utilGetLE16(&hdrAcl.hdr);
len = utilGetLE16(&hdrAcl.len);
if (len != datalen) {
loge("ACL packet claims %ub but had %ub\n", len, datalen);
break;
}
sgTruncFront(packet, sizeof(struct hciAclHdr));
hciRxAcl(hdr, packet);
packet = NULL;
break;
case HCI_PKT_TYP_SCO:
if (datalen < sizeof(struct hciScoHdr)) {
loge("Got SCO packet of %ub\n", datalen);
break;
}
datalen -= sizeof(struct hciScoHdr);
sgSerialize(packet, 0, sizeof(struct hciScoHdr), &hdrSco);
hdr = utilGetLE16(&hdrSco.hdr);
len = utilGetLE8(&hdrSco.len);
if (len != datalen) {
loge("SCO packet claims %ub but had %ub\n", len, datalen);
break;
}
sgTruncFront(packet, sizeof(struct hciScoHdr));
hciRxSco(hdr, packet);
packet = NULL;
break;
case HCI_PKT_TYP_EVT:
if (datalen < sizeof(struct hciEvtHdr)) {
loge("Got EVT packet of %ub\n", datalen);
break;
}
datalen -= sizeof(struct hciEvtHdr);
sgSerialize(packet, 0, sizeof(struct hciEvtHdr), &hdrEvt);
len = utilGetLE8(&hdrEvt.len);
if (len != datalen) {
loge("EVT packet claims %ub but had %ub\n", len, datalen);
break;
}
hciRxEvt(packet);
packet = NULL;
break;
default:
loge("Dropping unknown packet with type %d\n", typ);
break;
}
if (packet)
sgFree(packet);
}
/*
* FUNCTION: hciCallbackWorkDeallocator
* USE: Called to cleanup each item of callback workqueue at deinit time
* PARAMS: workItem - a work item
* RETURN: NONE
* NOTES:
*/
static void hciCallbackWorkDeallocator(void *workItem)
{
struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)workItem;
if (w->cbkWorkItemType == CBK_WORK_ACL_DATA)
sgFree(w->aclDataRx.packet);
free(workItem);
}
/*
* FUNCTION: hciCallbackWorkDeallocator
* USE: Called to cleanup each item of command send workqueue at deinit time
* PARAMS: workItem - a work item
* RETURN: NONE
* NOTES:
*/
static void hciCmdSendWorkDeallocator(void *workItem)
{
struct hciCmdSendWorkItem *w = (struct hciCmdSendWorkItem*)workItem;
sgFree(w->cmd);
free(workItem);
}
/*
* FUNCTION: hciCbkWorker
* USE: Worker thread for calling callbacks for HCI completed items
* PARAMS: unused - unused
* RETURN: unused
* NOTES:
*/
static void* hciCbkWorker(void *unused)
{
struct hciCbkWorkItem *w;
int ret;
pthread_setname_np(pthread_self(), "bt_hci_callbacks");
while (1) {
ret = workQueueGet(mCallbackWork, (void**)&w);
if (ret)
break;
switch (w->cbkWorkItemType){
case CBK_WORK_ITEM_DONE_CBK:
w->done.cbk(w->done.cbkData, w->done.status);
break;
case CBK_WORK_ITEM_SIMPLE_CBK:
w->simple.cbk(w->simple.cbkData1, w->simple.cbkData2, w->simple.stackGoingDown, w->simple.haveEvt ? (struct hciEvtHdr*)(w + 1) : NULL);
break;
case CBK_WORK_EDR_DISC_DEV:
mDiscoverEdrCbk(mDiscoverEdrData, &w->edrDev.addr, w->edrDev.devCls, &w->edrDev.fng, w->edrDev.rssi, w->edrDev.eir, w->edrDev.eirLen);
break;
case CBK_WORK_EDR_DISC_NAME:
w->edrName.cbk(w->edrName.cbkData, &w->edrName.addr, w->edrName.nameReqState, w->edrName.name);
break;
case CBK_WORK_LE_DISC_DEV:
mDiscoverLeCbk(mDiscoverLeData, &w->leDev.addr, w->leDev.rssi, w->leDev.advType, w->leDev.adv, w->leDev.advLen);
break;
case CBK_WORK_FLUSH:
sem_post(w->flush.sem);
break;
case CBK_WORK_CONN_UP:
l2cAclLinkUp(w->connUp.conn, &w->connUp.peerAddr, &w->connUp.selfAddr, w->connUp.isMaster, w->connUp.isEncrypted, w->connUp.isMitmSafe);
break;
case CBK_WORK_CONN_DOWN:
l2cAclLinkDown(w->connDown.conn);
break;
case CBK_WORK_PARAMS_CHANGE:
l2cAclLinkParamsChange(w->leParamChange.conn, w->leParamChange.success, w->leParamChange.interval, w->leParamChange.latency, w->leParamChange.timeout);
break;
case CBK_WORK_ENCR_CHANGE:
l2cAclLinkEncrChange(w->encrChange.conn, w->encrChange.nowEncrypted, w->encrChange.isMitmSafe);
break;
case CBK_WORK_ACL_DATA:
l2cAclDataRx(w->aclDataRx.conn, w->aclDataRx.packet, w->aclDataRx.first);
break;
default:
loge("Unknown callback work type %u\n", w->cbkWorkItemType);
break;
}
free(w);
}
logd("HCI callback worker exiting\n");
return NULL;
}
/*
* FUNCTION: hciScheduleOpDoneCbk
* USE: Enqueue an op_done callback to be called
* PARAMS: cbk - the callback to call
* cbkData - the data for said callback
* status - the status byte
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleOpDoneCbk(hciOpDoneCbk cbk, void *cbkData, uint8_t status)
{
struct hciCbkWorkItem *w;
if (!cbk) {
logw("Not scheduling done callback with a NULL function pointer\n");
return true;
}
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_ITEM_DONE_CBK;
w->done.cbk = cbk;
w->done.cbkData = cbkData;
w->done.status = status;
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciScheduleSimpleCbk
* USE: Enqueue simple callback to be called
* PARAMS: cbk - the callback to call
* cbkData1 - the data for said callback
* cbkData2 - the data for said callback
* stackGoingDown - as the name implies...
* evt - event to provide, or NULL if none
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleSimpleCbk(hciSimpleCbk cbk, void *cbkData1, void *cbkData2, bool stackGoingDown, const struct hciEvtHdr *evt)
{
uint16_t evtLen = evt ? (sizeof(struct hciEvtHdr) + utilGetLE8(&evt->len)) : 0;
uint32_t neededLen = sizeof(struct hciCbkWorkItem) + evtLen;
struct hciCbkWorkItem *w;
if (!cbk) {
logw("Not scheduling simple callback with a NULL function pointer\n");
return true;
}
w = (struct hciCbkWorkItem*)malloc(neededLen);
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_ITEM_SIMPLE_CBK;
w->simple.cbk = cbk;
w->simple.cbkData1 = cbkData1;
w->simple.cbkData2 = cbkData2;
w->simple.stackGoingDown = stackGoingDown;
w->simple.haveEvt = evt != NULL;
if (evt)
memcpy(w + 1, evt, evtLen);
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciWorkFlush
* USE: Flush the workQueue
* PARAMS: NONE
* RETURN: true if success, false else
* NOTES: blocks till all existing work items have been finished
*/
static bool hciWorkFlush(void)
{
struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
bool ret = false;
sem_t sem;
if (!w) {
loge("Failed to alloc workQ flush cmd\n");
return false;
}
if (sem_init(&sem, 0, 0)) {
free(w);
loge("Failed to alloc workQ flush sem\n");
return false;
}
w->cbkWorkItemType = CBK_WORK_FLUSH;
w->flush.sem = &sem;
if (workQueuePut(mCallbackWork, w)) {
r_sem_wait(&sem);
ret = true;
} else
free(w);
sem_destroy(&sem);
return ret;
}
/*
* FUNCTION: hciScheduleConnUpNotif
* USE: Enqueue a call to the ACL link up notif
* PARAMS: conn - the connection
* peerAddr - peer address
* selfAddr - whom we were being when we made the connection
* isMaster - are we master on the link
* isEncrypted - is the link encrypted
* isMitmSafe - is th elink MITM-safe?
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleConnUpNotif(hci_conn_t conn, const struct bt_addr *peerAddr, const struct bt_addr *selfAddr, bool isMaster, bool isEncrypted, bool isMitmSafe)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_CONN_UP;
w->connUp.conn = conn;
w->connUp.isMaster = isMaster;
w->connUp.isEncrypted = isEncrypted;
memcpy(&w->connUp.peerAddr, peerAddr, sizeof(w->connUp.peerAddr));
memcpy(&w->connUp.selfAddr, selfAddr, sizeof(w->connUp.selfAddr));
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciScheduleConnDownNotif
* USE: Enqueue a call to the ACL link down notif
* PARAMS: conn - the connection
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleConnDownNotif(hci_conn_t conn)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_CONN_DOWN;
w->connDown.conn = conn;
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciScheduleConnParamChange
* USE: Enqueue a call to the connection param change notif
* PARAMS: conn - the conenction
* success - success?
* interval - new interval
* latency - new latency
* timeout - new timeout
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleConnParamChange(hci_conn_t conn, bool success, uint16_t interval, uint16_t latency, uint16_t timeout)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_PARAMS_CHANGE;
w->leParamChange.conn = conn;
w->leParamChange.success = success;
w->leParamChange.interval = interval;
w->leParamChange.latency = latency;
w->leParamChange.timeout = timeout;
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciScheduleConnEncrChange
* USE: Enqueue a call to the connection encr change notif
* PARAMS: conn - the conenction
* nowEncrypted - are we now encrypted?
* isMitmSafe - are we now MITM-safe?
* RETURN: true if enqueued, false else
* NOTES:
*/
static bool hciScheduleConnEncrChange(hci_conn_t conn, bool nowEncrypted, bool isMitmSafe)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_ENCR_CHANGE;
w->encrChange.conn = conn;
w->encrChange.nowEncrypted = nowEncrypted;
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciScheduleConnDataRx
* USE: Enqueue a call to the connection RX notif
* PARAMS: conn - the conenction
* packet - the packet
* first - was packet marked as first?
* RETURN: true if enqueued, false else
* NOTES: data is copied
*/
static bool hciScheduleConnDataRx(hci_conn_t conn, sg packet, bool first)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem));
if (!w)
return false;
w->cbkWorkItemType = CBK_WORK_ACL_DATA;
w->aclDataRx.conn = conn;
w->aclDataRx.packet = packet;
w->aclDataRx.first = first;
if (workQueuePut(mCallbackWork, w))
return true;
free(w);
return false;
}
/*
* FUNCTION: hciCmdSendWorker
* USE: Worker thread for sending commands to the chip
* PARAMS: unused - unused
* RETURN: unused
* NOTES:
*/
static void* hciCmdSendWorker(void *unused)
{
struct hciCmdSendWorkItem *w;
bool weExpectReply = false;
struct timespec ts;
int ret, i;
pthread_setname_np(pthread_self(), "bt_hci_cmds_send");
while (1) {
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += BT_COMMAND_TIMEOUT;
if (r_sem_timedwait(&mCmdSendSem, &ts)) {
loge("CHIP DEAD!\n");
mt_analize_death();
abort();
}
vendorTxDoneWithEvts();
ret = workQueueGet(mCmdsSendWork, (void**)&w);
if (ret)
break;
/*
* This lock makes sure adding a handler for an event and sending the
* corresponding command are handled as one atomic unit.
*/
pthread_mutex_lock(&mCmdLock);
for (i = 0; i < w->numEventWaiters; i++) {
if (!w->ewds[i].descr.persistent)
weExpectReply = true;
w->ewds[i].id = hciEvtWaitEnqueue(&w->ewds[i].descr, w->cmdID);
if (!w->ewds[i].id) {
loge("Failed to enqueue handler [%d] \n", i);
goto failed;
}
}
if (vendorTx(HCI_PKT_TYP_CMD, w->cmd, weExpectReply))
goto success;
loge("Failed to TX a cmd\n");
sgFree(w->cmd);
failed:
loge("Command not sent\n");
while (i)
hciEvtWaitDequeue(w->ewds[--i].id);
success:
pthread_mutex_unlock(&mCmdLock);
free(w);
}
logd("HCI command send worker exiting\n");
return NULL;
}
/*
* FUNCTION: hciUp
* USE: Bring up HCI (comms to bt chip)
* PARAMS: addr - the BT addr
* ioCapability - our user interaction capabilities (HCI_DISP_CAP_*)
* RETURN: true on success, false else
* NOTES: may take a while
*/
bool hciUp(const uint8_t *addr, uint8_t ioCapability)
{
int ret;
mIoCapability = ioCapability;
logd("Vendor open\n");
if (!vendorOpen()) {
loge("Failed to open vendor lib\n");
goto out_lib_open;
}
logd("Vendor up\n");
if (!vendorUp(addr, hciCbkChipHwUp, hciRx, NULL)) {
loge("Failed to bring up the chip\n");
goto out_chip_up;
}
ret = sem_init(&mCmdSendSem, 0, 1);
if (ret) {
loge("Sem init failure\n");
goto out_sem;
}
mCmdsSendWork = workQueueAlloc(HCI_NUM_OUTSTANDING_CMDS);
if (!mCmdsSendWork) {
loge("Failed to create command send workqueue\n");
goto out_cmd_workq;
}
mCallbackWork = workQueueAlloc(HCI_NUM_OUTSTANDING_CBKS);
if (!mCallbackWork) {
loge("Failed to create hci callback workqueue\n");
goto out_cbk_workq;
}
ret = pthread_create(&mCallbackThread, NULL, hciCbkWorker, NULL);
if (ret) {
loge("Failed(%d) to create hci callback worker\n", ret);
goto out_cbk_worker;
}
ret = pthread_create(&mCmdsSendThread, NULL, hciCmdSendWorker, NULL);
if (ret) {
loge("Failed(%d) to create hci cmd send worker\n", ret);
goto out_cmd_worker;
}
logd("Stack up\n");
if (!hciBtStackUp()) {
loge("Failed to bring up BT stack\n");
goto out_stack_up;
}
return true;
out_stack_up:
workQueueWakeAll(mCmdsSendWork, 1);
sem_post(&mCmdSendSem); /* wakes up cmdSend worker if it is waiting on a cmd credit */
pthread_join(mCmdsSendThread, NULL);
out_cmd_worker:
workQueueWakeAll(mCallbackWork, 1);
pthread_join(mCallbackThread, NULL);
out_cbk_worker:
workQueueFree(mCallbackWork, hciCallbackWorkDeallocator);
out_cbk_workq:
workQueueFree(mCmdsSendWork, hciCmdSendWorkDeallocator);
out_cmd_workq:
sem_destroy(&mCmdSendSem);
out_sem:
vendorDown();
out_chip_up:
vendorClose();
out_lib_open:
return false;
}
/*
* FUNCTION: hciDown
* USE: Bring down HCI (comms to bt chip)
* PARAMS: NONE
* RETURN: NONE
* NOTES: may take a while, some shutdown actions may go
* on after this (in higher layers)
*/
void hciDown(void)
{
logd("Cancelling all event waiters\n");
pthread_mutex_lock(&mCmdWaitLock);
while (mEvtWaitHead) {
mEvtWaitHead->cbk(NULL, mEvtWaitHead->cbkData, mEvtWaitHead->evtWaitStateID, mEvtWaitHead->forCmdID);
hciEvtWaitDequeueInt(mEvtWaitHead);
}
while (mConns) {
struct hciAclConn *t = mConns;
mConns = mConns->next;
free(t);
}
pthread_mutex_unlock(&mCmdWaitLock);
//more deinit here
pthread_mutex_lock(&mDiscoveryStateLock);
mDiscoverEdrHandle = 0;
mDiscoverLeHandle = 0;
while (mDiscoverNames) {
struct hciDiscoverNameState *t = mDiscoverNames;
mDiscoverNames = mDiscoverNames->next;
free(t);
}
pthread_mutex_unlock(&mDiscoveryStateLock);
workQueueWakeAll(mCallbackWork, 1);
workQueueWakeAll(mCmdsSendWork, 1);
sem_post(&mCmdSendSem); /* wakes up cmdSend worker if it is waiting on a cmd credit */
pthread_join(mCallbackThread, NULL);
pthread_join(mCmdsSendThread, NULL);
workQueueFree(mCallbackWork, hciCallbackWorkDeallocator);
workQueueFree(mCmdsSendWork, hciCmdSendWorkDeallocator);
sem_destroy(&mCmdSendSem);
vendorDown();
vendorClose();
}
/*
* FUNCTION: hciCmdSubmit
* USE: Atomically enqueue a command to be sent to the chip and event handlers for
* events that will result.
* PARAMS: ogf - the command group
* ocf - the command code
* paramData - command params
* paramLen - parameter length
* ... - NULL-terminated list of const struct hciEvtWaitDescr* for wanted events
* RETURN: success
* NOTES: SHALL FINISH QUICKLY AND NOT BLOCK. WILL NOT DEADLOCK YOU! ALWAYS SAFE TO CALL!
*/
static bool hciCmdSubmit(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, ...)
{
const struct hciEvtWaitDescr *ewd;
struct hciCmdSendWorkItem *w;
uint8_t numHandlers = 0;
struct hciCmdHdr cmdhdr;
va_list vl;
sg cmd;
/* count up the events we want to wait for */
va_start(vl, paramLen);
while (va_arg(vl, const struct hciEvtWaitDescr*))
numHandlers++;
va_end(vl);
cmd = sgNew();
if (!cmd) {
loge("Failed to alloc cmd sg\n");
goto out_ret;
}
/* allocate the work item */
w = (struct hciCmdSendWorkItem*)malloc(sizeof(struct hciCmdSendWorkItem) + sizeof(struct hciEvtWaitDescrWithID[numHandlers]));
if (!w) {
loge("Failed to alloc command work for cmd 0x%02X.%04X\n", ogf, ocf);
goto out_sgfree;
}
/* copy wait state objects into it */
numHandlers = 0;
va_start(vl, paramLen);
while ((ewd = va_arg(vl, const struct hciEvtWaitDescr*)) != NULL)
memcpy(&w->ewds[numHandlers++].descr, ewd, sizeof(struct hciEvtWaitDescr));
va_end(vl);
w->numEventWaiters = numHandlers;
/* create the command */
utilSetLE16(&cmdhdr.opcode, CMD_MAKE_OPCODE(ogf, ocf));
utilSetLE16(&cmdhdr.paramLen, paramLen);
if (!sgConcatBackCopy(cmd, &cmdhdr, sizeof(cmdhdr)) || !sgConcatBackCopy(cmd, paramData, paramLen)) {
loge("Failed to fill command sg\n");
goto out_wfree;
}
w->cmd = cmd;
/* try to enqueue it */
if (workQueuePut(mCmdsSendWork, w))
return true;
/* in case of failure, free all that we allocated */
loge("Failed to enqueue command\n");
out_wfree:
free(w);
out_sgfree:
sgFree(cmd);
out_ret:
return false;
}
/*
* FUNCTION: hciCmdSubmitSimpleCbk
* USE: Command callback for hciCmdSubmitSimple
* PARAMS: evt - the event
* cbkData - struct hciSimpleCbkData - our data
* evtWaitStateID - event waiterID
* forCmdID - commandID
* RETURN: true if we handled it, false else
* NOTES:
*/
static bool hciCmdSubmitSimpleCbk(const struct hciEvtHdr *evt, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID)
{
struct hciSimpleCbkData *cbkd = (struct hciSimpleCbkData*)cbkData;
struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1);
struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1);
const char* name = cbkd->complete ? "complete" : "status";
uint8_t evtType;
uint8_t paramLen;
uint8_t evtHdrLen;
bool ret = false;
if (!evt)
return hciScheduleSimpleCbk(cbkd->cbk, cbkd->cbkData1, cbkd->cbkData2, true, NULL);
evtType = utilGetLE8(&evt->code);
paramLen = utilGetLE8(&evt->len);
evtHdrLen = cbkd->complete ? sizeof(struct hciEvtCmdComplete) : sizeof(struct hciEvtCmdStatus);
if (paramLen < evtHdrLen) {
logw("Comand %s event too short @ %ub (wanted %ub+)\n", name, paramLen, evtHdrLen);
goto out;
}
paramLen -= evtHdrLen;
/* is param length within bounds? */
if (paramLen < cbkd->expectedParamLenMin || paramLen > cbkd->expectedParamLenMax) {
logw("Got expected %s event, but wrong size: %u !< %u !< %u\n", name,
cbkd->expectedParamLenMin, paramLen, cbkd->expectedParamLenMax);
goto out;
}
/* enqueue the callback */
ret = hciScheduleSimpleCbk(cbkd->cbk, cbkd->cbkData1, cbkd->cbkData2, false, cbkd->wantEventItself ? evt : NULL);
out:
free(cbkData);
return ret;
}
/*
* FUNCTION: hciCmdSubmitSimple
* USE: Send a command and setup a handler for a command complete/command status for it
* PARAMS: ogf - command group
* ocf - command code
* paramData - command params
* paramLen - length of command params
* cbk - callback to call when command complete event arrives
* cbkData1 - data1 to pass to that callback
* cbkData2 - data2 to pass to that callback
* minEvtDataLen - minimum event data length to accept (excluding command complete/status event itself)
* maxEvtDataLen - maximum event data length to accept (excluding command complete/status event itself)
* wantEvt - pass a copy of the event to callback?
* expectComplete - expect command complete event? (else command status)
* RETURN: true if we enqueued it, false else
* NOTES:
*/
static bool hciCmdSubmitSimple(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciSimpleCbk cbk, void *cbkData1,
void *cbkData2, uint8_t minEvtDataLen, uint8_t maxEvtDataLen, bool wantEvt, bool expectComplete)
{
struct hciEvtWaitDescr ewd;
struct hciSimpleCbkData *cbkd = (struct hciSimpleCbkData*)malloc(sizeof(struct hciSimpleCbkData));
if (!cbkd)
return false;
cbkd->cbk = cbk;
cbkd->cbkData1 = cbkData1;
cbkd->cbkData2 = cbkData2;
cbkd->complete = expectComplete ? 1 : 0;
cbkd->wantEventItself = wantEvt ? 1 : 0;
cbkd->expectedParamLenMin = minEvtDataLen;
cbkd->expectedParamLenMax = maxEvtDataLen;
ewd.evtType = expectComplete ? HCI_EVT_Command_Complete : HCI_EVT_Command_Status;
ewd.extra = CMD_MAKE_OPCODE(ogf, ocf);
ewd.cbk = hciCmdSubmitSimpleCbk;
ewd.cbkData = cbkd;
ewd.persistent = false;
if (hciCmdSubmit(ogf, ocf, paramData, paramLen, &ewd, NULL))
return true;
free(cbkd);
return false;
}
/*
* FUNCTION: hciCmdSubmitWithDoneSimpleCbk
* USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteWithDoneCbk()
* PARAMS: cbkData1 - 1st callback data (actually the original hciOpDoneCbk)
* cbkData2 - 2nd callback data (actually original data for hciOpDoneCbk)
* evt - the arrived event
* RETURN: true if we handled the event (always yes)
* NOTES:
*/
static void hciCmdSubmitWithDoneSimpleCbk(void* cbkData1, void* cbkData2, bool goingDown, struct hciEvtHdr *evt)
{
struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1);
struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1);
uint8_t *paramP = (uint8_t*)(cmpl + 1); //for compelte
hciOpDoneCbk cbkF = (hciOpDoneCbk)cbkData1;
uint8_t status;
if (goingDown)
status = HCI_STAT_GOING_DOWN;
else if (utilGetLE8(&evt->code) == HCI_EVT_Command_Complete)
status = utilGetLE8(paramP);
else
status = utilGetLE8(&stat->status);
if (!hciScheduleOpDoneCbk(cbkF, cbkData2, status))
loge("Failed to shedule done callback for simple command complete handler\n");
}
/*
* FUNCTION: hciCmdSubmitSimpleWithCompleteWithDoneCbk
* USE: Send a command and setup a handler for a command complete for it. call DoneCbk
* PARAMS: ogf - command group
* ocf - command code
* paramData - command params
* paramLen - length of command params
* cbk - doneCbk
* cbkData - data1 to pass to doneCbk
* RETURN: true if we enqueued it, false else
* NOTES: This only works if the first byte of the event is a "status" byte. WATCH OUT FOR THIS!!!
*/
static bool hciCmdSubmitSimpleWithCompleteWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData)
{
return hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitWithDoneSimpleCbk, (void*)cbk, cbkData, 1/* status byte required */, 255, true, true);
}
/*
* FUNCTION: hciCmdSubmitSimpleWithStatusWithDoneCbk
* USE: Send a command and setup a handler for a command status for it. call DoneCbk
* PARAMS: ogf - command group
* ocf - command code
* paramData - command params
* paramLen - length of command params
* cbk - doneCbk
* cbkData - data1 to pass to doneCbk
* RETURN: true if we enqueued it, false else
* NOTES:
*/
static bool hciCmdSubmitSimpleWithStatusWithDoneCbk(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, hciOpDoneCbk cbk, void *cbkData)
{
return hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitWithDoneSimpleCbk, (void*)cbk, cbkData, 0, 255, true, false);
}
/*
* FUNCTION: hciCmdSubmitCompleteSyncSimpleCbk
* USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteSync()
* PARAMS: cbkData1 - 1st callback data (struct hciCmdSubmitWithCompleteSyncData*)
* cbkData2 - 2nd callback data (unused)
* evt - the arrived event
* RETURN: NONE
* NOTES:
*/
static void hciCmdSubmitCompleteSyncSimpleCbk(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt)
{
struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1);
struct hciCmdSubmitWithCompleteSyncData *data = (struct hciCmdSubmitWithCompleteSyncData*)cbkData1;
void* evtData = (void*)(cmpl + 1);
uint8_t paramLen;
data->goingDown = stackGoingDown;
if (stackGoingDown)
sem_post(data->sem);
paramLen = utilGetLE8(&evt->len) - sizeof(struct hciEvtCmdComplete);
if (paramLen > data->evtBufSz) {
logw("Sync complete event shortened %ud->%ud\n", paramLen, data->evtBufSz);
paramLen = data->evtBufSz;
}
memcpy(data->evtBuf, evtData, paramLen);
sem_post(data->sem);
}
/*
* FUNCTION: hciCmdSubmitSimpleWithCompleteSync
* USE: Send a command and setup a handler for a command complete for it. Wait for it to complete. Optionally provide resulting event back.
* PARAMS: ogf - command group
* ocf - command code
* paramData - command params
* paramLen - length of command params
* evtBuf - buffer where to put event (command complete event will be stripped, only params will remain)
* evtBufSz - size of said buffer. Event will be truncated if it does not fit
* RETURN: true if it happened. false else
* NOTES: Will block!
*/
static bool hciCmdSubmitSimpleWithCompleteSync(uint8_t ogf, uint16_t ocf, const void* paramData, uint8_t paramLen, void* evtBuf, uint8_t evtBufSz)
{
bool ret = true;
sem_t sem;
struct hciCmdSubmitWithCompleteSyncData data;
if (pthread_self() == mCallbackThread) {
loge("Refusing to issue sync command from callback thread. This WILL deadlock\n");
return false;
}
data.sem = &sem;
data.evtBuf = evtBuf;
data.evtBufSz = evtBufSz;
if (sem_init(&sem, 0, 0)) {
loge("sem init failed\n");
return false;
}
if (hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitCompleteSyncSimpleCbk, &data, NULL, 0, 255, true, true)) {
r_sem_wait(&sem);
if (data.goingDown) {
logi("going down\n");
ret = false;
}
} else
ret = false;
sem_destroy(&sem);
return ret;
}
/*
* FUNCTION: hciCmdSubmitStatusSyncSimpleCbk
* USE: Event callback for command complete event used by hciCmdSubmitSimpleWithCompleteSync()
* PARAMS: cbkData1 - 1st callback data (sem_t* sem)
* cbkData2 - 2nd callback data (struct hciEvtCmdStatus* dstEvt)
* evt - the arrived event
* RETURN: NONE
* NOTES:
*/
static void hciCmdSubmitStatusSyncSimpleCbk(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt)
{
struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1);
sem_t *sem = (sem_t*)cbkData1;
struct hciEvtCmdStatus* dstSta = (struct hciEvtCmdStatus*)cbkData2;
if (stackGoingDown)
utilSetLE8(&dstSta->status, HCI_STAT_GOING_DOWN);
else
memcpy(dstSta, stat, sizeof(struct hciEvtCmdStatus));
sem_post(sem);
}
/*
* FUNCTION: hciCmdSubmitSimpleWithStatusSync
* USE: Send a command and setup a handler for a command status for it. Wait for it. Provide resulting event back.
* PARAMS: ogf - command group
* ocf - command code
* paramData - command params
* paramLen - length of command params
* dstEvt - will store event here
* RETURN: true if it happened. false else
* NOTES: Will block!
*/
static bool hciCmdSubmitSimpleWithStatusSync(uint8_t ogf, uint16_t ocf, const void *paramData, uint8_t paramLen, struct hciEvtCmdStatus *dstEvt)
{
bool ret = true;
sem_t sem;
if (pthread_self() == mCallbackThread) {
loge("Refusing to issue sync command from callback thread. This WILL deadlock\n");
return false;
}
if (sem_init(&sem, 0, 0)) {
loge("sem init failed\n");
return false;
}
if (hciCmdSubmitSimple(ogf, ocf, paramData, paramLen, hciCmdSubmitStatusSyncSimpleCbk, &sem, dstEvt, 0, 0, true, false))
r_sem_wait(&sem);
else
ret = false;
sem_destroy(&sem);
return ret;
}
/*
* FUNCTION: hciSetAdvertiseEnable
* USE: Enable or disable LE advertsement
* PARAMS: on - enable ro disable it?
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetAdvertiseEnable(bool on, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciLeSetAdvEnable cmd = {0,};
if (!mHaveLe) {
loge("Refusing LE adv with no LE support\n");
return false;
}
utilSetLE8(&cmd.advOn, on ? 1 : 0);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Set_Advertise_Enable, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* FUNCTION: hciSetLocalName
* USE: Set local device name
* PARAMS: name - the name (up to 248 non-NULL chars, NULL-terminated)
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetLocalName(const char *name, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciWriteLocalName cmd = {{0,}, };
unsigned len = strlen(name);
if (!mHaveEdr) {
loge("Refusing name setting with no EDR support\n");
return false;
}
if (len >= sizeof(cmd.name)) {
memcpy(cmd.name, name, sizeof(cmd.name));
if (len > sizeof(cmd.name))
logw("Truncating BT name to %u chars\n", (int)sizeof(cmd.name));
} else
strcpy(cmd.name, name);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Local_Name, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* FUNCTION: hciSetDiscoverableConnectable
* USE: Set local discoverability/connectability
* PARAMS: discoverableState - NULL to leave as is, else pointer to wanted discoverability state
* connectableStateP - NULL to leave as is, else pointer to wanted connectablity state
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetDiscoverableConnectable(const bool *discoverableStateP, const bool *connectableStateP, hciOpDoneCbk cbkDone, void *cbkData)
{
uint8_t newState = mInquiryState;
if (!mHaveEdr) {
loge("Refusing scan state setting with no EDR support\n");
return false;
}
if (discoverableStateP)
newState = (newState &~ HCI_SCAN_ENABLE_INQUIRY) | (*discoverableStateP ? HCI_SCAN_ENABLE_INQUIRY : 0);
if (connectableStateP)
newState = (newState &~ HCI_SCAN_ENABLE_PAGE) | (*connectableStateP ? HCI_SCAN_ENABLE_PAGE : 0);
if (newState == mInquiryState) { /* nothing to change - tell caller we're done */
if (!hciScheduleOpDoneCbk(cbkDone, cbkData, 0)) {
loge("Failed to shedule done callback\n");
return false;
}
} else { /* things to do - do them */
struct hciWriteScanEnable cmd;
utilSetLE8(&cmd.state, newState);
if (hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Scan_Enable, &cmd, sizeof(cmd), cbkDone, cbkData))
mInquiryState = newState;
else
return false;
}
return true;
}
/*
* FUNCTION: hciSetDeviceClass
* USE: Set local device class
* PARAMS: class - the class value
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetDeviceClass(uint32_t cls, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciWriteClassOfDevice cmd;
if (!mHaveEdr) {
loge("Refusing device class setting with no EDR support\n");
return false;
}
if (cls >> 24)
logw("Truncating device class to 24 bits\n");
utilSetLE24(&cmd.cls, cls);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Class_Of_Device, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* FUNCTION: hciSetEirData
* USE: Set EIR data for EDR
* PARAMS: data - the data
* dlen - length of said data
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES: We always use FEC
*/
bool hciSetEirData(const uint8_t *data, uint8_t dlen, bool fecRequired, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciWriteEIR cmd = {0, };
if (!mHaveEdr) {
loge("Refusing EIR setting with no EDR support\n");
return false;
}
if (mBtVer <= HCI_VERSION_2_1 || !(mLocalFtrs[0] & HCI_LMP_FTR_EXTENDED_INQUIRY_RESPONSE)) {
loge("Refusing EIR setting with no EIR support\n");
return false;
}
if (dlen > sizeof(cmd.data)) {
loge("Refusing EIR setting that is too long\n");
return false;
}
utilSetLE8(&cmd.useFec, 1);
memcpy(cmd.data, data, dlen);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Extended_Inquiry_Response, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* FUNCTION: hciSetAdvData
* USE: Set EIR data for LE advertisements
* PARAMS: data - the data
* dlen - length of said data
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetAdvData(const uint8_t *data, uint8_t dlen, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciLeSetAdvData cmd = {0,};
if (!mHaveLe) {
loge("Refusing adv data setting with no EDR support\n");
return false;
}
if (dlen > sizeof(cmd.advData)) {
loge("Refusing LE adv setting that is too long\n");
return false;
}
memcpy(cmd.advData, data, dlen);
utilSetLE8(&cmd.advDataLen, dlen);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Set_Advertising_Data, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* FUNCTION: hciSetRspData
* USE: Set EIR data for LE scan responses
* PARAMS: data - the data
* dlen - length of said data
* cbkDone - callback to call when operation completes
* cbkData - data to pass to the callback
* RETURN: false on error, true if you should expect callback call
* NOTES:
*/
bool hciSetRspData(const uint8_t *data, uint8_t dlen, hciOpDoneCbk cbkDone, void *cbkData)
{
struct hciSetScanResponseData cmd;
if (!mHaveLe) {
loge("Refusing rsp data setting with no EDR support\n");
return false;
}
if (dlen > sizeof(cmd.scanRspData)) {
loge("Refusing LE rsp setting that is too long\n");
return false;
}
memcpy(cmd.scanRspData, data, dlen);
utilSetLE8(&cmd.scanRspDataLen, dlen);
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Set_Scan_Response_Data, &cmd, sizeof(cmd), cbkDone, cbkData);
}
/*
* This generates simple SYNC functions for BT commands.
* We need a lot of them, so we play with macros here.
* It's either this, magic number mess, or hundreds of
* functions. Deal with it.
*
*
*
* for all generated funcs this applies:
*
* RETURN: status byte or negatives on error
* NOTES: sync
*
* WRITE funcs just return status value, the reset of the event is discarded. expects cmd complete event
* READ funcs take "evt" param to save event to. expects cmd complete event
* NOCMDPARAMS funcs do not have params. expects cmd complete event
* W_STATUS - expects cmd status event
*/
#define HCI_SIMPLY_SYNC_CMD_W_STATUS(name, params, ogf, ocf) \
static int hci##name##Sync params \
{ \
struct hci##name cmdD; \
struct hci##name *cmd = &cmdD; \
const uint32_t cmdSz = sizeof(*cmd); \
struct hciEvtCmdStatus evtD; \
struct hciEvtCmdStatus *evt = &evtD; \
const uint8_t _ogf = ogf; \
const uint16_t _ocf = ocf; \
const bool want_sta = true; \
uint32_t _len = 0;
#define HCI_SIMPLY_SYNC_WRITE(name, params, ogf, ocf) \
static int hci##name##Sync params \
{ \
struct hci##name cmdD; \
struct hci##name *cmd = &cmdD; \
const uint32_t cmdSz = sizeof(*cmd); \
struct hciCmpl##name evtD; \
struct hciCmpl##name *evt = &evtD; \
const uint8_t _ogf = ogf; \
const uint16_t _ocf = ocf; \
const bool want_sta = false; \
uint32_t _len = 0;
#define HCI_SIMPLY_SYNC_WRITE_NOCMDPARAMS(name, ogf, ocf) \
static int hci##name##Sync (void) \
{ \
struct hci##name *cmd = NULL; \
const uint32_t cmdSz = 0; \
struct hciCmpl##name evtD; \
struct hciCmpl##name *evt = &evtD; \
const uint8_t _ogf = ogf; \
const uint16_t _ocf = ocf; \
const bool want_sta = false; \
uint32_t _len = 0;
#define HCI_SIMPLY_SYNC_READ(name, params, ogf, ocf) \
static int hci##name##Sync params \
{ \
struct hci##name cmdD; \
struct hci##name *cmd = &cmdD; \
const uint32_t cmdSz = sizeof(*cmd); \
const uint8_t _ogf = ogf; \
const uint16_t _ocf = ocf; \
const bool want_sta = false; \
uint32_t _len = 0;
#define HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(name, params, ogf, ocf) \
static int hci##name##Sync params \
{ \
struct hci##name *cmd = NULL; \
const uint32_t cmdSz = 0; \
const uint8_t _ogf = ogf; \
const uint16_t _ocf = ocf; \
const bool want_sta = false; \
uint32_t _len = 0;
#define REQUIRE_VERSION(_v) if (mBtVer < _v) { \
loge("attempt to call 0x%02X.%04X on v%u\n", _ogf, _ocf, mBtVer); \
return -2; \
}
#define REQUIRE_FEATURE(_page, _v) if (!(mLocalFtrs[_page] & _v)) { \
loge("attempt to call 0x%02X.%04X w/o feature\n", _ogf, _ocf); \
return -2; \
}
#define REQUIRE_LE_FEATURE(_v) if (!(mLocalLeFtrs & _v)) { \
loge("attempt to call 0x%02X.%04X w/o feature\n", _ogf, _ocf); \
return -2; \
}
#define HCI_COPY_IN_U64(name) utilSetLE64(&cmd->name, name);
#define HCI_COPY_IN_U32(name) utilSetLE32(&cmd->name, name);
#define HCI_COPY_IN_U24(name) utilSetLE24(&cmd->name, name);
#define HCI_COPY_IN_U16(name) utilSetLE16(&cmd->name, name);
#define HCI_COPY_IN_U8(name) utilSetLE8(&cmd->name, name);
#define HCI_COPY_IN_BOOL(name) utilSetLE8(&cmd->name, name ? 1 : 0);
#define HCI_COPY_IN_MAC(name) memcpy(cmd->name, name, sizeof(cmd->name));
#define HCI_COPY_IN_DATA(name,len) if (len > sizeof(cmd->name)) { \
logw("Truncating "#name" to %ub\n",(int)sizeof(cmd->name)); \
len = sizeof(cmd->name); \
} \
memcpy(cmd->name, name, len);
#define HCI_SIMPLY_SYNC_END if (!want_sta && !hciCmdSubmitSimpleWithCompleteSync(_ogf, _ocf, cmd, \
cmdSz, evt, sizeof(*evt))) \
return -1; \
if (want_sta && !hciCmdSubmitSimpleWithStatusSync(_ogf, _ocf, cmd, \
cmdSz, (struct hciEvtCmdStatus*)evt)) \
return -1; \
return (unsigned)utilGetLE8(&evt->status); \
}
/*
* FUNCTION: hciSetEventMaskSync
* USE: Set event mask
* PARAMS: mask - the wanted mask
*/
HCI_SIMPLY_SYNC_WRITE(SetEventMask, (uint64_t mask), HCI_OGF_Controller_and_Baseband, HCI_CMD_Set_Event_Mask)
HCI_COPY_IN_U64(mask)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWritePinTypeSync
* USE: Set whether we have a fixed or variable PIN
* PARAMS: isFixed - is the pin fixed?
*/
HCI_SIMPLY_SYNC_WRITE(WritePinType, (bool isFixed), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_PIN_Type)
HCI_COPY_IN_BOOL(isFixed)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWritePageTimeoutSync
* USE: Set how long we try to connect at link layer
* PARAMS: timeout - the timeout in unit of 0.625ms
*/
HCI_SIMPLY_SYNC_WRITE(WritePageTimeout, (uint16_t timeout), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Page_Timeout)
HCI_COPY_IN_U16(timeout)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteSyncFlowCtrlEnableSync
* USE: Set whether we get command complete packets for SCO data
* PARAMS: syncFlowCtrlOn - set if we will get those packets, false else
*/
HCI_SIMPLY_SYNC_WRITE(WriteSyncFlowCtrlEnable, (bool syncFlowCtrlOn), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_SCO_Flow_Control_Enable)
HCI_COPY_IN_BOOL(syncFlowCtrlOn)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteConnAcceptTimeoutSync
* USE: Set how long after a connection request comes in taht we auto-reject it
* PARAMS: timeout - timeout in units of 0.625ms
*/
HCI_SIMPLY_SYNC_WRITE(WriteConnAcceptTimeout, (uint16_t timeout), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Connection_Accept_Timeout)
HCI_COPY_IN_U16(timeout)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteInquiryModeSync
* USE: Set what kind of inquiry result we want
* PARAMS: inqMode - the result format (INQ_MODE_*)
*/
HCI_SIMPLY_SYNC_WRITE(WriteInquiryMode, (uint8_t inqMode), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Inquiry_Mode)
REQUIRE_VERSION(HCI_VERSION_1_2)
HCI_COPY_IN_U8(inqMode)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteSimplePairingModeSync
* USE: Enable/disable SSP
* PARAMS: sspOn - use ssp?
*/
HCI_SIMPLY_SYNC_WRITE(WriteSimplePairingMode, (bool useSsp), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Simple_Pairing_Mode)
REQUIRE_VERSION(HCI_VERSION_2_1)
REQUIRE_FEATURE(0, HCI_LMP_FTR_SSP)
HCI_COPY_IN_BOOL(useSsp)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteLeHostSupportedSync
* USE: Enable/disable LE
* PARAMS: leSupportedHost - does host support LE?
* simultaneousLeHost - does host support simultaneous LE/EDR to same device
*/
HCI_SIMPLY_SYNC_WRITE(WriteLeHostSupported, (uint8_t leSupportedHost, uint8_t simultaneousLeHost), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_LE_Host_Supported)
REQUIRE_VERSION(HCI_VERSION_4_0)
REQUIRE_FEATURE(0, HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER)
if (simultaneousLeHost) {
REQUIRE_FEATURE(0, HCI_LMP_FTR_SIMUL_LE_EDR_CAPABLE_CONTROLLER)
}
HCI_COPY_IN_BOOL(leSupportedHost)
HCI_COPY_IN_BOOL(simultaneousLeHost)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWritePageScanActivitySync
* USE: Set connectivity settings (page scan)
* PARAMS: scanInterval - the scan interval
* scanWindow - the scan window
*/
HCI_SIMPLY_SYNC_WRITE(WritePageScanActivity, (uint16_t scanInterval, uint16_t scanWindow), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Page_Scan_Activity)
HCI_COPY_IN_U16(scanInterval)
HCI_COPY_IN_U16(scanWindow)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciWriteInquiryScanActivitySync
* USE: Set discoverability settings (inquiry scan)
* PARAMS: scanInterval - the scan interval
* scanWindow - the scan window
*/
HCI_SIMPLY_SYNC_WRITE(WriteInquiryScanActivity, (uint16_t scanInterval, uint16_t scanWindow), HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Inquiry_Scan_Activity)
HCI_COPY_IN_U16(scanInterval)
HCI_COPY_IN_U16(scanWindow)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciReadLocalVersionSync
* USE: read local version
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(ReadLocalVersion, (struct hciCmplReadLocalVersion *evt), HCI_OGF_Informational, HCI_CMD_Read_Local_Version_Information)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciReadLocalSupportedFeaturesSync
* USE: read local supported features
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(ReadLocalSupportedFeatures, (struct hciCmplReadLocalSupportedFeatures *evt), HCI_OGF_Informational, HCI_CMD_Read_Local_Supported_Features)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciReadLocalExtendedFeaturesSync
* USE: read local extended features
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ(ReadLocalExtendedFeatures, (uint8_t page, struct hciCmplReadLocalExtendedFeatures *evt), HCI_OGF_Informational, HCI_CMD_Read_Local_Extended_Features)
REQUIRE_VERSION(HCI_VERSION_2_1)
REQUIRE_FEATURE(0, HCI_LMP_FTR_EXTENDED_FEATURES)
HCI_COPY_IN_U8(page)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciReadBufferSizeSync
* USE: read local EDR buffer sizes
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(ReadBufferSize, (struct hciCmplReadBufferSize *evt), HCI_OGF_Informational, HCI_CMD_Read_Buffer_Size)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciLeReadBufferSizeSync
* USE: read local LE buffer sizes
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(LeReadBufferSize, (struct hciCmplLeReadBufferSize *evt), HCI_OGF_LE, HCI_CMD_LE_Read_Buffer_Size)
REQUIRE_VERSION(HCI_VERSION_4_0)
REQUIRE_FEATURE(0, HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER);
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciReadBdAddrSync
* USE: read local MAC Address
* PARAMS: evt - event to return
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(ReadBdAddr, (struct hciCmplReadBdAddr *evt), HCI_OGF_Informational, HCI_CMD_Read_BD_ADDR)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciInquiryCancelSync
* USE: Cancel inquiry
* PARAMS: NONE
*/
HCI_SIMPLY_SYNC_WRITE_NOCMDPARAMS(InquiryCancel, HCI_OGF_Link_Control, HCI_CMD_Inquiry_Cancel)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciLeSetScanEnableSync
* USE: Enable/disable LE scan
* PARAMS: scanOn - scan on/off
* filterDuplicates - this rarely works, do not use
*/
HCI_SIMPLY_SYNC_WRITE(LeSetScanEnable, (bool scanOn, bool filterDuplicates), HCI_OGF_LE, HCI_CMD_LE_Set_Scan_Enable)
REQUIRE_VERSION(HCI_VERSION_4_0)
REQUIRE_FEATURE(0, HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER);
HCI_COPY_IN_BOOL(scanOn)
HCI_COPY_IN_BOOL(filterDuplicates)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciLeSetScanParamsSync
* USE: Set LE scan parameters
* PARAMS: scanOn - scan on/off
* filterDuplicates - this rarely works, do not use
*/
HCI_SIMPLY_SYNC_WRITE(LeSetScanParams, (uint8_t activeScan, uint16_t scanInterval, uint16_t scanWindow, bool useOwnRandomAddr, bool onlyWhitelist), HCI_OGF_LE, HCI_CMD_LE_Set_Scan_Parameters)
REQUIRE_VERSION(HCI_VERSION_4_0)
REQUIRE_FEATURE(0, HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER);
HCI_COPY_IN_U16(activeScan);
HCI_COPY_IN_U16(scanInterval);
HCI_COPY_IN_U16(scanWindow);
HCI_COPY_IN_BOOL(useOwnRandomAddr);
HCI_COPY_IN_BOOL(onlyWhitelist);
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: LeReadLocalSupportedFeatures
* USE: Set LE scan parameters
* PARAMS: scanOn - scan on/off
* filterDuplicates - this rarely works, do not use
*/
HCI_SIMPLY_SYNC_READ_NOCMDPARAMS(LeReadLocalSupportedFeatures, (struct hciCmplLeReadLocalSupportedFeatures* evt), HCI_OGF_LE, HCI_CMD_LE_Read_Local_Supported_Features)
REQUIRE_VERSION(HCI_VERSION_4_0)
REQUIRE_FEATURE(0, HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER);
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciRemoteNameRequestCancelSync
* USE: Cancel a remote name request
* PARAMS: mac -the mac
*/
HCI_SIMPLY_SYNC_WRITE(RemoteNameRequestCancel, (const uint8_t *mac), HCI_OGF_Link_Control, HCI_CMD_Remote_Name_Request_Cancel)
HCI_COPY_IN_MAC(mac);
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciInquirySync
* USE: Enable EDR scan
* PARAMS: lap - the lap to use
* inqLen - how long for (max is 0x30 -> 61.44 sec)
* numResp - max responses (0 = unlimited)
*/
HCI_SIMPLY_SYNC_CMD_W_STATUS(Inquiry, (uint32_t lap, uint8_t inqLen, uint8_t numResp), HCI_OGF_Link_Control, HCI_CMD_Inquiry)
HCI_COPY_IN_U24(lap)
HCI_COPY_IN_U8(inqLen)
HCI_COPY_IN_U8(numResp)
HCI_SIMPLY_SYNC_END
/*
* FUNCTION: hciInquiryUpdatePersistDeviceDb
* USE: Update persistent "seen devices" DB with EIR data
* PARAMS: addr - the address of the remote device
* devClsP - device class or NULL if not known
* eir - the provided EIR data (if any)
* eirLen - size of the above buffer
* RETURN: NONE
* NOTES:
*/
void hciInquiryUpdatePersistDeviceDb(const struct bt_addr *addr, const uint32_t *devClsP, const void *eir, uint8_t eirLen)
{
const uint8_t *name = NULL;
uint32_t nameLen = 0;
uint32_t eirDevCls;
bool nameIsFull = false;
if (eirLen) { /* parse eir to find name */
const uint8_t *eirP = (const uint8_t*)eir;
const uint8_t *eirEnd = eirP + eirLen;
while (eirP < eirEnd) {
uint8_t type, len = *eirP++;
if (!len) /* zero length item means end of EIR */
break;
type = *eirP++;
len--;
if (type == HCI_EIR_TYPE_SHORTENED_NAME || type == HCI_EIR_TYPE_FULL_NAME) {
name = eirP;
nameLen = len;
nameIsFull = type == HCI_EIR_TYPE_FULL_NAME;
} else if (type == HCI_EIR_DEV_CLS && len == 3) {
eirDevCls = utilGetLE24(eirP);
devClsP = &eirDevCls;
}
eirP += len;
}
}
persistAddKnownDev(addr, name, name ? &nameLen : NULL, nameIsFull, devClsP);
}
/*
* FUNCTION: hciInquiryEvtEdrSingleItem
* USE: Enqueue a callback to discovery EDR cbk
* PARAMS: addr - the address of the remote device
* devCls - device class
* fng - opaque struct to allow a name request
* rssi - the RSSI
* eir - the provided EIR data (if any)
* eirLen - size of the above buffer
* RETURN: NONE
* NOTES: a copy of all params is made
*/
static void hciInquiryEvtEdrSingleItem(const struct bt_addr *addr, uint32_t devCls, struct hciNameGetInfo* fng, int8_t rssi, const void *eir, uint8_t eirLen)
{
struct hciCbkWorkItem *w;
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem) + eirLen);
if (!w) {
logw("Dropping EDR discovered item due to memory error\n");
return;
}
w->cbkWorkItemType = CBK_WORK_EDR_DISC_DEV;
memcpy(&w->edrDev.addr, addr, sizeof(w->edrDev.addr));
memcpy(&w->edrDev.fng, fng, sizeof(w->edrDev.fng));
memcpy(&w->edrDev.eir, eir, eirLen);
w->edrDev.devCls = devCls;
w->edrDev.rssi = rssi;
w->edrDev.eirLen = eirLen;
hciInquiryUpdatePersistDeviceDb(addr, &devCls, eir, eirLen);
if (workQueuePut(mCallbackWork, w))
return;
free(w);
logi("Dropping EDR discovered item dues to queue error\n");
}
/*
* FUNCTION: hciInquiryEvtLeSingleItem
* USE: Enqueue a callback to discovery LE cbk
* PARAMS: addr - the address of the remote device
* advType - type of reply
* rssi - the RSSI
* advData - the provided advertisement data
* advDataLen - size of the above buffer
* RETURN: NONE
* NOTES: a copy of all params is made
*/
static void hciInquiryEvtLeSingleItem(const struct bt_addr *addr, uint8_t advType, int8_t rssi, const void *advData, uint8_t advDataLen)
{
struct hciCbkWorkItem *w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem) + advDataLen);
if (!w) {
logw("Dropping LE discovered item due to memory error\n");
return;
}
w->cbkWorkItemType = CBK_WORK_LE_DISC_DEV;
memcpy(&w->leDev.addr, addr, sizeof(w->leDev.addr));
memcpy(w->leDev.adv, advData, advDataLen);
w->leDev.advType = advType;
w->leDev.advLen = advDataLen;
w->leDev.rssi = rssi;
hciInquiryUpdatePersistDeviceDb(addr, NULL, advData, advDataLen);
if (workQueuePut(mCallbackWork, w))
return;
free(w);
logi("Dropping LE discovered item dues to queue error\n");
}
/*
* FUNCTION: hciInquiryEvtEdrName
* USE: Enqueue a callback to discovery EDR name cbk
* PARAMS: dns - the request state we care about
* prev - the previous request state in line
* nameReqState - HCI_NAME_REQ_STATUS_*
* lastCbk - if set, we'll free the hciDiscoverNameState struct
* name - name buffer pointer (may not be null-terminated)
* nameMaxLen - size of the above buffer
* RETURN: NONE
* NOTES: a copy of all params is made as needed. call with mDiscoveryStateLock held
*/
static void hciInquiryEvtEdrName(struct hciDiscoverNameState *dns, struct hciDiscoverNameState *prev, uint8_t nameReqState, bool lastCbk, const char *name, uint8_t nameMaxLen)
{
struct hciCbkWorkItem *w;
bool success = false;
uint8_t nameLen;
for (nameLen = 0; nameLen < nameMaxLen && name[nameLen]; nameLen++);
w = (struct hciCbkWorkItem*)malloc(sizeof(struct hciCbkWorkItem) + nameLen + 1);
if (!w) {
logw("Dropping EDR name discovered item due to memory error\n");
return;
}
w->cbkWorkItemType = CBK_WORK_EDR_DISC_NAME;
memcpy(&w->edrName.addr, &dns->addr, sizeof(w->edrName.addr));
memcpy(&w->edrName.name, name, nameLen);
w->edrName.name[nameLen] = 0;
w->edrName.nameReqState = nameReqState;
if (!dns)
logd("Got name reply with nobody waiting for it. Dropping\n");
else{
w->edrName.cbk = dns->cbk;
w->edrName.cbkData = dns->cbkData;
if (workQueuePut(mCallbackWork, w))
success = true;
/* remove the item if needed */
if (lastCbk) {
if (prev)
prev->next = dns->next;
else
mDiscoverNames = dns->next;
free(dns);
}
}
if (!success) {
free(w);
logi("Dropping EDR name discovered item due to queue or other error\n");
}
}
/*
* FUNCTION: hciInquiryEvtEdrNameMac
* USE: Enqueue a callback to discovery EDR name cbk (find by MAC)
* PARAMS: mac - the raw mac address of the remote device
* nameReqState - HCI_NAME_REQ_STATUS_*
* lastCbk - if set, we'll free the hciDiscoverNameState struct
* name - name buffer pointer (may not be null-terminated)
* nameMaxLen - size of the above buffer
* RETURN: NONE
* NOTES: a copy of all params is made as needed
*/
static void hciInquiryEvtEdrNameMac(const uint8_t *mac, uint8_t nameReqState, bool lastCbk, const char *name, uint32_t nameMaxLen)
{
struct hciDiscoverNameState *dns, *prev = NULL;
struct bt_addr addr;
/* now find the callback & enqueue the work*/
pthread_mutex_lock(&mDiscoveryStateLock);
dns = mDiscoverNames;
while (dns && memcmp(&dns->addr.addr, mac, sizeof(dns->addr.addr))) {
prev = dns;
dns = dns->next;
}
if (!dns) {
logw("No record of this name inquiry found. Dropping\n");
return;
}
addr.type = BT_ADDR_TYPE_EDR;
memcpy(addr.addr, mac, sizeof(addr.addr));
persistAddKnownDev(&addr, name, &nameMaxLen, true, NULL);
hciInquiryEvtEdrName(dns, prev, nameReqState, lastCbk, name, nameMaxLen);
pthread_mutex_unlock(&mDiscoveryStateLock);
}
/*
* FUNCTION: hciInquiryEvtEdrNameHandle
* USE: Enqueue a callback to discovery EDR name cbk (find by handle)
* PARAMS: handle - the handle fo the request
* nameReqState - HCI_NAME_REQ_STATUS_*
* lastCbk - if set, we'll free the hciDiscoverNameState struct
* name - name buffer pointer (may not be null-terminated)
* nameMaxLen - size of the above buffer
* RETURN: NONE
* NOTES: a copy of all params is made as needed
*/
static void hciInquiryEvtEdrNameHandle(uniq_t handle, uint8_t nameReqState, bool lastCbk, const char *name, uint8_t nameMaxLen)
{
struct hciDiscoverNameState *dns, *prev = NULL;
/* now find the callback & enqueue the work*/
pthread_mutex_lock(&mDiscoveryStateLock);
dns = mDiscoverNames;
while (dns && dns->handle != handle) {
prev = dns;
dns = dns->next;
}
if (!dns) {
logw("No record of this name inquiry found. Dropping\n");
return;
}
hciInquiryEvtEdrName(dns, prev, nameReqState, lastCbk, name, nameMaxLen);
pthread_mutex_unlock(&mDiscoveryStateLock);
}
/*
* FUNCTION: hciConnRequestReplyYes
* USE: Called externally to accept a connection request
* PARAMS: addr - the address of requestor
* becomeMaster - try to become master?
* RETURN: false on error
* NOTES:
*/
bool hciConnRequestReplyYes(const struct bt_addr *addr, bool becomeMaster)
{
struct hciAcceptConnection cmd;
if (!BT_ADDR_IS_EDR(*addr)) {
logw("Acoonot accept non-EDR connection\n");
return false;
}
memcpy(cmd.mac, addr->addr, sizeof(cmd.mac));
utilSetLE8(&cmd.remainSlave, becomeMaster ? 1 : 0);
return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Accept_Connection_Request, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL);
}
/*
* FUNCTION: hciConnRequestReplyNo
* USE: Called externally to refuse a connection request
* PARAMS: addr - the address of requestor
* RETURN: false on error
* NOTES:
*/
bool hciConnRequestReplyNo(const struct bt_addr *addr)
{
struct hciRejectConnection cmd;
if (!BT_ADDR_IS_EDR(*addr)) {
logw("Acoonot accept non-EDR connection\n");
return false;
}
memcpy(cmd.mac, addr->addr, sizeof(cmd.mac));
utilSetLE8(&cmd.reason, 0x13); //TODO: better dealings with this value...
return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Reject_Connection_Request, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL);
}
/*
* FUNCTION: hciSecUserCbkPinCodeReq
* USE: Called externally to give us the PIN to use for a connection
* PARAMS: addr - the address of peer
* pin - the pin
* len - pin length 1..HCI_PIN_MAX_LEN or 0 for none
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkPinCodeReq(const struct bt_addr *addr, const uint8_t *pin, uint8_t len)
{
struct hciPinCodeRequestNegativeReply cmdN;
struct hciPinCodeRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
if (len >= HCI_LINK_KEY_LEN)
len = HCI_LINK_KEY_LEN;
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
memcpy(cmdY.pinCode, pin, len);
utilSetLE8(&cmdY.pinCodeLen, len);
if (len)
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_PIN_Code_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_PIN_Code_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to pin code request\n");
}
/*
* FUNCTION: hciSecUserCbkLinkKeyReq
* USE: Called externally to give us the LinkKey to use for a connection
* PARAMS: addr - the address of peer
* cbkData - data we sent to external code
* key - the key (HCI_LINK_KEY_LE bytes) or NULL if none
* RETURN: NONE
* NOTES:
*/
static void hciSecUserCbkLinkKeyReq(const struct bt_addr *addr, const uint8_t *key)
{
struct hciLinkKeyRequestNegativeReply cmdN;
struct hciLinkKeyRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
if (key){
memcpy(&cmdY.key, key, HCI_LINK_KEY_LEN);
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Link_Key_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
} else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Link_Key_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to link key request\n");
}
/*
* FUNCTION: hciSecUserCbkSspIoCapabilityReq
* USE: Called externally to give us our IO capabilities
* PARAMS: addr - the address of peer
* cap - our capabilities (HCI_DISP_CAP_*)
* haveOobData - do we have OOB data
* authReq - what we want (HCI_AUTH_REQMENT_*) or HCI_AUTH_REQMENT_CANCEL_SSP
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkSspIoCapabilityReq(const struct bt_addr *addr, uint8_t cap, bool haveOobData, uint8_t authReq)
{
struct hciIoCapabilityRequestNegativeReply cmdN;
struct hciIoCapabilityRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
utilSetLE8(&cmdN.reason, 0x13); //TODO: better handing of this value
utilSetLE8(&cmdY.cap, cap);
utilSetLE8(&cmdY.oobPresent, haveOobData);
utilSetLE8(&cmdY.authReqments, authReq);
if (authReq != HCI_AUTH_REQMENT_CANCEL_SSP)
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_IO_Capability_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_IO_Capability_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to IO cap request\n");
}
/*
* FUNCTION: hciSecUserCbkSspUserConfirmationReq
* USE: Called externally to give us confrimation (or not) of number displayed
* PARAMS: addr - the address of peer
* confirmed - confirmed?
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkSspUserConfirmationReq(const struct bt_addr *addr, bool confirmed)
{
struct hciUserConfRequestNegativeReply cmdN;
struct hciUserConfRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
if (confirmed)
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_User_Confirmation_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_User_Confirmation_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to user confirmation request\n");
}
/*
* FUNCTION: hciSecUserCbkSspUserPasskeyReq
* USE: Called externally to give us the number user entered
* PARAMS: addr - the address of peer
* numeric - what user entered or HCI_SSP_NUMERIC_INVALID if user cancelled
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkSspUserPasskeyReq(const struct bt_addr *addr, uint32_t numeric)
{
struct hciUserPasskeyRequestNegativeReply cmdN;
struct hciUserPasskeyRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
if (numeric != HCI_SSP_NUMERIC_INVALID) {
utilSetLE32(&cmdY.num, numeric);
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_User_Passkey_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
} else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_User_Passkey_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to user passkey request\n");
}
/*
* FUNCTION: hciSecUserCbkSspUserPasskeyReqProgress
* USE: Called externally to give us the user's progress on number entry
* PARAMS: addr - the address of peer
* evt - the user event (HCI_SSP_ENTRY_*)
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkSspUserPasskeyReqProgress(const struct bt_addr *addr, uint8_t evt)
{
struct hciSendKeypressNotification cmd;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmd.mac, addr->addr, sizeof(cmd.mac));
utilSetLE8(&cmd.notifType, evt);
if (!hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Send_Keypress_Notification, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL))
loge("Failed to send user passkey progress event\n");
}
/*
* FUNCTION: hciSecUserCbkSspUserOobReq
* USE: Called externally to give us the OOD data
* PARAMS: addr - the address of peer
* cbkData - data we sent to external code
* C - the C value (HCI_OOB_LEN bytes) or NULL if none
* R - the R value (HCI_OOB_LEN bytes) or NULL if none
* RETURN: NONE
* NOTES:
*/
void hciSecUserCbkSspUserOobReq(const struct bt_addr *addr, const uint8_t *C, const uint8_t *R)
{
struct hciRemoteOobDataRequestNegativeReply cmdN;
struct hciRemoteOobDataRequestReply cmdY;
bool ret;
if (!BT_ADDR_IS_EDR(*addr)) {
loge("cannot use LinkControl commands to reply to non-EDR security-related requests\n");
return;
}
memcpy(cmdY.mac, addr->addr, sizeof(cmdY.mac));
memcpy(cmdN.mac, addr->addr, sizeof(cmdN.mac));
if (C && R) {
memcpy(cmdY.C, C, sizeof(cmdY.C));
memcpy(cmdY.R, R, sizeof(cmdY.R));
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Remote_OOB_Data_Request_Reply, &cmdY, sizeof(cmdY), hciCmdStatusCbk, NULL);
} else
ret = hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Remote_OOB_Data_Request_Negative_Reply, &cmdN, sizeof(cmdN), hciCmdStatusCbk, NULL);
if (!ret)
loge("Failed to reply to OOB data request\n");
}
/*
* FUNCTION: hciSecHandleLinkKeyReq
* USE: Try to find the proper key for a given EDR connection
* PARAMS: addr - the peer address
* RETURN: NONE
* NOTES:
*/
static void hciSecHandleLinkKeyReq(const struct bt_addr *addr)
{
uint8_t key[HCI_LINK_KEY_LEN];
bool mitmProt = true;
if (!persistGetDevKey(addr, KEY_TYPE_MITM_PROTECTED, key)) {
mitmProt = false;
if (!persistGetDevKey(addr, KEY_TYPE_MITM_UNPROTECTED, key)) {
logd("Link key request from "ADDRFMT" declined\n", ADDRCONV(*addr));
hciSecUserCbkLinkKeyReq(addr, NULL);
return;
}
}
logd("Found and provided link key for "ADDRFMT". Mitm protection: %s\n", ADDRCONV(*addr), mitmProt ? "YES" : "NO");
hciSecUserCbkLinkKeyReq(addr, key);
}
/*
* FUNCTION: hciSecHandleNewLinkKey
* USE: Record a new link key for an EDR device
* PARAMS: addr - the peer address
* key - the key provided
* btKeyType - key type as per BT spec
* RETURN: NONE
* NOTES:
*/
static void hciSecHandleNewLinkKey(const struct bt_addr *addr, const uint8_t *key, uint8_t btKeyType)
{
uint8_t persistKeyType;
switch (btKeyType) {
case HCI_EDR_LINK_KEY_COMBO:
case HCI_EDR_LINK_KEY_AUTH_COMBO:
case HCI_EDR_LINK_KEY_CHANGED:
persistKeyType = KEY_TYPE_MITM_PROTECTED;
break;
case HCI_EDR_LINK_KEY_LOCAL:
case HCI_EDR_LINK_KEY_REMOTE:
case HCI_EDR_LINK_KEY_UNAUTH_COMBO:
persistKeyType = KEY_TYPE_MITM_UNPROTECTED;
break;
case HCI_EDR_LINK_KEY_DEBUG:
default:
logd("Ignoring key type %u\n", btKeyType);
return;
}
if (!persistAddDevKey(addr, persistKeyType, key))
logw("Failed to stor elink key for "ADDRFMT".\n", ADDRCONV(*addr));
}
/*
* FUNCTION: hciSecUserEvtCbk
* USE: Called with user-interaction-related security events
* PARAMS: evt - the event
* cbkData - callback data
* evtWaitStateID - event wait state ID (unused)
* forCmdID - event commadn ID (unused)
* RETURN: false on error
* NOTES:
*/
static bool hciSecUserEvtCbk(const struct hciEvtHdr *evtHdr, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID)
{
struct bt_addr addr = {.type = BT_ADDR_TYPE_EDR};
struct hciAclConn* conn;
uint8_t evtType;
if (!evtHdr)
return false;
evtType = utilGetLE8(&evtHdr->code);
if (evtType == HCI_EVT_PIN_Code_Request) {
struct hciEvtPinCodeReq *evt = (struct hciEvtPinCodeReq*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByAddr(&addr);
if (conn && conn->state == CONN_STATE_CFG) {
/*
* We do not call java to give congfig time to finish. See comment for SSP under HCI_EVT_IO_Capability_Request
*/
conn->pinPending = true;
} else {
if (!conn)
logw("Connection not found for pending PIN req\n");
aapiBondStateChangedCbk(&addr, AAPI_BOND_STATE_INPROGRESS);
aapiPinReqCbk(&addr);
}
pthread_mutex_unlock(&mConnsLock);
} else if (evtType == HCI_EVT_Link_Key_Request) {
struct hciEvtPinCodeReq *evt = (struct hciEvtPinCodeReq*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
hciSecHandleLinkKeyReq(&addr);
} else if (evtType == HCI_EVT_Link_Key_Notification) {
struct hciEvtLinkKeyNotif *evt = (struct hciEvtLinkKeyNotif*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
hciSecHandleNewLinkKey(&addr, evt->key, utilGetLE8(&evt->keyType));
} else if (evtType == HCI_EVT_IO_Capability_Request) {
struct hciEvtIoCapRequest *evt = (struct hciEvtIoCapRequest*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByAddr(&addr);
if (conn && conn->state == CONN_STATE_CFG) {
/*
* Not replying immediately here holds SSP up a bit, allowing connection configuration to complete. This
* means that we'll know the device name by the time we tell java about this SSP request.
*/
conn->sspPending = true;
} else {
if (!conn)
logw("Connection not found for pending SSP req\n");
hciSecUserCbkSspIoCapabilityReq(&addr, mIoCapability, false, HCI_AUTH_REQMENT_MITM_PROT_GENERAL_BOND);
}
pthread_mutex_unlock(&mConnsLock);
} else if (evtType == HCI_EVT_IO_Capability_Response) {
struct hciEvtIoCapResponse *evt = (struct hciEvtIoCapResponse*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
logd("Remote dev capabilities: %u oob %u authReq %u\n", utilGetLE8(&evt->ioCapability), !!utilGetLE8(&evt->oobDataPresent), utilGetLE8(&evt->authReqments));
} else if (evtType == HCI_EVT_User_Confirmation_Request) {
struct hciEvtUserConfRequest *evt = (struct hciEvtUserConfRequest*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
aapiBondStateChangedCbk(&addr, AAPI_BOND_STATE_INPROGRESS);
aapiSspReqCbk(&addr, BT_SSP_VARIANT_PASSKEY_CONFIRMATION, utilGetLE32(&evt->numericValue));
} else if (evtType == HCI_EVT_User_Passkey_Request) {
struct hciEvtUserPasskeyRequest *evt = (struct hciEvtUserPasskeyRequest*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
aapiBondStateChangedCbk(&addr, AAPI_BOND_STATE_INPROGRESS);
aapiSspReqCbk(&addr, BT_SSP_VARIANT_PASSKEY_ENTRY, 0);
} else if (evtType == HCI_EVT_Remote_OOB_Data_Request) {
struct hciEvtRemoteOobRequest *evt = (struct hciEvtRemoteOobRequest*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
logd("Denying OOB request for SSP\n");
hciSecUserCbkSspUserOobReq(&addr, NULL, NULL);
} else if (evtType == HCI_EVT_Simple_Pairing_Complete) {
struct hciEvtSimplePairingComplete *evt = (struct hciEvtSimplePairingComplete*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
aapiBondStateChangedCbk(&addr, utilGetLE8(&evt->status) ? AAPI_BOND_STATE_FAILED : AAPI_BOND_STATE_SUCCESS);
} else if (evtType == HCI_EVT_User_Passkey_Notification) {
struct hciEvtUserPasskeyNotif *evt = (struct hciEvtUserPasskeyNotif*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
aapiBondStateChangedCbk(&addr, AAPI_BOND_STATE_INPROGRESS);
aapiSspReqCbk(&addr, BT_SSP_VARIANT_PASSKEY_NOTIFICATION, utilGetLE32(&evt->passkey));
} else if (evtType == HCI_EVT_Keypress_Notification) {
struct hciEvtKeypressNotification *evt = (struct hciEvtKeypressNotification*)(evtHdr + 1);
memcpy(addr.addr, evt->mac, sizeof(addr.addr));
logi("Dropping remote SSP passkey key entry event %u\n", utilGetLE8(&evt->notifType));
} else
return false;
return true;
}
/*
* FUNCTION: hciConnEvtCbk
* USE: Called with connection state events
* PARAMS: evt - the event
* cbkData - callback data
* evtWaitStateID - event wait state ID (unused)
* forCmdID - event commadn ID (unused)
* RETURN: false on error
* NOTES:
*/
static bool hciConnEvtCbk(const struct hciEvtHdr *evt, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID)
{
uint8_t evtType;
if (!evt)
return false;
evtType = utilGetLE8(&evt->code);
if (evtType == HCI_EVT_Connection_Request) {
struct hciEvtConnRequest *req = (struct hciEvtConnRequest*)(evt + 1);
uint32_t devCls = utilGetLE24(req->deviceClass);
struct bt_addr addr;
memcpy(addr.addr, req->mac, sizeof(addr.addr));
addr.type = BT_ADDR_TYPE_EDR;
persistAddKnownDev(&addr, NULL, NULL, false, &devCls); /* record the device sighting */
//TODO: ask higher layer/user/whatever?
logd("ACL EDR conn request from "MACFMT" of class "CLSFMT" -> accepting\n", MACCONV(req->mac), CLSCONV(req->deviceClass));
if (!hciConnRequestReplyYes(&addr, true))
loge("Failed to accept this connection\n");
} else if (evtType == HCI_EVT_Connection_Complete) {
struct hciEvtConnComplete *conn = (struct hciEvtConnComplete*)(evt + 1);
uint16_t cid = utilGetLE16(&conn->conn);
bool isAcl = !!utilGetLE8(&conn->isAclLink);
bool encr = !!utilGetLE8(&conn->encrypted);
uint8_t status = utilGetLE8(&conn->status);
if (!isAcl) {
cid &= SCO_HDR_MASK_CONN_ID;
//TODO: SCO EDR conn
} else {
cid &= ACL_HDR_MASK_CONN_ID;
logd("ACL EDR conn status %d to "MACFMT" w/ handle %d encr %d\n", status, MACCONV(conn->mac), cid, encr);
if (!hciHandleConnAclEdrUp(status, cid, conn->mac, encr))
loge("Failed to handle EDR conn up\n");
}
} else if (evtType == HCI_EVT_Disconnection_Complete) {
struct hciEvtDiscComplete *disc = (struct hciEvtDiscComplete*)(evt + 1);
uint8_t status = utilGetLE8(&disc->status);
uint16_t cid = utilGetLE16(&disc->conn);
uint8_t reason = utilGetLE8(&disc->reason);
logd("ACL conn %d down for reason %d with status %d\n", cid, reason, status);
hciHandleConnAclDown(cid, reason);
} else if (evtType == HCI_EVT_Encryption_Change) {
struct hciEvtEncrChange *encr = (struct hciEvtEncrChange*)(evt + 1);
uint8_t status = utilGetLE8(&encr->status);
uint16_t cid = utilGetLE16(&encr->conn);
bool encrOn = !status && utilGetLE8(&encr->encrOn);
logd("ACL conn %d encr %d with status %d\n", cid, encrOn, status);
if (!hciHandleConnAclEncr(cid, encrOn))
loge("Failed to handle conn encr\n");
} else if (evtType == HCI_EVT_Authentication_Complete) {
struct hciEvtEncrChange *encr = (struct hciEvtEncrChange*)(evt + 1);
uint8_t status = utilGetLE8(&encr->status);
uint16_t cid = utilGetLE16(&encr->conn);
logd("ACL conn %d auth with status %d\n", cid, status);
if (!hciHandleConnAclAuth(cid, !status))
loge("Failed to handle conn auth\n");
} else if (evtType == HCI_EVT_Role_Change) {
struct hciEvtRoleChange *role = (struct hciEvtRoleChange*)(evt + 1);
uint8_t status = utilGetLE8(&role->status);
bool amSlave = !!utilGetLE8(&role->amSlave);
logd("ACL EDR conn to "MACFMT" changed role to %c with status %d\n", MACCONV(role->mac), amSlave ? 'S' : 'M', status);
if (!hciHandleConnAclRoleChange(role->mac, amSlave))
loge("Failed to handle EDR conn role change\n");
} else if (evtType == HCI_EVT_LE_Meta) {
struct hciEvtLeMeta *meta = (struct hciEvtLeMeta*)(evt + 1);
evtType = utilGetLE8(&meta->subevent);
if (evtType == HCI_EVTLE_Connection_Complete) {
struct hciEvtLeConnectionComplete *conn = (struct hciEvtLeConnectionComplete*)(meta + 1);
uint8_t status = utilGetLE8(&conn->status);
uint16_t cid = utilGetLE16(&conn->conn);
bool amSlave = !!utilGetLE8(&conn->amSlave);
bool peerAddrRandom = !!utilGetLE8(&conn->peerAddrRandom);
uint16_t interval = utilGetLE16(&conn->connInterval);
uint16_t latency = utilGetLE16(&conn->connLatency);
uint16_t timeout = utilGetLE16(&conn->supervisionTimeout);
uint8_t mca = utilGetLE8(&conn->masterClockAccuracy);
logd("ACL LE conn %d up to %c"MACFMT" as %c: {%d,%d,%d,%d}\n", cid, peerAddrRandom ? 'R' : 'P', MACCONV(conn->peerMac), amSlave ? 'S' : 'M', interval, latency, timeout, mca);
if (!hciHandleConnAclLeUp(status, cid, !amSlave, conn->peerMac, peerAddrRandom, interval, latency, timeout, mca))
loge("Failed to handle conn LE conn up\n");
} else if (evtType == HCI_EVTLE_Connection_Update_Complete) {
struct hciEvtLeConnectionUpdateComplete *updt = (struct hciEvtLeConnectionUpdateComplete*)(meta + 1);
uint8_t status = utilGetLE8(&updt->status);
uint16_t cid = utilGetLE16(&updt->conn);
uint16_t interval = utilGetLE16(&updt->connInterval);
uint16_t latency = utilGetLE16(&updt->connLatency);
uint16_t timeout = utilGetLE16(&updt->supervisionTimeout);
logd("ACL LE conn %d update to {%d,%d,%d}\n", cid, interval, latency, timeout);
if (!hciHandleConnAclLeUpdate(status, cid, interval, latency, timeout))
loge("Failed to handle LE conn update\n");
} else
return false;
} else
return false;
return true;
}
/*
* FUNCTION: hciInquiryEvtCbk
* USE: Called with inquiry events
* PARAMS: evt - the event
* cbkData - callback data
* evtWaitStateID - event wait state ID (unused)
* forCmdID - event commadn ID (unused)
* RETURN: false on error
* NOTES:
*/
static bool hciInquiryEvtCbk(const struct hciEvtHdr *evt, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID)
{
uint8_t evtType;
struct bt_addr addr;
struct hciNameGetInfo fng;
uint8_t i, num;
if (!evt)
return false;
evtType = utilGetLE8(&evt->code);
if(evtType == HCI_EVT_Inquiry_Result) {
struct hciEvtInquiryResult *res = (struct hciEvtInquiryResult*)(evt + 1);
struct hciEvtInquiryResultItem *item = res->items;
addr.type = BT_ADDR_TYPE_EDR;
num = utilGetLE8(&res->numResponses);
for (i = 0; i < num; i++, item++) {
memcpy(addr.addr, item->mac, sizeof(addr.addr));
fng.PSRM = utilGetLE8(&item->PSRM);
fng.PSM = mBtVer >= HCI_VERSION_1_2 ? 0 : utilGetLE8(&item->PSM);
fng.clockOffset = utilGetLE16(&item->clockOffset) | HCI_CLOCK_OFST_VALID;
memcpy(fng.mac, item->mac, sizeof(fng.mac));
hciInquiryEvtEdrSingleItem(&addr, utilGetLE24(&item->deviceClass), &fng, HCI_RSSI_UNKNOWN, NULL, 0);
}
} else if(evtType == HCI_EVT_Inquiry_Result_With_RSSI) {
struct hciEvtInquiryResultWithRssi *res = (struct hciEvtInquiryResultWithRssi*)(evt + 1);
struct hciEvtInquiryResultWithRssiItem *item = res->items;
addr.type = BT_ADDR_TYPE_EDR;
num = utilGetLE8(&res->numResponses);
for (i = 0; i < num; i++, item++) {
memcpy(addr.addr, item->mac, sizeof(addr.addr));
fng.PSRM = utilGetLE8(&item->PSRM);
fng.PSM = 0;
fng.clockOffset = utilGetLE16(&item->clockOffset) | HCI_CLOCK_OFST_VALID;
memcpy(fng.mac, item->mac, sizeof(fng.mac));
hciInquiryEvtEdrSingleItem(&addr, utilGetLE24(&item->deviceClass), &fng, utilGetLE8(&item->RSSI), NULL, 0);
}
} else if(evtType == HCI_EVT_Extended_Inquiry_Result) {
struct hciEvtExtendedInquiryResult *res = (struct hciEvtExtendedInquiryResult*)(evt + 1);
addr.type = BT_ADDR_TYPE_EDR;
num = utilGetLE8(&res->numResponses);
if (num != 1)
logw("eir reply with num = %d dropped\n", num);
else {
memcpy(addr.addr, res->mac, sizeof(addr.addr));
fng.PSRM = utilGetLE8(&res->PSRM);
fng.PSM = 0;
fng.clockOffset = utilGetLE16(&res->clockOffset) | HCI_CLOCK_OFST_VALID;
memcpy(fng.mac, res->mac, sizeof(fng.mac));
hciInquiryEvtEdrSingleItem(&addr, utilGetLE24(&res->deviceClass), &fng, utilGetLE8(&res->RSSI), res->EIR, sizeof(res->EIR));
}
} else if(evtType == HCI_EVT_LE_Meta) {
struct hciEvtLeMeta *meta = (struct hciEvtLeMeta*)(evt + 1);
struct hciEvtLeAdvReport *adv = (struct hciEvtLeAdvReport*)(meta + 1);
struct hciEvtLeAdvReportItem *item = (struct hciEvtLeAdvReportItem*)(adv + 1);
uint8_t subevent = utilGetLE8(&meta->subevent);
uint8_t *rssiP;
if (subevent != HCI_EVTLE_Advertising_Report) {
loge("Unexpected LE subevent 0x%02X in discovery event handler\n", subevent);
return false;
}
num = utilGetLE8(&adv->numReports);
for (i = 0; i < num; i++) {
uint8_t dataLen = utilGetLE8(&item->dataLen);
addr.type = utilGetLE8(&item->randomAddr) ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC;
memcpy(addr.addr, item->mac, sizeof(addr.addr));
rssiP = item->data + dataLen;
hciInquiryEvtLeSingleItem(&addr, utilGetLE8(&item->advType), utilGetLE8(rssiP), item->data, dataLen);
item = (struct hciEvtLeAdvReportItem*)(rssiP + 1);
}
} else if (evtType == HCI_EVT_Command_Status) {
struct hciEvtCmdStatus *staEvt = (struct hciEvtCmdStatus*)(evt + 1);
uint8_t status = utilGetLE8(&staEvt->status);
uniq_t handle = *(uniq_t*)cbkData;
if (utilGetLE16(&staEvt->opcode) != CMD_MAKE_OPCODE(HCI_OGF_Link_Control, HCI_CMD_Remote_Name_Request)) {
loge("Unexpected status subevent 0x%04X in discovery event handler\n", utilGetLE16(&staEvt->opcode));
return false;
}
free(cbkData);
hciInquiryEvtEdrNameHandle(handle, status ? HCI_NAME_REQ_STATUS_ERROR : HCI_NAME_REQ_STATUS_IN_PROGRESS, !!status, NULL, 0);
} else if (evtType == HCI_EVT_Remote_Name_Request_Complete) {
struct hciEvtRemoteNameReqComplete *cmpl = (struct hciEvtRemoteNameReqComplete*)(evt + 1);
uint8_t status = utilGetLE8(&cmpl->status);
hciInquiryEvtEdrNameMac(cmpl->mac, status ? HCI_NAME_REQ_STATUS_ERROR : HCI_NAME_REQ_STATUS_COMPLETE, true, cmpl->name, sizeof(cmpl->name));
} else if (evtType == HCI_EVT_Inquiry_Complete) {
logd("inquiry ended uncancelled\n");
} else {
loge("Unexopected event 0x%02X in discovery event handler\n", evtType);
return false;
}
return true;
}
/*
* FUNCTION: hciSetManyEventPersistentHandlers
* USE: Set a few identical event handlers for variosu events
* PARAMS: evtTypes - event types list (ended with a 0)
* evtExtras - event extras array (or NULL if all zeros are to be assumed)
* cbk - the handler
* cbkData - data for the handler
* RETURN: false on error
* NOTES:
*/
static bool hciSetManyEventPersistentHandlers(const uint8_t *evtTypes, const uint16_t *evtExtras, hciEvtCbk cbk, void *cbkData)
{
struct hciEvtWaitDescr ewd = {0, };
uint8_t i;
ewd.cbk = cbk;
ewd.cbkData = cbkData;
ewd.persistent = true;
for (i = 0; evtTypes[i]; i++) {
ewd.evtType = evtTypes[i];
ewd.extra = evtExtras ? evtExtras[i] : 0;
if (!hciEvtWaitEnqueue(&ewd, 0)) {
loge("Failed to enqueue handler %d: 0x%02X.%04X\n", i, ewd.evtType, ewd.extra);
return false;
}
}
return true;
}
/*
* FUNCTION: hciGetLocalAddress
* USE: Get the local static BT addr
* PARAMS: addr - the buffer to stash address in. not a bt_addr struct since type is not determinable
* RETURN: NONE
* NOTES:
*/
void hciGetLocalAddress(uint8_t *addr)
{
memcpy(addr, mLocalAddr, sizeof(mLocalAddr));
}
/*
* FUNCTION: hciBtStackUp
* USE: Brings up the chip in a sane configuration, reags versions, etc
* PARAMS: NONE
* RETURN: false on error
* NOTES:
*/
static bool hciBtStackUp(void)
{
int ret = 0;
uint8_t inqMode = HCI_INQ_MODE_STD;
/* read local BT version */
{
struct hciCmplReadLocalVersion evt;
uint8_t hciVersion, lmpVersion;
logi("BT UP: reading version\n");
ret = hciReadLocalVersionSync(&evt);
if (ret)
goto out;
hciVersion = utilGetLE8(&evt.hciVersion);
lmpVersion = utilGetLE8(&evt.lmpVersion);
logi("BT UP: bt versions reported: %u, %d\n", hciVersion, lmpVersion);
if (hciVersion > HCI_VERSION_4_1)
hciVersion = HCI_VERSION_4_1;
if (lmpVersion > HCI_VERSION_4_1)
lmpVersion = HCI_VERSION_4_1;
mBtVer = hciVersion < lmpVersion ? hciVersion : lmpVersion;
if (mBtVer == HCI_VERSION_2_0) { /* do 2.0 chips really exist? */
logi("BT 2.0 chip???\n");
mBtVer = HCI_VERSION_1_2;
}
logi("BT UP: effective BT version: %u (BT spec %s)\n", mBtVer, mBtVers[mBtVer]);
if (mBtVer < HCI_VERSION_1_1) {
loge("BT 1.1 minimum!\n");
ret = -3;
goto out;
}
}
/* read supported features */
{
struct hciCmplReadLocalSupportedFeatures evt;
logi("BT UP: reading features\n");
ret = hciReadLocalSupportedFeaturesSync(&evt);
if (ret)
goto out;
mLocalFtrs[0] = utilGetLE64(&evt.features);
logi("BT UP: bt features reported: "FMT64"X\n", CNV64(mLocalFtrs[0]));
}
/* read extended features if possible */
if (mBtVer >= HCI_VERSION_2_1 && (mLocalFtrs[0] & HCI_LMP_FTR_EXTENDED_FEATURES)) {
uint8_t page, numPages = 1;
for (page = 0; page < numPages && page < MAX_FTR_PAGES; page++) {
struct hciCmplReadLocalExtendedFeatures evt;
uint64_t ftrs;
uint8_t claimedPage;
ret = hciReadLocalExtendedFeaturesSync(page, &evt);
if (ret)
goto out;
ftrs = utilGetLE64(&evt.features);
claimedPage = utilGetLE8(&evt.page);
numPages = utilGetLE8(&evt.maxPage) + 1;
if (claimedPage < MAX_FTR_PAGES)
mLocalFtrs[claimedPage] = ftrs;
else
logw("Ignoring report for ftr page %u\n", claimedPage);
logi("BT UP: bt extended features[%d/%d, requested %u] reported: "FMT64"X\n", claimedPage, numPages, page, CNV64(ftrs));
}
}
/* read MAC address and show it */
{
struct hciCmplReadBdAddr evt;
logi("Reading local MAC\n");
ret = hciReadBdAddrSync(&evt);
if (ret)
goto out;
memcpy(mLocalAddr, evt.mac, sizeof(mLocalAddr));
logi("Local MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", evt.mac[5], evt.mac[4], evt.mac[3], evt.mac[2], evt.mac[1], evt.mac[0]);
}
/* read EDR buffer sizes */
{
struct hciCmplReadBufferSize evt;
uint16_t i;
logi("BT UP: reading EDR buffer size\n");
ret = hciReadBufferSizeSync(&evt);
if (ret)
goto out;
mScoBufSz = utilGetLE8(&evt.scoBufferLen);
mAclBufSzEdr = utilGetLE16(&evt.aclBufferLen);
mAclBufNumEdr = utilGetLE16(&evt.numAclBuffers);
mScoBufNum = utilGetLE16(&evt.numScoBuffers);
logi("BT UP: EDR buffers: %ux%ub ACL + %ux%ub SCO\n", mAclBufNumEdr, mAclBufSzEdr, mScoBufNum, mScoBufSz);
for (i = 0; i < mAclBufNumEdr; i++)
sem_post(&mAclPacketsEdr);
for (i = 0; i < mScoBufNum; i++)
sem_post(&mScoPackets);
}
/* read LE buffer sizes & features, if LE exists */
if (mBtVer >= HCI_VERSION_4_0 && (mLocalFtrs[0] & HCI_LMP_FTR_LE_SUPPORTED_CONTROLLER)) {
struct hciCmplLeReadLocalSupportedFeatures evtFtrs;
struct hciCmplLeReadBufferSize evt;
uint16_t i;
logi("BT UP: reading LE buffer size\n");
ret = hciLeReadBufferSizeSync(&evt);
if (ret)
goto out;
mAclBufSzLe = utilGetLE16(&evt.leBufferSize);
mAclBufNumLe = utilGetLE8(&evt.leNumBuffers);
mHaveLe = true;
if (mLocalFtrs[0] & HCI_LMP_FTR_BR_EDR_NOT_SUPPORTED)
mHaveEdr = false;
logi("BT UP: LE buffers: %ux%ub\n", mAclBufNumLe, mAclBufSzLe);
if (!mAclBufNumLe)
logi("BT UP: joint EDR/LE ACL buffers detected\n");
else
mBtJointBuffers = false;
for (i = 0; i < mAclBufNumLe; i++)
sem_post(&mAclPacketsLe);
logi("Reading local LE features\n");
ret = hciLeReadLocalSupportedFeaturesSync(&evtFtrs);
if (ret)
goto out;
mLocalLeFtrs = utilGetLE64(&evtFtrs.leFeatures);
logi("BT UP: le features reported: "FMT64"X\n", CNV64(mLocalLeFtrs));
}
/* setup a few simple settings */
{
logi("Setting PIN type to variable\n");
ret = hciWritePinTypeSync(false);
if (ret)
goto out;
logi("Setting auto-accept timeout\n");
ret = hciWriteConnAcceptTimeoutSync(HCI_CONN_RX_TIMEOUT);
if (ret)
goto out;
logi("Setting connection timeout\n");
ret = hciWritePageTimeoutSync(HCI_CONN_TX_TIMEOUT);
if (ret)
goto out;
}
/* set event mask */
{
uint64_t evts = 0;
switch (mBtVer) {
case HCI_VERSION_1_1:
evts = HCI_EVENT_ALL_BT_1_1;
break;
case HCI_VERSION_1_2:
evts = HCI_EVENT_ALL_BT_1_2;
break;
case HCI_VERSION_2_1:
evts = HCI_EVENT_ALL_BT_2_1;
break;
case HCI_VERSION_3_0:
evts = HCI_EVENT_ALL_BT_3_0;
break;
case HCI_VERSION_4_0:
evts = HCI_EVENT_ALL_BT_4_0;
break;
case HCI_VERSION_4_1:
evts = HCI_EVENT_ALL_BT_4_1;
break;
}
evts &=~ HCI_EVENT_REMOTE_HOST_SUPPORTED_FEATURES; /* we do not want this one - it is spammy and useless for now */
evts &=~ HCI_EVENT_MAX_SLOTS_CHANGE; /* same here really */
logi("Setting event mask to 0x"FMT64"X\n", CNV64(evts));
ret = hciSetEventMaskSync(evts);
if (ret)
goto out;
}
/* set discoverability and connectivity scan settings */
{
logi("Setting page scan timings\n");
ret = hciWritePageScanActivitySync(0x100, 0x12);
if (ret) {
logw("Failed to set page scan settings\n");
/* if we actually goto out here, QC chips fail */
}
logi("Setting inquiry scan timings\n");
ret = hciWriteInquiryScanActivitySync(0x80, 0x12);
if (ret) {
logw("Failed to set inquiry scan settings\n");
/* if we actually goto out here, QC chips fail */
}
}
/* enable SSP, if possible */
if (mBtVer >= HCI_VERSION_2_1 && (mLocalFtrs[0] & HCI_LMP_FTR_SSP)) {
logi("Enabling SSP\n");
ret = hciWriteSimplePairingModeSync(true);
if (ret)
goto out;
logi("SSP should be on\n");
}
/* enable LE, if possible */
if (mHaveLe) {
mSimulLe = (mLocalFtrs[0] & HCI_LMP_FTR_SIMUL_LE_EDR_CAPABLE_CONTROLLER) != 0;
logi("Enabling LE. Simul=%s\n", mSimulLe ? "YES" : "NO");
ret = hciWriteLeHostSupportedSync(true, mSimulLe);
if (ret)
goto out;
logi("LE should be on\n");
}
/* set scan results to most informative format */
if (mBtVer >= HCI_VERSION_1_2) {
if (mLocalFtrs[0] & HCI_LMP_FTR_RSSI_WITH_INQUIRY_RESULTS)
inqMode = HCI_INQ_MODE_RSSI;
if (mBtVer >= HCI_VERSION_2_1 && (mLocalFtrs[0] & HCI_LMP_FTR_EXTENDED_INQUIRY_RESPONSE))
inqMode = HCI_INQ_MODE_EIR;
logi("Setting inquiry mode to %d\n", inqMode);
ret = hciWriteInquiryModeSync(inqMode);
if (ret)
goto out;
}
/* set up event handler(s) for discovery results */
{
static const uint8_t discEvtTypes[] = {HCI_EVT_Inquiry_Complete, HCI_EVT_Inquiry_Result, HCI_EVT_Inquiry_Result_With_RSSI,
HCI_EVT_Extended_Inquiry_Result, HCI_EVT_Remote_Name_Request_Complete, HCI_EVT_LE_Meta, 0};
static const uint16_t discEvtExtras[] = {0, 0, 0, 0, 0, HCI_EVTLE_Advertising_Report};
if (!hciSetManyEventPersistentHandlers(discEvtTypes, discEvtExtras, hciInquiryEvtCbk, NULL)) {
ret = -2;
loge("Failed to enqueue inquiry handlers\n");
goto out;
}
}
/* set event handlers for connection states */
{
static const uint8_t connEvtTypes[] = {HCI_EVT_Connection_Request, HCI_EVT_Connection_Complete, HCI_EVT_Disconnection_Complete, HCI_EVT_Encryption_Change,
HCI_EVT_Authentication_Complete, HCI_EVT_Role_Change, HCI_EVT_LE_Meta, HCI_EVT_LE_Meta, 0};
static const uint16_t connEvtExtras[] = {0, 0, 0, 0, 0, 0, HCI_EVTLE_Connection_Complete, HCI_EVTLE_Connection_Update_Complete};
if (!hciSetManyEventPersistentHandlers(connEvtTypes, connEvtExtras, hciConnEvtCbk, NULL)) {
ret = -2;
loge("Failed to enqueue connection state handlers\n");
goto out;
}
}
/* set event handlers for security events with user interaction */
{
static const uint8_t connEvtTypes[] = {HCI_EVT_PIN_Code_Request, HCI_EVT_Link_Key_Request, HCI_EVT_Link_Key_Notification, HCI_EVT_IO_Capability_Request,
HCI_EVT_IO_Capability_Response, HCI_EVT_User_Confirmation_Request, HCI_EVT_User_Passkey_Request,
HCI_EVT_Remote_OOB_Data_Request, HCI_EVT_Simple_Pairing_Complete, HCI_EVT_User_Passkey_Notification,
HCI_EVT_Keypress_Notification, 0};
if (!hciSetManyEventPersistentHandlers(connEvtTypes, NULL, hciSecUserEvtCbk, NULL)) {
ret = -2;
loge("Failed to enqueue user-interacting-security-related event handlers\n");
goto out;
}
}
out:
if (ret) {
loge("Status was 0x%02X\n", ret);
return false;
}
return true;
}
/*
* FUNCTION: hciDiscoverStopAndFreeNameReq
* USE: Stops EDR name request if possible and frees the struct
* PARAMS: toFree - the name discovery struct
* RETURN: NONE
* NOTES:
*/
static void hciDiscoverStopAndFreeNameReq(struct hciDiscoverNameState *toFree)
{
if (mBtVer >= HCI_VERSION_1_2)
hciRemoteNameRequestCancelSync(toFree->addr.addr); /* error ignored on purpose */
free(toFree);
}
/*
* FUNCTION: hciDiscoverEdrStart
* USE: Starts EDR discovery
* PARAMS: cbk - device discovered callback
* cbkData - data for said callback
* msec - how long to run for
* RETURN: discovery handle or 0 for error
* NOTES: Callbacks might be called even if func returns false, but
* will stop once said return value is actually returned.
* 61440 msec maximum.
*/
uniq_t hciDiscoverEdrStart(hciDeviceDiscoveredEdrCbk cbk, void *cbkData, uint32_t msec)
{
uniq_t ret = 0;
uint32_t len;
int sta;
if (!mHaveEdr) {
loge("Refusing EDR discovery with no EDR support\n");
return 0;
}
if (!cbk) {
loge("Refusing discovery with no callback\n");
return 0;
}
pthread_mutex_lock(&mDiscoveryStateLock);
if (mDiscoverEdrHandle)
logw("Refusing to do EDR discovery while another in progress\n");
else {
ret = mDiscoverEdrHandle = uniqGetNext();
mDiscoverEdrCbk = cbk;
mDiscoverEdrData = cbkData;
}
pthread_mutex_unlock(&mDiscoveryStateLock);
if (!ret)
return 0;
/* round length to units - err on doing it longer */
len = (msec + HCI_INQUIRY_LENGTH_UNIT - 1) / HCI_INQUIRY_LENGTH_UNIT;
if (len > HCI_INQUIRY_LENGTH_MAX)
len = HCI_INQUIRY_LENGTH_MAX;
logd("Discovery request for %u msec will run for %u units\n", msec, len);
sta = hciInquirySync(HCI_LAP_Unlimited_Inquiry, len, 0x00);
if (!sta)
return ret;
pthread_mutex_lock(&mDiscoveryStateLock);
mDiscoverEdrHandle = 0;
pthread_mutex_unlock(&mDiscoveryStateLock);
loge("Failed to start inquiry: %d\n", sta);
return 0;
}
/*
* FUNCTION: hciDiscoverEdrStop
* USE: Stops EDR discovery and any extant name-getting callbacks for it
* PARAMS: handle - the handle from hciDiscoverEdrStart()
* RETURN: false on error
* NOTES: Callbacks will stop once this returns
*/
bool hciDiscoverEdrStop(uniq_t handle)
{
if (!handle) {
loge("Cannot pass NULL handle here\n");
return false;
}
pthread_mutex_lock(&mDiscoveryStateLock);
if (mDiscoverEdrHandle != handle)
handle = 0;
else
mDiscoverEdrHandle = uniqGetNext(); /* not us so nobdy can re-cancel. not zero so nobody can start another scan */
pthread_mutex_unlock(&mDiscoveryStateLock);
hciInquiryCancelSync();
hciWorkFlush();
return !!handle;
}
/*
* FUNCTION: hciDiscoverEdrGetName
* USE: Get a device name during EDR discovery
* PARAMS: cbk - name callback
* cbkData - data for said callback
* forNameGetting - data passed to edr discovery callback
* RETURN: name-getting operation handle or 0 on error
* NOTES:
*/
uniq_t hciDiscoverEdrGetName(hciDeviceDiscoveredEdrNameCbk cbk, void *cbkData, const struct hciNameGetInfo *forNameGetting)
{
struct hciDiscoverNameState *dns = (struct hciDiscoverNameState*)malloc(sizeof(struct hciDiscoverNameState));
struct hciRemoteNameRequest cmd;
struct hciEvtWaitDescr ewd;
uniq_t *handleCopy, ret;
if (!mHaveEdr) {
loge("Refusing EDR name discovery with no EDR support\n");
return 0;
}
if (!dns)
return 0;
handleCopy = malloc(sizeof(uniq_t));
if (!handleCopy) {
free(dns);
return 0;
}
dns->cbk = cbk;
dns->cbkData = cbkData;
dns->handle = *handleCopy = ret = uniqGetNext();
memcpy(dns->addr.addr, forNameGetting->mac, sizeof(dns->addr.addr));
dns->addr.type = BT_ADDR_TYPE_EDR;
ewd.extra = CMD_MAKE_OPCODE(HCI_OGF_Link_Control, HCI_CMD_Remote_Name_Request);
ewd.evtType = HCI_EVT_Command_Status;
ewd.cbk = hciInquiryEvtCbk;
ewd.cbkData = handleCopy;
ewd.persistent = false;
memcpy(cmd.mac, forNameGetting->mac, sizeof(cmd.mac));
utilSetLE8(&cmd.PSRM, forNameGetting->PSRM);
utilSetLE8(&cmd.PSM, forNameGetting->PSM);
utilSetLE16(&cmd.clockOffset, forNameGetting->clockOffset);
pthread_mutex_lock(&mDiscoveryStateLock);
if (hciCmdSubmit(HCI_OGF_Link_Control, HCI_CMD_Remote_Name_Request, &cmd, sizeof(cmd), &ewd, NULL)) {
dns->next = mDiscoverNames;
mDiscoverNames = dns;
} else {
free(handleCopy);
free(dns);
ret = 0;
}
pthread_mutex_unlock(&mDiscoveryStateLock);
return ret;
}
/*
* FUNCTION: hciDiscoverEdrGetNameCancel
* USE: Cancel a device name discovery during EDR discovery
* PARAMS: handle - the handle from hciDiscoverEdrGetName()
* RETURN: false on error
* NOTES: cCallback will certainly not fire once this returns
*/
bool hciDiscoverEdrGetNameCancel(uniq_t handle)
{
struct hciDiscoverNameState *c = NULL, *p;
pthread_mutex_lock(&mDiscoveryStateLock);
c = mDiscoverNames;
p = NULL;
while (c && c->handle != handle) {
p = c;
c = c->next;
}
if (c) {
if (p)
p->next = c->next;
else
mDiscoverNames = c->next;
}
pthread_mutex_unlock(&mDiscoveryStateLock);
if (c)
hciDiscoverStopAndFreeNameReq(c);
return !!c;
}
/*
* FUNCTION: hciDiscoverLeStart
* USE: Starts LE discovery
* PARAMS: cbk - device discovered callback
* cbkData - data for said callback
* active - do active scan?
* RETURN: discovery handle or 0 for error
* NOTES: Callbacks might be called even if func returns false, but
* will stop once said return value is actually returned.
* Discovery goes on until stopped.
*/
uniq_t hciDiscoverLeStart(hciDeviceDiscoveredLeCbk cbk, void *cbkData, bool active, bool useRandomAddr)
{
uniq_t ret = 0;
int sta;
if (!cbk) {
loge("Refusing discovery with no callback\n");
return 0;
}
if (!mHaveLe) {
loge("Failing request to LE discovery due to lack of LE\n");
return 0;
}
pthread_mutex_lock(&mDiscoveryStateLock);
if (mDiscoverLeHandle)
logw("Refusing to do LE discovery while another in progress\n");
else {
ret = mDiscoverLeHandle = uniqGetNext();
mDiscoverLeCbk = cbk;
mDiscoverLeData = cbkData;
}
pthread_mutex_unlock(&mDiscoveryStateLock);
if (!ret)
return 0;
sta = hciLeSetScanParamsSync(active, HCI_LE_SCAN_INTERVAL, HCI_LE_SCAN_WINDOW, useRandomAddr, false);
if (sta) {
loge("Failed to set LE scan params: %d\n", sta);
goto err;
}
sta = hciLeSetScanEnableSync(true, false);
if (sta) {
loge("Failed to set LE scan on: %d\n", sta);
goto err;
}
return ret;
err:
pthread_mutex_lock(&mDiscoveryStateLock);
mDiscoverLeHandle = 0;
pthread_mutex_unlock(&mDiscoveryStateLock);
return 0;
}
/*
* FUNCTION: hciDiscoverLeStop
* USE: Stops LE discovery
* PARAMS: handle - the handle from hciDiscoverLeStart()
* RETURN: false on error
* NOTES: Callbacks will stop once this returns
*/
bool hciDiscoverLeStop(uniq_t handle)
{
int sta;
if (!handle) {
loge("Cannot pass NULL handle here\n");
return false;
}
pthread_mutex_lock(&mDiscoveryStateLock);
if (mDiscoverLeHandle != handle)
handle = 0;
else
mDiscoverLeHandle = 0;
pthread_mutex_unlock(&mDiscoveryStateLock);
if (handle){
sta = hciLeSetScanEnableSync(false, false);
if (sta)
loge("Failed to set LE scan off: %d\n", sta);
}
hciWorkFlush();
return !!handle;
}
/*
* FUNCTION: hciInfoSharedBuffers
* USE: Provide info on whether EDR & LE ACL buffers are shared in our controller
* PARAMS: NONE
* RETURN: the answer
* NOTES:
*/
bool hciInfoSharedBuffers(void)
{
return mBtJointBuffers;
}
/*
* FUNCTION: hciInfoSharedBuffers
* USE: Provide info on SCO buffer size in our controller and SCO support
* PARAMS: bufSz - where to store buffer sizes (or NULL if uninterested)
* bufNum - where to store number of buffers (or NULL if uninterested)
* RETURN: true if SCO supported
* NOTES:
*/
bool hciInfoScoBufSize(uint16_t *bufSz, uint16_t *bufNum) //false if no EDR support
{
if (!(mLocalFtrs[0] & HCI_LMP_FTR_SCO_LINKS))
return false;
if (bufSz)
*bufSz = mScoBufSz;
if (bufNum)
*bufNum = mScoBufNum;
return true;
}
/*
* FUNCTION: hciInfoSharedBuffers
* USE: Provide info on EDR ACL buffer size in our controller and EDR support
* PARAMS: bufSz - where to store buffer sizes (or NULL if uninterested)
* bufNum - where to store number of buffers (or NULL if uninterested)
* RETURN: true if EDR supported
* NOTES:
*/
bool hciInfoAclBufSizeEdr(uint16_t *bufSz, uint16_t *bufNum)
{
if (!mHaveEdr)
return false;
if (bufSz)
*bufSz = mAclBufSzEdr;
if (bufNum)
*bufNum = mAclBufNumEdr;
return true;
}
/*
* FUNCTION: hciInfoSharedBuffers
* USE: Provide info on LE ACL buffer size in our controller and LE support
* PARAMS: bufSz - where to store buffer sizes (or NULL if uninterested)
* bufNum - where to store number of buffers (or NULL if uninterested)
* RETURN: true if LE supported
* NOTES:
*/
bool hciInfoAclBufSizeLe(uint16_t *bufSz, uint16_t *bufNum)
{
if (!mHaveLe)
return false;
if (bufSz)
*bufSz = mAclBufSzLe;
if (bufNum)
*bufNum = mAclBufNumLe;
return true;
}
/*
* FUNCTION: hciCmdStatusCbk
* USE: Gives us the status of a command we started
* PARAMS: cbkData - non-null for LE
* status - status from the chip
* RETURN: NONE
* NOTES: This is the universal sink for status events we do not care much about
*/
static void hciCmdStatusCbk(void *cbkData, uint8_t status)
{
logd("status: %d\n", status);
}
/*
* FUNCTION: hciConnectEdr
* USE: Start an EDR connection to a given address
* PARAMS: mac - the mac
* RETURN: true if attempt will be made
* NOTES: call with mConnsLock held
*/
static bool hciConnectEdr(const uint8_t *mac)
{
struct hciCreateConnection cmd;
if (!mHaveEdr) {
logw("Refusing attempt to create EDR connection with no EDR support\n");
return false;
}
memcpy(cmd.mac, mac, sizeof(cmd.mac));
utilSetLE16(&cmd.allowedPackets, HCI_PKT_TYP_DEFAULT);
utilSetLE8(&cmd.PSRM, 0);
utilSetLE16(&cmd.clockOffset, 0);
utilSetLE8(&cmd.allowRoleSwitch, 0);
if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Create_Connection, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL)) {
logi("Failed to request connect to "MACFMT"\n", MACCONV(mac));
return false;
}
return true;
}
/*
* FUNCTION: hciConnEncryptLe
* USE: Start an encryption attempt for an LE connection
* PARAMS: conn - the connection
* demandMitmSafe - do we want mitm-safety?
* RETURN: true if attempt will be made
* NOTES: call with mConnsLock held
*/
static bool hciConnEncryptLe(struct hciAclConn *conn, bool demandMitmSafe)
{
if (!(mLocalLeFtrs & HCI_LE_FTR_ENCRYPTION)) {
loge("Encryption not supported\n");
return false;
}
if (!(conn->le.ftrs & HCI_LE_FTR_ENCRYPTION)) {
loge("Encryption not supported remotely\n");
return false;
}
//TODO: LE Security Manager integration
return false;
}
/*
* FUNCTION: hciConnEncryptEdr
* USE: Start an encryption attempt for an EDR connection
* PARAMS: conn - the connection
* demandMitmSafe - do we want mitm-safety?
* RETURN: true if attempt will be made
* NOTES: call with mConnsLock held
*/
static bool hciConnEncryptEdr(struct hciAclConn *conn, bool demandMitmSafe)
{
struct hciSetConnectionEncryption cmdEncr;
struct hciAuthRequested cmdAuth;
if (!(mLocalFtrs[0] & HCI_LMP_FTR_ENCRYPTION)) {
loge("Encryption not supported locally\n");
return false;
}
if (!(conn->edr.ftrs[0] & HCI_LMP_FTR_ENCRYPTION)) {
loge("Encryption not supported remotely\n");
return false;
}
utilSetLE16(&cmdEncr.conn, conn->id);
utilSetLE8(&cmdEncr.encrOn, 1);
utilSetLE16(&cmdAuth.conn, conn->id);
if (demandMitmSafe) {
if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Authentication_Requested, &cmdAuth, sizeof(cmdAuth), hciCmdStatusCbk, NULL)) {
logi("Failed to request encr to cid %d\n", conn->id);
return false;
}
} else {
if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Set_Connection_Encryption, &cmdEncr, sizeof(cmdEncr), hciCmdStatusCbk, NULL)) {
logi("Failed to request encr to cid %d\n", conn->id);
return false;
}
}
return true;
}
/*
* FUNCTION: hciConnectLe
* USE: Start an LE connection to a given address
* PARAMS: mac - the mac
* randomAddr - peer address is random?
* scanInt - connect scan interval
* scanWindow - connect scan window
* intMin - minimum wanted interval
* intMax - maximum wanted interval
* latency - wanted latency
* timeout - wanted timeout
* useOwnRandomAddr - send peer our random addr?
* RETURN: true if attempt will be made
* NOTES: call with mConnsLock held
*/
static bool hciConnectLe(const uint8_t *mac, bool randomAddr, uint16_t scanInt, uint16_t scanWindow, uint16_t intMin, uint16_t intMax, uint16_t latency, uint16_t timeout, bool useOwnRandomAddr)
{
struct hciLeCreateConnection cmd;
if (!mHaveLe) {
logw("Refusing attempt to create LE connection with no LE support\n");
return false;
}
utilSetLE16(&cmd.scanInterval, scanInt);
utilSetLE16(&cmd.scanWindow, scanWindow);
utilSetLE8(&cmd.connectToAnyWhitelistedDevice, 0);
utilSetLE8(&cmd.peerRandomAddr, randomAddr ? 1 : 0);
memcpy(cmd.peerMac, mac, sizeof(cmd.peerMac));
utilSetLE8(&cmd.useOwnRandomAddr, useOwnRandomAddr ? 1 : 0);
utilSetLE16(&cmd.connIntervalMin, intMin);
utilSetLE16(&cmd.connIntervalMax, intMax);
utilSetLE16(&cmd.connLatency, latency);
utilSetLE16(&cmd.supervisionTimeout, timeout);
utilSetLE16(&cmd.minConnLen, 0); /* not a useful hint, i know */
utilSetLE16(&cmd.maxConnLen, intMin * 2);
if (!hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_LE_Create_Connection, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL)) {
logi("Failed to request connect to "MACFMT"\n", MACCONV(mac));
return false;
}
return true;
}
/*
* FUNCTION: hciConnAclStructDel
* USE: Delete a connection structure
* PARAMS: conn - the structure
* RETURN: NONE
* NOTES: call with mConnsLock held
*/
static void hciConnAclStructDel(struct hciAclConn* conn)
{
if (conn->outstandingPackets) {
if (BT_ADDR_IS_EDR(conn->peerAddr)) {
while (conn->outstandingPackets--)
sem_post(&mAclPacketsEdr);
l2cAclCreditAvail(false);
} else {
while (conn->outstandingPackets--)
sem_post(&mAclPacketsLe);
l2cAclCreditAvail(true);
}
}
if (conn->next)
conn->next->prev = conn->prev;
if (conn->prev)
conn->prev->next = conn->next;
else
mConns = conn->next;
if (conn->rxBacklog)
sgFree(conn->rxBacklog);
logd("deleting conn struct for "ADDRFMT" h="HANDLEFMT"\n", ADDRCONV(conn->peerAddr), HANDLECNV(conn->handle));
free(conn);
}
/*
* FUNCTION: hciConnAclStructNew
* USE: Create a connection structure
* PARAMS: id - the ACL connection ID
* addr - the peer address
* state - the state to create the connection in
* isMaster - set us as master?
* RETURN: the new conn struct or NULL
* NOTES: call with mConnsLock held
*/
static struct hciAclConn* hciConnAclStructNew(uint16_t id, const struct bt_addr *addr, uint8_t state, bool isMaster)
{
struct hciAclConn *conn = (struct hciAclConn*)calloc(1, sizeof(struct hciAclConn));
if (!conn)
return NULL;
memcpy(&conn->peerAddr, addr, sizeof(conn->peerAddr));
conn->state = state;
conn->isMaster = isMaster;
conn->id = id;
conn->handle = uniqGetNext();
conn->next = mConns;
mConns = conn;
return conn;
}
/*
* FUNCTION: hciConnect
* USE: Start a connection to a given address
* PARAMS: addr - the address
* RETURN: true if attempt will be made
* NOTES: For LE default params will be used
*/
bool hciConnect(const struct bt_addr* addr)
{
bool ret = false;
logd("Requested a conect to "ADDRFMT"\n", ADDRCONV(*addr));
pthread_mutex_lock(&mConnsLock);
if (hciConnFindByAddr(addr))
logw("refusing to connect to address i already have a connection to\n");
else {
struct hciAclConn* conn = hciConnAclStructNew(ACL_CONN_ID_INVALID, addr, CONN_STATE_WAIT, true);
if (!conn)
loge("Failed to allocate conn struct\n");
else {
if (BT_ADDR_IS_EDR(*addr))
ret = hciConnectEdr(addr->addr);
else
ret = hciConnectLe(addr->addr, addr->type == BT_ADDR_TYPE_LE_RANDOM, HCI_LE_CONN_SCAN_INTERVAL,
HCI_LE_CONN_SCAN_WINDOW, HCI_LE_CONN_INT_MIN, HCI_LE_CONN_INT_MAX,
HCI_LE_CONN_LATENCY, HCI_LE_CONN_TIMEOUT, HCI_LE_CONN_USE_RAND_ADDR);
if (!ret)
hciConnAclStructDel(conn);
}
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciDisconnect
* USE: Kill a connection [attempt] to a given address
* PARAMS: addr - the address
* RETURN: true if attempt will be made
* NOTES:
*/
bool hciDisconnect(const struct bt_addr* addr)
{
struct hciAclConn* conn;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByAddr(addr);
if (!conn)
logw("Failed to find connection to disconnect "ADDRFMT"\n", ADDRCONV(*addr));
else {
bool isLE = BT_ADDR_IS_LE(conn->peerAddr);
switch (conn->state) {
case CONN_STATE_WAIT:
if (isLE) {
if (!hciConnLeCancel())
loge("Failed to request LE conection cancellation for "ADDRFMT"\n", ADDRCONV(conn->peerAddr));
} else
logd("cannot cancel in-progress EDR connection\n");
conn->state = CONN_STATE_DELETING;
break;
case CONN_STATE_CFG:
case CONN_STATE_RUNNING:
hciConnDisconnect(conn->id);
hciConnAclStructDel(conn);
break;
case CONN_STATE_DELETING:
logd("cannot cancel already-in-cancellation connection\n");
break;
default:
loge("Invalid connection state %d\n", conn->state);
break;
}
}
pthread_mutex_unlock(&mConnsLock);
return !!conn;
}
/*
* FUNCTION: hciUpdateLeParams
* USE: Try to update LE params
* PARAMS: aclConn - the connection
* minInt - the minimum wanted interval
* maxInt - the maximum wanted interval
* lat - wanted latency
* to - wanted timeout
* RETURN: true if attempt will be made
* NOTES:
*/
bool hciUpdateLeParams(hci_conn_t aclConn, uint16_t minInt, uint16_t maxInt, uint16_t lat, uint16_t to)
{
struct hciAclConn* conn;
bool ret = false;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByHandle(aclConn);
if (!conn)
logw("Conection for update not found\n");
else if (conn->state != CONN_STATE_RUNNING)
logw("Cannot update params for not-yet-established connection\n");
else
ret = hciConnUpdate(conn->id, minInt, maxInt, lat, to);
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciDemandEncr
* USE: Try to encrypt a connection
* PARAMS: aclConn - the connection
* RETURN: true if attempt will be made
* NOTES:
*/
bool hciDemandEncr(hci_conn_t aclConn, bool demandMitmSafe)
{
struct hciAclConn* conn;
bool ret = false;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByHandle(aclConn);
if (!conn)
logw("Conection for encrypt not found\n");
else if (conn->state != CONN_STATE_RUNNING)
logw("Cannot update encryption for not-yet-established connection\n");
else if (conn->encrypted && (conn->mitmSafe || !demandMitmSafe))
logw("Connection already encrypted up to requested level\n");
else if (BT_ADDR_IS_LE(conn->peerAddr))
ret = hciConnEncryptLe(conn, demandMitmSafe);
else
ret = hciConnEncryptEdr(conn, demandMitmSafe);
pthread_mutex_unlock(&mConnsLock);
return !!conn;
}
/*
* FUNCTION: hciLeConnGetInfo
* USE: Query info for an LE connection
* PARAMS: aclConn - the connection
* masterP - where to stor if we're master (or NULL)
* peerFtrsP - where to store peer features (or NULL)
* intP - where to store connection interval (or NULL)
* latP - where to sotre connectio nlatency (or NULL)
* toP - where to store connection timeout (or NULL)
* mcaP - where to store MCA (meaningless if we're master) (or NULL)
* RETURN: true if connection was found and results have been returned
* NOTES:
*/
bool hciLeConnGetInfo(hci_conn_t aclConn, bool *masterP, uint64_t *peerFtrsP, uint16_t *intP, uint16_t *latP, uint16_t *toP, uint8_t *mcaP)
{
struct hciAclConn* conn;
bool ret = false;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByHandle(aclConn);
if (!conn)
logw("Conection for encrypt not found\n");
else if (!BT_ADDR_IS_LE(conn->peerAddr))
logw("Not an LE connection\n");
else {
if (masterP)
*masterP = conn->isMaster;
if (peerFtrsP)
*peerFtrsP = conn->le.ftrs;
if (intP)
*intP = conn->le.interval;
if (latP)
*latP = conn->le.latency;
if (toP)
*toP = conn->le.timeout;
if (mcaP)
*mcaP = conn->le.mca;
ret = true;
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciTryToTx
* USE: Try to send some data
* PARAMS: aclConn - the connection
* data - the data
* first - mark as first fragment?
* RETURN: true if attempt will be made
* NOTES: all data larger then controller's buffer WILL be dropped. WILL NOT BLOCK!
*/
uint8_t hciTryToTx(hci_conn_t aclConn, sg data, uint8_t boundary)
{
struct hciAclConn* conn;
uint8_t ret = HCI_TX_SEND_ERROR;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindByHandle(aclConn);
if (!conn) {
ret = HCI_TX_SEND_NO_CONN;
logw("Cannot send on a nonexistent connection\n");
} else if (conn->state != CONN_STATE_RUNNING) {
ret = HCI_TX_SEND_NO_CONN;
logw("Cannot send on a not-yet-established connection\n");
} else {
bool isLE = BT_ADDR_IS_LE(conn->peerAddr);
bool useLeBuffers = isLE && !mBtJointBuffers;
sem_t *relevantSem = useLeBuffers ? &mAclPacketsLe : &mAclPacketsEdr;
int rv;
rv = r_sem_trywait(relevantSem);
if (rv && errno == EAGAIN)
ret = HCI_TX_SEND_NO_CREDITS;
else if (!rv) {
struct hciAclHdr hdr;
uint16_t pb = 0xFFFF; /* this will catch all other cases */
uint16_t start_pb = mBtVer >= HCI_VERSION_2_1 ? ACL_HDR_PB_FIRST_NONAUTO : ACL_HDR_PB_FIRST_AUTO;
uint16_t complete_pb = /*mBtVer >= HCI_VERSION_3_0 ? ACL_HDR_PB_COMPLETE : */start_pb; /* XXX: this causes problems with TI BT chips - they send "complete" as a "continue" confusing the other side */
switch(boundary) {
case HCI_BOUNDARY_COMPLETE:
pb = isLE ? start_pb : complete_pb;
break;
case HCI_BOUNDARY_START:
pb = start_pb;
break;
case HCI_BOUNDARY_CONT:
pb = ACL_HDR_PB_CONINUED;
break;
}
utilSetLE16(&hdr.len, sgLength(data));
utilSetLE16(&hdr.hdr, conn->id | pb);
if (sgConcatFrontCopy(data, &hdr, sizeof(hdr))) {
if (vendorTx(HCI_PKT_TYP_ACL, data, false)) {
conn->outstandingPackets++;
ret = HCI_TX_SEND_OK;
} else {
/* undo our concat of header in case caller wants to use this sg again */
sgTruncFront(data, sizeof(hdr));
}
}
}
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciHandleConnAclDown
* USE: Called when a connection has gone down
* PARAMS: cid - the connection ID
* reason - the reason
* RETURN: NONE
* NOTES:
*/
static void hciHandleConnAclDown(uint16_t cid, uint8_t reason)
{
struct hciAclConn* conn;
pthread_mutex_lock(&mConnsLock);
conn = hciConnFindById(cid);
if (!conn)
logd("Conn down not found %d\n", cid);
else {
switch(conn->state) {
case CONN_STATE_WAIT:
logw("Unexpected connection down in wait state!\n");
/* fallthrough */
case CONN_STATE_CFG:
case CONN_STATE_RUNNING:
if (!hciScheduleConnDownNotif(conn->handle))
loge("Failed to schedule conn down notif\n");
break;
case CONN_STATE_DELETING:
/* nothing here really */
break;
}
hciConnAclStructDel(conn);
}
pthread_mutex_unlock(&mConnsLock);
}
/*
* FUNCTION: hciConnCfgEdrNameCbk
* USE: Name callback for an EDR connection's configuration stage
* PARAMS: cbkData - the data from hciConnCfg (step and aux encoded into a pointer)
* addr - peer's address
* nameReqStatus - status of the request
* name - the name (if status is success). null terminated
* RETURN: NONE
* NOTES:
*/
static void hciConnCfgEdrNameCbk(void *cbkData, const struct bt_addr *addr, uint8_t nameReqStatus, const char *name)
{
uint16_t step = ((uint32_t)(uintptr_t)cbkData) >> 16;
uint16_t aux = (uint16_t)(uintptr_t)cbkData;
struct hciAclConn *c = NULL;
if (nameReqStatus == HCI_NAME_REQ_STATUS_IN_PROGRESS)
return;
pthread_mutex_lock(&mConnsLock);
c = hciConnFindByAddr(addr);
if (!c)
loge("Unable to find conection for name request to "ADDRFMT"\n", ADDRCONV(*addr));
else if (nameReqStatus == HCI_NAME_REQ_STATUS_COMPLETE)
hciConnCfg(c, step + 1, aux);
else {
loge("Error getting name from "ADDRFMT"\n", ADDRCONV(*addr));
hciConnDisconnect(c->id);
}
pthread_mutex_unlock(&mConnsLock);
}
/*
* FUNCTION: hciConnCfgCbk
* USE: event handler for hciConnCfg()
* PARAMS: evt - the resuting event
* cbkData - the data from hciConnCfg (step and aux encoded into a pointer)
* evtWaitStateID - unused
* forCmdID - unused
* RETURN: true if this is our event (always yes)
* NOTES: call with mConnsLock held
*/
static bool hciConnCfgCbk(const struct hciEvtHdr *evt, void *cbkData, uniq_t evtWaitStateID, uniq_t forCmdID)
{
struct hciEvtLeMeta *leMetaEvt = (struct hciEvtLeMeta*)(evt + 1);
struct hciEvtReadRemoteSupportedFeaturesComplete *rrsf = (struct hciEvtReadRemoteSupportedFeaturesComplete*)(evt + 1);
struct hciEvtLeReadRemoteFeaturesComplete *rrf = (struct hciEvtLeReadRemoteFeaturesComplete*)(leMetaEvt + 1);
struct hciEvtReadRemoteExtFeturesComplete *rref = (struct hciEvtReadRemoteExtFeturesComplete*)(evt + 1);
struct hciEvtReadRemoteVersionComplete *rrv = (struct hciEvtReadRemoteVersionComplete*)(evt + 1);
struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1);
struct hciEvtCmdStatus *stat = (struct hciEvtCmdStatus*)(evt + 1);
struct hciCmplWriteAutoFlushTimeout *waft = (struct hciCmplWriteAutoFlushTimeout*)(cmpl + 1);
uint16_t step = ((uint32_t)(uintptr_t)cbkData) >> 16;
uint16_t aux = (uint16_t)(uintptr_t)cbkData;
uint8_t evtType = utilGetLE8(&evt->code);
struct hciAclConn *c = NULL;
uint8_t sta = 0xff;
uint16_t cid;
pthread_mutex_lock(&mConnsLock);
switch (step) {
case CONN_CFG_STEP_LE + 0: /* first LE config step = get remote features */
cid = utilGetLE16(&rrf->conn);
sta = utilGetLE8(&rrf->status);
c = hciConnFindById(cid);
if (!c)
break;
if (sta)
break;
c->le.ftrs = utilGetLE64(&rrf->leFeatures);
step++;
break;
case CONN_CFG_STEP_EDR + 1: /* second EDR step = set flush timeout */
cid = utilGetLE16(&waft->conn);
sta = utilGetLE8(&waft->status);
c = hciConnFindById(cid);
if (!c)
break;
if (sta)
break;
step++;
break;
case CONN_CFG_STEP_EDR + 2: /* third EDR step = read remote version */
if (evtType == HCI_EVT_Command_Status) { /* we just started the request */
sta = utilGetLE8(&stat->status);
if (!sta) /* if no error we do nothing - let the request go on */
goto out;
break; /* on error, report it and kill conn */
}
cid = utilGetLE16(&rrv->conn);
sta = utilGetLE8(&rrv->status);
c = hciConnFindById(cid);
if (!c)
break;
if (sta)
break;
step++;
c->edr.btVer = utilGetLE8(&rrv->lmpVersion);
logd("Remote reports BT version %u (effective: %s)\n", c->edr.btVer, mBtVers[c->edr.btVer > HCI_VERSION_4_1 ? HCI_VERSION_4_1 : c->edr.btVer]);
break;
case CONN_CFG_STEP_EDR + 3: /* fourth EDR step = read remote features/extended features */
if (evtType == HCI_EVT_Command_Status) { /* we just started the request */
sta = utilGetLE8(&stat->status);
if (!sta) /* if no error we do nothing - let the request go on */
goto out;
break; /* on error, report it and kill conn */
}
if (evtType == HCI_EVT_Read_Remote_Supported_Features_Complete) {
cid = utilGetLE16(&rrsf->conn);
sta = utilGetLE8(&rrsf->status);
c = hciConnFindById(cid);
if (!c)
break;
if (sta)
break;
step++;
c->edr.ftrs[0] = utilGetLE64(&rrsf->lmpFeatures);
} else {
uint8_t pgIdx, pgMax;
cid = utilGetLE16(&rref->conn);
sta = utilGetLE8(&rref->status);
c = hciConnFindById(cid);
if (!c)
break;
if (sta)
break;
pgIdx = utilGetLE8(&rref->pageNum);
pgMax = utilGetLE8(&rref->maxPageNum);
if (pgIdx >= MAX_FTR_PAGES) {
logd("Got remote features for page %u, which we do not support. Moving on.\n", pgIdx);
step++;
aux = 0;
} else {
c->edr.ftrs[pgIdx] = utilGetLE64(&rref->extLmpFeatures);
if (pgIdx == pgMax) {
aux = 0;
step++;
} else
aux++;
}
}
break;
default:
loge("unknown connection config step step 0x%04X.05%04X\n", step, aux);
break;
}
if (!c)
loge("connection config failed: unable to find connection\n");
else if (!sta)
hciConnCfg(c, step, aux);
else if (sta) {
loge("connection config failed: sta 0x%02X. Closing.\n", sta);
hciConnDisconnect(c->id);
}
out:
pthread_mutex_unlock(&mConnsLock);
return true;
}
/*
* FUNCTION: hciConnCfg
* USE: Configure a new connection to our liking and tell upper layer when done
* PARAMS: c - the connection struct
* step - the step to perform
* aux - substep
* RETURN: NONE
* NOTES: call with mConnsLock held
*/
static void hciConnCfg(struct hciAclConn *c, uint32_t step, uint32_t aux)
{
struct hciEvtWaitDescr ewd[2] = {{.cbk = hciConnCfgCbk, .persistent = false}, {.cbk = hciConnCfgCbk, .persistent = false}};
struct hciNameGetInfo fng = {.clockOffset = 0, .PSRM = 0, .PSM = 0};
void *userData = (void*)(uintptr_t)((step << 16) | aux);
struct hciReadRemoteSupportedFeatures rrsf;
struct hciReadRemoteExtendedFeatures rref;
struct hciLeReadRemoteUsedFeatures rrf;
struct hciWriteAutoFlushTimeout waft;
struct hciReadRemoteVersionInfo rrv;
bool success = true;
if (c->state != CONN_STATE_CFG) {
logw("Tried to config a connection not in config state\n");
return;
}
switch (step) {
case CONN_CFG_STEP_LE + 0: /* first LE config step = get remote features */
if (!c->isMaster)
break; /* as a slave there is no config to do */
utilSetLE16(&rrf.conn, c->id);
ewd[0].evtType = HCI_EVT_LE_Meta;
ewd[0].extra = HCI_EVTLE_Read_Remote_Used_Features_Complete;
ewd[0].cbkData = userData;
if (!hciCmdSubmit(HCI_OGF_LE, HCI_CMD_LE_Read_Remote_Used_Features, &rrf, sizeof(rrf), ewd + 0, NULL)) {
success = false;
loge("Failed to send read remote used features command\n");
break;
}
return;
case CONN_CFG_STEP_LE + 1: /* second LE config step = done! */
break;
case CONN_CFG_STEP_EDR + 0: /* first EDR step = get name */
memcpy(&fng.mac, c->peerAddr.addr, sizeof(fng.mac));
hciDiscoverEdrGetName(hciConnCfgEdrNameCbk, userData, &fng);
return;
case CONN_CFG_STEP_EDR + 1: /* second EDR step = set flush timeout */
utilSetLE16(&waft.conn, c->id);
utilSetLE16(&waft.timeout, 0); /* infinite timeout */
ewd[0].evtType = HCI_EVT_Command_Complete;
ewd[0].extra = CMD_MAKE_OPCODE(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Automatic_Flush_Timeout);
ewd[0].cbkData = userData;
if (!hciCmdSubmit(HCI_OGF_Controller_and_Baseband, HCI_CMD_Write_Automatic_Flush_Timeout, &waft, sizeof(waft), ewd + 0, NULL)) {
success = false;
loge("Failed to send set flush timeout command\n");
break;
}
return;
case CONN_CFG_STEP_EDR + 2: /* third EDR step = read remote version */
utilSetLE16(&rrv.conn, c->id);
ewd[0].evtType = HCI_EVT_Command_Status;
ewd[0].extra = CMD_MAKE_OPCODE(HCI_OGF_Link_Control, HCI_CMD_Read_Remote_Version_Information);
ewd[0].cbkData = userData;
ewd[1].evtType = HCI_EVT_Read_Remote_Version_Complete;
ewd[1].cbkData = userData;
if (!hciCmdSubmit(HCI_OGF_Link_Control, HCI_CMD_Read_Remote_Version_Information , &rrv, sizeof(rrv), ewd + 0, ewd + 1, NULL)) {
success = false;
loge("Failed to send get remote version command\n");
break;
}
return;
case CONN_CFG_STEP_EDR + 3: /* fourth EDR step = read remote features/extended features */
ewd[0].evtType = HCI_EVT_Command_Status;
ewd[0].cbkData = userData;
ewd[1].cbkData = userData;
if (c->edr.btVer >= HCI_VERSION_1_2 && mBtVer >= HCI_VERSION_1_2) { /* can use read extended features */
ewd[0].extra = CMD_MAKE_OPCODE(HCI_OGF_Link_Control, HCI_CMD_Read_Remote_Extended_Features);
ewd[1].evtType = HCI_EVT_Read_Remote_Extended_Features_Complete;
utilSetLE16(&rref.conn, c->id);
utilSetLE8(&rref.page, aux);
if (!hciCmdSubmit(HCI_OGF_Link_Control, HCI_CMD_Read_Remote_Extended_Features, &rref, sizeof(rref), ewd + 0, ewd + 1, NULL)) {
success = false;
loge("Failed to send read remote extended features command\n");
break;
}
} else { //must use read features
ewd[0].extra = CMD_MAKE_OPCODE(HCI_OGF_Link_Control, HCI_CMD_Read_Remote_Supported_Features);
ewd[1].evtType = HCI_EVT_Read_Remote_Supported_Features_Complete;
utilSetLE16(&rrsf.conn, c->id);
if (!hciCmdSubmit(HCI_OGF_Controller_and_Baseband, HCI_CMD_Read_Remote_Supported_Features, &waft, sizeof(waft), ewd + 0, NULL)) {
success = false;
loge("Failed to send read remote supported features command\n");
break;
}
}
return;
case CONN_CFG_STEP_EDR + 4: /* fifth EDR step = done! */
break;
}
/* if we got here, the configuration step ended and it is time to proceed */
if (success) {
c->state = CONN_STATE_RUNNING;
if (hciScheduleConnUpNotif(c->handle, &c->peerAddr, &c->selfAddr, c->isMaster, c->encrypted, c->mitmSafe)) {
/* if there are any backlogged RX messages, queue them up too */
while (c->rxBacklog) {
struct hciBacklogItemHdr itemHdr;
sg packet;
if (!sgSerializeCutFront(c->rxBacklog, &itemHdr, sizeof(itemHdr))) {
loge("backlog header dequque fail!\n");
goto fail;
}
packet = sgSplit(c->rxBacklog, itemHdr.len);
if (!packet) {
loge("backlog packet dequeue fail\n");
goto fail;
}
sgSwap(packet, c->rxBacklog);
if (!sgLength(c->rxBacklog)) {
sgFree(c->rxBacklog);
c->rxBacklog = NULL;
}
if (!hciRxAclForRunningConn(c, itemHdr.aclHdr, packet)) {
loge("backlog packet rx enqueue fail\n");
sgFree(packet);
goto fail;
}
}
/* if there is a backlogged PIN or SSP request, fulfill them */
if (c->pinPending) {
logd("Fulfilling pending PIN request\n");
aapiBondStateChangedCbk(&c->peerAddr, AAPI_BOND_STATE_INPROGRESS);
aapiPinReqCbk(&c->peerAddr);
c->pinPending = false;
}
if (c->sspPending) {
logd("Fulfilling pending SSP request\n");
hciSecUserCbkSspIoCapabilityReq(&c->peerAddr, mIoCapability, false, HCI_AUTH_REQMENT_MITM_PROT_GENERAL_BOND);
c->sspPending = false;
}
return;
} else
loge("Failed to enqueue upcall for conn up\n");
}
fail:
hciConnDisconnect(c->id);
}
/*
* FUNCTION: hciConnAclUp
* USE: Setup a local ACL conn struct for this conn
* PARAMS: established - is it established?
* id - the id
* mac - the mac
* addrType - the address type for bt_addr struct
* encrypted - true if encrypted
* isMaster - am i master? (not authoritative in EDR cases)
* RETURN: connection struct or NULL if error
* NOTES: call with mConnsLock held
*/
static struct hciAclConn* hciConnAclUp(bool established, uint16_t id, const uint8_t *mac, uint8_t addrType, bool encrypted, bool isMaster)
{
bool isLE = addrType != BT_ADDR_TYPE_EDR;
struct hciAclConn *c;
struct bt_addr addr;
memcpy(addr.addr, mac, sizeof(addr.addr));
addr.type = addrType;
c = hciConnFindByAddr(&addr);
if (!established) {
if (!c)
logw("cannection we didn't know about failed. Do we care?\n");
else
hciConnAclStructDel(c);
return NULL;
}
if (hciConnFindById(id)) {
loge("Refusing to create conn struct for existing connection %d\n", id);
return NULL;
}
if (!c) {
c = hciConnAclStructNew(id, &addr, CONN_STATE_CFG, isMaster);
if (!c) {
loge("Failed to alloc conn struct for new ACL link\n");
hciConnDisconnect(id);
return NULL;
}
c->isMaster = isLE ? isMaster : false;
} else if (c->state == CONN_STATE_WAIT) {
c->state = CONN_STATE_CFG;
if (isLE)
c->isMaster = isMaster;
/* TODO: start next LE connection/restart adv/etc? */
} else {
loge("Connection in state %d at up time\n", c->state);
}
c->encrypted = encrypted;
return c;
}
/*
* FUNCTION: hciHandleConnAclEdrUp
* USE: Finish setup of a local ACL.EDR conn struct for this conn if success
* PARAMS: status - the connection status
* id - the id
* mac - the mac
* encrypted - true if encrypted
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclEdrUp(uint8_t status, uint16_t id, const uint8_t *mac, bool encrypted)
{
struct hciAclConn *c;
pthread_mutex_lock(&mConnsLock);
c = hciConnAclUp(!status, id, mac, BT_ADDR_TYPE_EDR, encrypted, false);
if (c) {
memcpy(c->selfAddr.addr, mLocalAddr, sizeof(c->selfAddr.addr));
c->selfAddr.type = BT_ADDR_TYPE_EDR;
hciConnCfg(c, CONN_CFG_STEP_EDR, 0);
}
pthread_mutex_unlock(&mConnsLock);
return !!c;
}
/*
* FUNCTION: hciHandleConnAclLeUp
* USE: Setup a local ACL.LE conn struct for this conn
* PARAMS: status - the connection status
* id - the conection id
* isMaster - true if we're the master
* mac - the mac
* addrRandom - true if adddress is random
* interval - LE connection interval
* latency - LE conenction latency
* timeout - LE connection timeout
* mca - LE connection master clock accuracy
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclLeUp(uint8_t status, uint16_t id, bool isMaster, const uint8_t *mac, bool addrRandom, uint16_t interval, uint16_t latency, uint16_t timeout, uint8_t mca)
{
struct hciAclConn *c;
pthread_mutex_lock(&mConnsLock);
c = hciConnAclUp(!status, id, mac, addrRandom ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC, false, isMaster);
if (c) {
c->le.interval = interval;
c->le.latency = latency;
c->le.timeout = timeout;
c->le.mca = mca;
/* XXX: TODO: once we start advertising random addresses, this will need to evolve */
memcpy(c->selfAddr.addr, mLocalAddr, sizeof(c->selfAddr.addr));
c->selfAddr.type = BT_ADDR_TYPE_LE_PUBLIC;
hciConnCfg(c, CONN_CFG_STEP_LE, 0);
}
pthread_mutex_unlock(&mConnsLock);
return !!c;
}
/*
* FUNCTION: hciHandleConnAclEncr
* USE: Notify all interested in encryption change
* PARAMS: cid - the connection id
* encrOn - is encryption on?
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclEncr(uint16_t cid, bool encrOn)
{
struct hciAclConn *c;
bool ret = false;
logd("cid %d now %sencrypted\n", cid, encrOn ? "" : "not ");
pthread_mutex_lock(&mConnsLock);
c = hciConnFindById(cid);
if (c) {
logd("cid %d was %sencrypted\n", cid, c->encrypted ? "" : "not ");
c->encrypted = encrOn;
ret = hciScheduleConnEncrChange(c->handle, c->encrypted, c->mitmSafe
);
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciHandleConnAclAuth
* USE: Notify all interested in authentication change
* PARAMS: cid - the connection id
* authOn - is auth on
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclAuth(uint16_t cid, bool authOn)
{
struct hciAclConn *c;
bool ret = false;
logd("cid %d now %sauthed\n", cid, authOn ? "" : "not ");
pthread_mutex_lock(&mConnsLock);
c = hciConnFindById(cid);
if (c) {
logd("cid %d was %sauthed\n", cid, c->mitmSafe ? "" : "not ");
c->mitmSafe = authOn;
ret = hciScheduleConnEncrChange(c->handle, c->encrypted, c->mitmSafe);
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciHandleConnAclRoleChange
* USE: Notify all interested in role change
* PARAMS: mac - the peer address
* amSlave - am i now slave?
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclRoleChange(const uint8_t* mac, bool amSlave)
{
struct hciAclConn *c;
struct bt_addr addr;
bool ret = false;
memcpy(addr.addr, mac, sizeof(addr.addr));
addr.type = BT_ADDR_TYPE_EDR;
logd("conn to "MACFMT" now %s\n", MACCONV(mac), amSlave? "slave" : "master");
pthread_mutex_lock(&mConnsLock);
c = hciConnFindByAddr(&addr);
if (c) {
logd("conn to "MACFMT" was %s\n", MACCONV(mac), c->isMaster? "master" : "slave");
c->isMaster = !amSlave;
ret = true;
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciHandleConnAclLeUpdate
* USE: Notify all interested in conn param update
* PARAMS: status - did update succeed?
* cid - the connection id
* interval - the new interval (valid only in case of success)
* latency - the new latency (valid only in case of success)
* timeout - the new timeout (valid only in case of success)
* RETURN: true on success
* NOTES:
*/
static bool hciHandleConnAclLeUpdate(uint8_t status, uint16_t cid, uint16_t interval, uint16_t latency, uint16_t timeout)
{
struct hciAclConn *c;
bool ret = false;
logd("cid %d update success: %d\n", cid, status);
pthread_mutex_lock(&mConnsLock);
c = hciConnFindById(cid);
if (c) {
if (!status) {
logd("update: {%d,%d,%d} -> {%d,%d,%d}\n", c->le.interval, c->le.latency, c->le.timeout, interval, latency, timeout);
c->le.interval = interval;
c->le.latency = latency;
c->le.timeout = timeout;
}
ret = hciScheduleConnParamChange(c->handle, !status, c->le.interval, c->le.latency, c->le.timeout);
}
pthread_mutex_unlock(&mConnsLock);
return ret;
}
/*
* FUNCTION: hciConnLeCancel
* USE: Cancel an ongoing LE connection attempt
* PARAMS: addr - whom the connection was to
* RETURN: true on success
* NOTES:
*/
static bool hciConnLeCancel(void)
{
return hciCmdSubmitSimpleWithCompleteWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Create_Connection_Cancel, NULL, 0, hciCmdStatusCbk, NULL);
}
/*
* FUNCTION: hciConnUpdate
* USE: Start a connection update for an LE connection
* PARAMS: cid - the cid
* intMin - minimum wanted interval
* intMax - maximum wanted itnerval
* latency - wanted latency
* timeout - wanted timeout
* RETURN: false on failure. else wait.
* NOTES:
*/
static bool hciConnUpdate(uint16_t cid, uint16_t intMin, uint16_t intMax, uint16_t latency, uint16_t timeout)
{
struct hciLeConnectionUpdate cmd;
utilSetLE16(&cmd.conn, cid);
utilSetLE16(&cmd.connIntervalMin, intMin);
utilSetLE16(&cmd.connIntervalMax, intMax);
utilSetLE16(&cmd.connLatency, latency);
utilSetLE16(&cmd.supervisionTimeout, timeout);
utilSetLE16(&cmd.minConnLen, 0); /* not a useful hint, i know */
utilSetLE16(&cmd.maxConnLen, intMin * 2);
return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_LE, HCI_CMD_LE_Connection_Update, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL);
}
/*
* FUNCTION: hciConnDisconnect
* USE: Start a disconnection process
* PARAMS: cid - the cid
* RETURN: false on failure. else wait.
* NOTES:
*/
static bool hciConnDisconnect(uint16_t cid)
{
struct hciDisconnect cmd;
utilSetLE16(&cmd.conn, cid);
utilSetLE16(&cmd.reason, 0x13); //TODO: better dealings with this value...
return hciCmdSubmitSimpleWithStatusWithDoneCbk(HCI_OGF_Link_Control, HCI_CMD_Disconnect, &cmd, sizeof(cmd), hciCmdStatusCbk, NULL);
}
/*
* FUNCTION: hciLeRandSimpleCbk
* USE: Called back when chip replies to our request for random numbers
* PARAMS: cbkData1 - the callback
* cbkData2 - callback data
* stackGoingDown - set if we're going down
* evt - the RXed event
* RETURN: NONE
* NOTES:
*/
static void hciLeRandSimpleCbk(void* cbkData1, void* cbkData2, bool stackGoingDown, struct hciEvtHdr *evt)
{
struct hciEvtCmdComplete *cmpl = (struct hciEvtCmdComplete*)(evt + 1);
struct hciCmplLeRand *rand = (struct hciCmplLeRand*)(cmpl + 1);
hciLeRandCbk cbk = (hciLeRandCbk)cbkData1;
cbk(cbkData2, !stackGoingDown && !utilGetLE8(&rand->status), utilGetLE64(&rand->rand));
}
/*
* FUNCTION: hciLeRand
* USE: Generate a random 64-bit number using LE radio's Rand()
* PARAMS: cbk - what to call when done
* cbkData -data to pass to said cbk
* RETURN: false on failure. else wait.
* NOTES:
*/
bool hciLeRand(hciLeRandCbk cbk, void *cbkData)
{
if (!mHaveLe)
return false;
return hciCmdSubmitSimple(HCI_OGF_LE, HCI_CMD_LE_Rand, NULL, 0, hciLeRandSimpleCbk, cbk, cbkData, sizeof(struct hciCmplLeRand), sizeof(struct hciCmplLeRand), true, true);
}