blob: a58653f67a0f07a968d848fe1c1cc8582de9e815 [file] [log] [blame]
/*
* Copyright 2018 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <stdlib.h>
#include "btleHid.h"
#include "gatt.h"
#include "util.h"
#include "log.h"
#include "mt.h"
// FOR various strange-looking checks, before assuming they are wrong, please see https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.human_interface_device.xml
//16-bit UUIDs for HID-over-GATT
#define BTLE_UUID_HID_CHAR_BOOT_KBD_INPUT_REPORT 0x2A22 // org.bluetooth.characteristic.boot_keyboard_input_report
//CCC MUST also be present (0x2902)
#define BTLE_UUID_HID_CHAR_BOOT_KBD_OUTPUT_REPORT 0x2A32 // org.bluetooth.characteristic.boot_keyboard_output_report
#define BTLE_UUID_HID_CHAR_BOOT_MOUSE_INPUT_REPORT 0x2A33 // org.bluetooth.characteristic.boot_mouse_input_report
//CCC MUST also be present (0x2902)
#define BTLE_UUID_HID_CHAR_INFORMATION 0x2A4A // org.bluetooth.characteristic.hid_information
#define BTLE_UUID_HID_CHAR_REPORT_MAP 0x2A4B // org.bluetooth.characteristic.report_map
#define BTLE_UUID_HID_CHARDESCR_EXTERNAL_REPORT_REF 0x2907 // org.bluetooth.descriptor.external_report_reference
#define BTLE_UUID_HID_CHAR_CONTROL_PT 0x2A4C // org.bluetooth.characteristic.hid_control_point
#define BTLE_UUID_HID_CHAR_REPORT 0x2A4D // org.bluetooth.characteristic.report (multiples allowed)
#define BTLE_UUID_HID_CHARDESCR_REPORT_REF 0x2908 // org.bluetooth.descriptor.report_reference
//CCC MUST also be present (0x2902)
#define BTLE_UUID_HID_CHAR_PROTOCOL_MODE 0x2A4E // org.bluetooth.characteristic.protocol_mode
#define BTLE_HID_REPORT_ID_INVALID 0xFF00 // a report ID that is never valid by HID spec
// from HID spec, needed here locally for some sanity checking
#define HID_REPORT_TYPE_IN 1
#define HID_REPORT_TYPE_OUT 2
#define HID_REPORT_TYPE_FEATURE 3
struct DataBuffer {
uint32_t dataSz;
uint8_t data[];
};
struct CharInfo {
uint16_t charHandle;
uint16_t valHandle;
uint16_t endHandle;
uint16_t cccdHandle; //optional
uint16_t extraHandle; //optional, only for some (BTLE_UUID_HID_CHARDESCR_EXTERNAL_REPORT_REF for BTLE_UUID_HID_CHAR_REPORT_MAP, BTLE_UUID_HID_CHARDESCR_REPORT_REF for BTLE_UUID_HID_CHAR_REPORT)
uint8_t charProps;
bool subbed;
// some chars we just read once and buffer forever. For them, the data will be stored here. sometimes we update these too
struct DataBuffer *chrData;
// sometimes we even need to read a descriptor too. Same as above, the data will be here
struct DataBuffer *descrData;
};
struct ReportsListNode {
struct ReportsListNode* next;
struct CharInfo chr;
uint16_t reportId; // 16 bits on purpose to allow invalid values to be set
uint8_t reportType; // as per hid spec
};
struct HidConn {
struct HidConn *prev;
struct HidConn *next;
ble_hid_conn_t id;
gatt_client_conn_t gattConn;
uint8_t state;
uint32_t substate; // intra-state state tracking
uint16_t hidServiceFirstHandle;
uint16_t hidServiceLastHandle;
struct CharInfo bootKbdInput;
struct CharInfo bootKbdOutput;
struct CharInfo bootMouseInput;
struct CharInfo hidInfo;
struct CharInfo reportMap;
struct CharInfo controlPt;
struct CharInfo protocolMode;
struct ReportsListNode *reports;
};
static BtleHidConnStateCbk mCbkState;
static BtleHidReportRxCbk mCbkRx;
static pthread_mutex_t mHidLock = PTHREAD_MUTEX_INITIALIZER;
static struct HidConn *mHidDevs = NULL;
static void btleHidAttachPerformPrerequisiteRead(gatt_client_conn_t gattConn);
/*
* FUNCTION: hidFindConnById
* USE: find a HidConn struct given its id
* PARAMS: id - the id to find
* RETURN: the structure or NULL if not found
* NOTES: call with mHidLock held
*/
static struct HidConn *hidFindConnById(ble_hid_conn_t id)
{
struct HidConn *c;
for (c = mHidDevs; c && c->id != id; c = c->next);
return c;
}
/*
* FUNCTION: hidFindConnByGattConn
* USE: find a HidConn struct given a gatt connection
* PARAMS: gattConn - the gatt connection to find
* RETURN: the structure or NULL if not found
* NOTES: call with mHidLock held
*/
static struct HidConn *hidFindConnByGattConn(gatt_client_conn_t gattConn)
{
struct HidConn *c;
for (c = mHidDevs; c && c->gattConn != gattConn; c = c->next);
return c;
}
/*
* FUNCTION: btleHidFreeCharInfo
* USE: Properly free a CharInfo struct (may call into GATT to unsubscribe)
* PARAMS: gattConn - the CATT connection ID (for unsubscribing) - pass 0 to not do that
* chr - the struct
* RETURN: NONE
* NOTES: calls into gatt. watch your locks!
*/
static void btleHidFreeCharInfo(gatt_client_conn_t gattConn, struct CharInfo *chr)
{
if (gattConn && chr->subbed && (GATT_CLI_STATUS_OK != gattClientNotifsUnsubscribe(gattConn, chr->valHandle)))
logw("Failed to unsubscribe from notifs/inds for value handle 0x%04x\n", chr->valHandle);
if (chr->chrData)
free(chr->chrData);
if (chr->descrData)
free(chr->descrData);
}
/*
* FUNCTION: btleHidDetachWithDev
* USE: Detach from a given HID device given its connection structure
* PARAMS: hid - the connection structure
* RETURN: success
* NOTES: call with mHidLock held
*/
static bool btleHidDetachWithDev(struct HidConn *hid)
{
struct ReportsListNode *node = hid->reports;
hid->state = BTLE_HID_CONN_STATE_TEARDOWN;
btleHidFreeCharInfo(hid->gattConn, &hid->bootKbdInput);
btleHidFreeCharInfo(hid->gattConn, &hid->bootKbdOutput);
btleHidFreeCharInfo(hid->gattConn, &hid->bootMouseInput);
btleHidFreeCharInfo(hid->gattConn, &hid->hidInfo);
btleHidFreeCharInfo(hid->gattConn, &hid->reportMap);
btleHidFreeCharInfo(hid->gattConn, &hid->controlPt);
btleHidFreeCharInfo(hid->gattConn, &hid->protocolMode);
while (node) {
struct ReportsListNode *t = node;
node = node->next;
btleHidFreeCharInfo(hid->gattConn, &t->chr);
free(t);
}
if (hid->next)
hid->next->prev = hid->prev;
if (hid->prev)
hid->prev->next = hid->next;
else
mHidDevs = hid->next;
free(hid);
return true;
}
/*
* FUNCTION: btleHidDetach
* USE: Detach from a HID device
* PARAMS: id - hid device id
* RETURN: the structure or NULL if not found
* NOTES:
*/
bool btleHidDetach(ble_hid_conn_t id)
{
struct HidConn *hid;
bool ret = false;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnById(id);
if (hid)
ret = btleHidDetachWithDev(hid);
else {
id = 0;
logw("Attempting to detach HID from a nonexistent HID device\n");
}
pthread_mutex_unlock(&mHidLock);
if (id)
mCbkState(id, BTLE_HID_CONN_STATE_TEARDOWN);
return ret;
}
/*
* FUNCTION: btleHidDetachFromGatt
* USE: Detach from a GATT connection
* PARAMS: gattConn - GATT connection
* RETURN: the structure or NULL if not found
* NOTES:
*/
bool btleHidDetachFromGatt(gatt_client_conn_t gattConn)
{
ble_hid_conn_t id = 0;
struct HidConn *hid;
bool ret = false;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (hid) {
ret = btleHidDetachWithDev(hid);
id = hid->id;
}
else
logw("Attempting to detach HID from an unknown GATT connection\n");
pthread_mutex_unlock(&mHidLock);
if (id)
mCbkState(id, BTLE_HID_CONN_STATE_TEARDOWN);
return ret;
}
/*
* FUNCTION: btleHidInit
* USE: Init HID-over-GATT subsystem
* PARAMS: stateCbk - the state callback
* rxCbk - the data RX callback
* RETURN: the structure or NULL if not found
* NOTES: call with mHidLock held
*/
void btleHidInit(BtleHidConnStateCbk stateCbk, BtleHidReportRxCbk rxCbk)
{
mCbkState = stateCbk;
mCbkRx = rxCbk;
}
/*
* FUNCTION: btleHidVerifyGattRequirementsMet
* USE: Now that GATT discovery is done, verify that the hid device characteristics/descriptors meet the minimum requirements of the spec
* PARAMS: hid - the hid device struct
* RETURN: the answer
* NOTES: called with mGattLock held.
*/
static bool btleHidVerifyGattRequirementsMet(struct HidConn *hid)
{
struct ReportsListNode *node;
if (hid->bootKbdInput.charHandle && !hid->bootKbdInput.cccdHandle)
logw("HID device has a Boot Keyboard Input characteristic, but said characteristic lacks a CCCD\n");
else if (hid->bootMouseInput.charHandle && !hid->bootMouseInput.cccdHandle)
logw("HID device has a Boot Mouse Input characteristic, but said characteristic lacks a CCCD\n");
else if (!hid->reportMap.charHandle)
logw("HID device has no report map characteristic\n");
else if (!hid->hidInfo.charHandle)
logw("HID device has no HID info characteristic\n");
else if (!hid->controlPt.charHandle)
logw("HID device has no control point characteristic\n");
else if ((hid->bootKbdInput.charHandle || hid->bootKbdOutput.charHandle || hid->bootMouseInput.charHandle) && !hid->protocolMode.charHandle)
logw("HID device has a Boot-mode descriptor characteristics but lacks a Protocol Mode selection descriptor\n");
else if (!hid->reports)
logw("HID device has no reports\n");
else if (hid->bootKbdInput.charHandle && ((hid->bootKbdInput.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != (GATT_PROP_READ | GATT_PROP_NOTIFY)))
loge("HID device's Boot Keyboard Input characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->bootKbdInput.charHandle, hid->bootKbdInput.charProps);
else if (hid->bootKbdOutput.charHandle && ((hid->bootKbdOutput.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != (GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP)))
loge("HID device's Boot Keyboard Output characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->bootKbdOutput.charHandle, hid->bootKbdOutput.charProps);
else if (hid->bootMouseInput.charHandle && ((hid->bootMouseInput.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != (GATT_PROP_READ | GATT_PROP_NOTIFY)))
loge("HID device's Boot Mouse Input characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->bootMouseInput.charHandle, hid->bootMouseInput.charProps);
else if (hid->hidInfo.charHandle && ((hid->hidInfo.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != GATT_PROP_READ))
loge("HID device's HID info characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->hidInfo.charHandle, hid->hidInfo.charProps);
else if (hid->controlPt.charHandle && ((hid->controlPt.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != GATT_PROP_WRITE_NO_RSP))
loge("HID device's control point characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->controlPt.charHandle, hid->controlPt.charProps);
else if (hid->protocolMode.charHandle && ((hid->protocolMode.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != (GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP)))
loge("HID device's Protocol Mode selection characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->protocolMode.charHandle, hid->protocolMode.charProps);
else if (hid->reportMap.charHandle && ((hid->reportMap.charProps & (GATT_PROP_BROADCAST | GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) != GATT_PROP_READ))
loge("HID device's report map characteristic's properties are wrong (0x%04x->0x%02x)\n", hid->reportMap.charHandle, hid->reportMap.charProps);
else {
for (node = hid->reports; node; node = node->next) {
if (!node->chr.charHandle)
logw("HID report somehow has no char handle?\n");
else if (node->chr.charProps & (GATT_PROP_BROADCAST | GATT_PROP_INDICATE | GATT_PROP_AUTH_SIG_WRITE)) // we'll check further when we know the report type
logw("HID report's characteristic's properties are wrong (0x%04x->0x%02x)\n", node->chr.charHandle, node->chr.charProps);
else if (!node->chr.extraHandle)
logw("HID report has no report reference descriptor\n");
else if ((node->chr.charProps & (GATT_PROP_NOTIFY | GATT_PROP_INDICATE)) && !node->chr.cccdHandle)
logw("HID report lacks a CCCD while we expect it\n");
else
continue;
return false;
}
return true;
}
return false;
}
/*
* FUNCTION: btleHidGetReportListNodeByCharHandle
* USE: find a report node in the reports list for a given char handle in a given HID device
* PARAMS: hid - the hid device struct
* chrValueHandle - the char value handle
* RETURN: the list node if found, else NULL
* NOTES: called with mGattLock held.
*/
static struct ReportsListNode* btleHidGetReportListNodeByCharValueHandle(struct HidConn *hid, uint16_t chrValueHandle)
{
struct ReportsListNode *node;
for (node = hid->reports; node; node = node->next) {
if (node->chr.valHandle == chrValueHandle)
return node;
}
return NULL;
}
/*
* FUNCTION: btleHidGetReportListNodeByReportId
* USE: find a report node in the reports list for a given report ID in a given HID device
* PARAMS: hid - the hid device struct
* reportId - the desired report ID
* RETURN: the list node if found, else NULL
* NOTES: called with mGattLock held.
*/
static struct ReportsListNode* btleHidGetReportListNodeByReportId(struct HidConn *hid, uint32_t reportId)
{
struct ReportsListNode *node;
for (node = hid->reports; node; node = node->next) {
if (node->reportId == reportId)
return node;
}
return NULL;
}
/*
* FUNCTION: btleHidReportDataArrivedCbk
* USE: Called when a notif/ind/read reply arrives
* PARAMS: gattConn - the GATT client connection id
* chrValueHandle - the char value handle
* data - the data
* isReadReply - was this a read reply
* RETURN: NONE
* NOTES:
*/
static void btleHidReportDataArrivedCbk(gatt_client_conn_t gattConn, uint16_t chrValueHandle, sg data, bool isReadReply)
{
struct ReportsListNode* node;
struct HidConn *hid;
int32_t reportId;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("No HID device found for GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (hid->state != BTLE_HID_CONN_STATE_UP) {
logi("Dropping RXed data for connection that is not in UP state\n");
goto out_unlock;
}
node = btleHidGetReportListNodeByCharValueHandle(hid, chrValueHandle);
if (node)
reportId = (uint32_t)node->reportId;
else if (chrValueHandle == hid->bootKbdInput.valHandle)
reportId = BTLE_HID_REPORT_ID_BOOT_KEYBOARD_IN;
else if (chrValueHandle == hid->bootMouseInput.valHandle)
reportId = BTLE_HID_REPORT_ID_BOOT_MOUSE_IN;
else {
logw("RXed data for value handle 0x%04x matches no report\n", chrValueHandle);
goto out_unlock;
}
pthread_mutex_unlock(&mHidLock);
mCbkRx(gattConn, reportId, data, isReadReply);
return;
out_unlock:
pthread_mutex_unlock(&mHidLock);
sgFree(data);
}
/*
* FUNCTION: btleHidAsyncDataArrivedCbk
* USE: Called when a notif/ind arrives
* PARAMS: gattConn - the GATT client connection id
* charValueHandle - the char value handle
* reliable - was this an indication
* data - the data
* RETURN: NONE
* NOTES:
*/
static void btleHidAsyncDataArrivedCbk(gatt_client_conn_t gattConn, uint16_t charValueHandle, bool reliable, sg data)
{
btleHidReportDataArrivedCbk(gattConn, charValueHandle, data, false);
}
/*
* FUNCTION: btleHidCccdWriteStatusCbk
* USE: Called when a notif/ind subscription happens
* PARAMS: gattConn - the GATT client connection id
* valHandle - the handle of the value handle in the char whose CCCD was written
* forSubscribe - true if status is of subscribe, false for unsubscribe
* status - GATT_CLI_STATUS_*
* RETURN: NONE
* NOTES:
*/
static void btleHidCccdWriteStatusCbk(gatt_client_conn_t gattConn, uint16_t valHandle, bool forSubscribe, uint8_t status)
{
struct ReportsListNode* node;
struct CharInfo *chr = NULL;
ble_hid_conn_t id = 0;
struct HidConn *hid;
if (!forSubscribe)
return;
logd("CCCD subscribe status %u for val handle 0x%04x\n", status, valHandle);
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("No HID device found for GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (status != GATT_CLI_STATUS_OK) {
logw("a CCCD write with handle 0x%04x failed: 0x%02x - detaching!\n", valHandle, status);
goto out_detach;
}
// find the character descriptor this is for
if (valHandle == hid->bootKbdInput.valHandle)
chr = &hid->bootKbdInput;
else if (valHandle == hid->bootKbdOutput.valHandle)
chr = &hid->bootKbdOutput;
else if (valHandle == hid->bootMouseInput.valHandle)
chr = &hid->bootMouseInput;
else if (valHandle == hid->hidInfo.valHandle)
chr = &hid->hidInfo;
else if (valHandle == hid->reportMap.valHandle)
chr = &hid->reportMap;
else if (valHandle == hid->controlPt.valHandle)
chr = &hid->controlPt;
else if (valHandle == hid->protocolMode.valHandle)
chr = &hid->protocolMode;
else for (node = hid->reports; node && !chr; node = node->next) {
if (valHandle == node->chr.valHandle)
chr = &node->chr;
}
if (!chr) {
logw("HID notif/ind sub for unknown characteristic!\n");
goto out_detach;
}
if (chr->subbed) {
logw("HID notif/ind sub for characteristic we're already subbed to!\n");
goto out_detach;
}
chr->subbed = true;
if (!hid->substate) {
logw("HID notif/ind refcounting error!\n");
goto out_detach;
}
if (--hid->substate)
logd("HID: still waiting on %u CCCD writes\n", hid->substate);
else { // WE're DONE!
hid->state = BTLE_HID_CONN_STATE_UP;
id = hid->id;
}
pthread_mutex_unlock(&mHidLock);
if (id)
mCbkState(id, BTLE_HID_CONN_STATE_UP);
return;
out_detach:
if (!btleHidDetachWithDev(hid))
loge("Failed to detach on error in CCCDs write!\n");
out_unlock:
pthread_mutex_unlock(&mHidLock);
}
/*
* FUNCTION: btleHidAttachWriteCccds
* USE: Request the writes to the CCCDs we care about
* PARAMS: gattConn - the GATT client connection id
* RETURN: NONE
* NOTES: If all writes are done, proceed to the next step of initialization
*/
static void btleHidAttachWriteCccds(gatt_client_conn_t gattConn)
{
uint16_t *arr, *charHandles, *valHandles, *cccdHandles, *reliable;
struct ReportsListNode *node;
struct HidConn *hid;
uint32_t num, i;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("No HID device found for GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (hid->state != BTLE_HID_CONN_STATE_WRITING_CCCDS) {
logw("HID device not in WRITING CCCDs state as expected "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_detach;
}
// count CCCDs
for (num = 0, node = hid->reports; node; node = node->next) {
if (node->chr.cccdHandle)
num++;
}
if (hid->bootKbdInput.cccdHandle)
num++;
if (hid->bootMouseInput.cccdHandle)
num++;
// sanity check
if (!num) {
logw("Unlikely that a HID device has no CCCDs for us to write\n");
goto out_detach;
}
hid->substate = num;
logd("HID: %u CCCDs to write\n", num);
// allocate array for temporarily storing all of this, assign pointers
arr = malloc(sizeof(uint16_t) * num * 4);
if (!arr)
goto out_detach;
charHandles = arr + 0 * num;
valHandles = arr + 1 * num;
cccdHandles = arr + 2 * num;
reliable = arr + 3 * num;
// now actually write the data into the arrays
for (i = 0, node = hid->reports; node; node = node->next) {
if (node->chr.cccdHandle) {
charHandles[i] = node->chr.charHandle;
valHandles[i] = node->chr.valHandle;
cccdHandles[i] = node->chr.cccdHandle;
reliable[i] = !(node->chr.charProps & GATT_PROP_NOTIFY); // only opt for indicaton when notification not available
i++;
}
}
if (hid->bootKbdInput.cccdHandle) {
charHandles[i] = hid->bootKbdInput.charHandle;
valHandles[i] = hid->bootKbdInput.valHandle;
cccdHandles[i] = hid->bootKbdInput.cccdHandle;
reliable[i] = !(hid->bootKbdInput.charProps & GATT_PROP_NOTIFY);
i++;
}
if (hid->bootMouseInput.cccdHandle) {
charHandles[i] = hid->bootMouseInput.charHandle;
valHandles[i] = hid->bootMouseInput.valHandle;
cccdHandles[i] = hid->bootMouseInput.cccdHandle;
reliable[i] = !(hid->bootMouseInput.charProps & GATT_PROP_NOTIFY);
i++;
}
pthread_mutex_unlock(&mHidLock);
for (i = 0; i < num; i++) {
logd("HID: writing %s CCCD {0x%04x 0x%04x 0x%04x}\n", reliable[i] ? "reliable" : "fast", charHandles[i], valHandles[i], cccdHandles[i]);
if (GATT_CLI_STATUS_OK != gattClientNotifsSubscribe(gattConn, charHandles[i], valHandles[i], cccdHandles[i], !!reliable[i], btleHidCccdWriteStatusCbk, btleHidAsyncDataArrivedCbk)) {
logw("Failed to write CCCD for {0x%04x,0x%04x,0x%04x,%u\n", charHandles[i], valHandles[i], cccdHandles[i], !!reliable[i]);
break;
}
}
free(arr);
// did it all work?
if (i != num) {
logw("Some CCCD writing failed! detaching\n");
if (!btleHidDetachFromGatt(gattConn))
loge("Failed to detach from GATT conn on error in descriptors search!\n");
return;
}
// once all callbacks fired, we'll be all set to go
return;
out_detach:
if (!btleHidDetachWithDev(hid))
loge("Failed to detach on error in CCCDs write!\n");
out_unlock:
pthread_mutex_unlock(&mHidLock);
}
/*
* FUNCTION: btleHidCheckAndProcessDescriptors
* USE: Now that descriptors are read, verify that the hid device is still sane
* PARAMS: hid - the hid device struct
* RETURN: the answer
* NOTES: called with mGattLock held.
*/
static bool btleHidCheckAndProcessDescriptors(struct HidConn *hid)
{
struct ReportsListNode *node, *t;
//process reports
for (node = hid->reports; node; node = node->next) {
uint8_t reportType, reportId;
if (!node->chr.descrData || node->chr.descrData->dataSz != 2) {
logw("HID report with handle 0x%04x lacks a proper report reference descriptor\n", node->chr.charHandle);
return false;
}
reportId = utilGetLE8(node->chr.descrData->data + 0);
reportType = utilGetLE8(node->chr.descrData->data + 1);
// verify report ID is not a dupe
t = btleHidGetReportListNodeByReportId(hid, reportId);
if (t) {
logw("HID report with handle 0x%04x has a report ID of 0x%02x, but so does the one at 0x%04x\n", node->chr.charHandle, reportId, t->chr.charHandle);
return false;
}
// verify report type, CCCDs present where needed and not present where not expected, verify capabilities
switch (reportType) {
case HID_REPORT_TYPE_IN:
if ((node->chr.charProps & (GATT_PROP_READ | GATT_PROP_WRITE_NO_RSP | GATT_PROP_AUTH_SIG_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_BROADCAST)) != (GATT_PROP_READ | GATT_PROP_NOTIFY)) {
logw("HID INPUT report's characteristic's properties are wrong (0x%04x->0x%02x)\n", node->chr.charHandle, node->chr.charProps);
return false;
}
if (!node->chr.cccdHandle) {
logw("HID INPUT report at 0x%04x is missing a CCCD\n", node->chr.charHandle);
return false;
}
break;
case HID_REPORT_TYPE_OUT:
if ((node->chr.charProps & (GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP | GATT_PROP_AUTH_SIG_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_BROADCAST)) != (GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP)) {
logw("HID OUTPUT report's characteristic's properties are wrong (0x%04x->0x%02x)\n", node->chr.charHandle, node->chr.charProps);
return false;
}
if (node->chr.cccdHandle) {
logw("HID OUTPUT report at 0x%04x unexpectedly has a CCCD\n", node->chr.charHandle);
return false;
}
break;
case HID_REPORT_TYPE_FEATURE:
if ((node->chr.charProps & (GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_WRITE_NO_RSP | GATT_PROP_AUTH_SIG_WRITE | GATT_PROP_NOTIFY | GATT_PROP_INDICATE | GATT_PROP_BROADCAST)) != (GATT_PROP_READ | GATT_PROP_WRITE)) {
logw("HID FEATURE report's characteristic's properties are wrong (0x%04x->0x%02x)\n", node->chr.charHandle, node->chr.charProps);
return false;
}
if (node->chr.cccdHandle) {
logw("HID FEATURE report at 0x%04x unexpectedly has a CCCD\n", node->chr.charHandle);
return false;
}
break;
default:
logw("HID report with handle 0x%04x has an invalid report type %u\n", node->chr.charHandle, reportType);
return false;
}
//store the result now that we're sure this report makes sense
node->reportId = reportId;
node->reportType = reportType;
}
if (!hid->hidInfo.charHandle || !hid->hidInfo.chrData || hid->hidInfo.chrData->dataSz != 4) { // hid info should have data of exactly 4 bytes
logw("HID info structure missing or invalid in size!\n");
return false;
}
return true;
}
/*
* FUNCTION: btleHidAttachPrerequisiteReadGetNext
* USE: figure out the next thing to read for prerequisite reads
* PARAMS: hid - the hid device struct
* handleP - if not null, store the handle to read here
* bufPPP - if not null, store pointer to buffer pointer here
* RETURN: true if there IS something next to read, false else
* NOTES: call with mGattLock held
*/
static bool btleHidAttachPrerequisiteReadGetNext(struct HidConn *hid, uint16_t *handleP, struct DataBuffer ***bufPPP)
{
struct DataBuffer **bufPP = NULL;
struct ReportsListNode *node;
uint16_t handleToRead;
// find first char/descr which we expect to be read and which is not yet
if (hid->protocolMode.charHandle && !hid->protocolMode.chrData) {
bufPP = &hid->protocolMode.chrData;
handleToRead = hid->protocolMode.valHandle;
}
else if (hid->reportMap.charHandle && !hid->reportMap.chrData) {
bufPP = &hid->reportMap.chrData;
handleToRead = hid->reportMap.valHandle;
}
else if (hid->reportMap.extraHandle && !hid->reportMap.descrData) {
bufPP = &hid->reportMap.descrData;
handleToRead = hid->reportMap.extraHandle;
}
else if (hid->hidInfo.charHandle && !hid->hidInfo.chrData) {
bufPP = &hid->hidInfo.chrData;
handleToRead = hid->hidInfo.valHandle;
}
else for (node = hid->reports; node && !bufPP; node = node->next) {
if (node->chr.extraHandle && !node->chr.descrData) {
bufPP = &node->chr.descrData;
handleToRead = node->chr.extraHandle;
}
}
if (!bufPP)
return false;
if (handleP)
*handleP = handleToRead;
if (bufPPP)
*bufPPP = bufPP;
return true;
}
/*
* FUNCTION: btleHidAttachPrerequisiteReadDataRxedCbk
* USE: Receive the result of a read done by btleHidAttachPerformPrerequisiteRead
* PARAMS: gattConn - the GATT client connection id
* trans - transaction ID (we store handle here as a way to double-check we got what we wanted)
* data - the data read or NULL on error
* RETURN: NONE
* NOTES:
*/
static void btleHidAttachPrerequisiteReadDataRxedCbk(gatt_client_conn_t gattConn, uniq_t trans, sg data)
{
uint16_t expectedHandle = 0, handle = trans;
struct DataBuffer **bufPP;
struct DataBuffer *buf;
struct HidConn *hid;
logd("HID: prereq read cbk read hadle 0x%04x with %sNULL ptr data\n", handle, data ? "non-" : "");
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("No HID device found for prereq read on GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (!data) {
logw("Failed to prereq read handle 0x%04x\n", handle);
goto out_detach;
}
if (!btleHidAttachPrerequisiteReadGetNext(hid, &expectedHandle, &bufPP) || expectedHandle != handle) {
logw("Read handle 0x%04x not same as expected (0x%04x), or none expected\n", handle, expectedHandle);
goto out_detach;
}
logd("HID: prereq read matches what we expected, recording\n");
// record this data we just read
buf = (struct DataBuffer*)malloc(sizeof(struct DataBuffer) + sgLength(data));
if (!buf) {
logw("Failed to allocate buffer for prereq read data\n");
goto out_detach;
}
buf->dataSz = sgLength(data);
(void)sgSerialize(data, 0, sgLength(data), buf->data);
*bufPP = buf;
// go read the next thing we need to read
pthread_mutex_unlock(&mHidLock);
sgFree(data);
btleHidAttachPerformPrerequisiteRead(gattConn);
return;
out_detach:
if (!btleHidDetachWithDev(hid))
loge("Failed to detach on error in prereq read!\n");
out_unlock:
pthread_mutex_unlock(&mHidLock);
if (data)
sgFree(data);
}
/*
* FUNCTION: btleHidAttachPerformPrerequisiteRead
* USE: Request the read of the next thing we need to read before declaring the device "ready"
* PARAMS: gattConn - the GATT client connection id
* RETURN: NONE
* NOTES: If all reads are done, proceed to the next step of initialization
*/
static void btleHidAttachPerformPrerequisiteRead(gatt_client_conn_t gattConn)
{
uint16_t handleToRead;
struct HidConn *hid;
logd("HID: prereq read do - deciding how to proceed\n");
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("No HID device found for GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (hid->state != BTLE_HID_CONN_STATE_READING) {
logw("HID device not in READING state as expected "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_detach;
}
if (!btleHidAttachPrerequisiteReadGetNext(hid, &handleToRead, NULL)) { // nothing more to read - go to next stage
ble_hid_conn_t id;
// again verify requirements now that we've read descriptors and such
if (!btleHidCheckAndProcessDescriptors(hid)) {
logw("HID device descriptor issues found. Detaching\n");
goto out_detach;
}
// start writing the required things
hid->state = BTLE_HID_CONN_STATE_WRITING_CCCDS;
id = hid->id;
pthread_mutex_unlock(&mHidLock);
mCbkState(id, BTLE_HID_CONN_STATE_WRITING_CCCDS);
btleHidAttachWriteCccds(gattConn);
return;
}
logd("HID: prereq read do decided to read handle 0x%04x\n", handleToRead);
// read the thing we've decided to read
pthread_mutex_unlock(&mHidLock);
if (GATT_CLI_STATUS_OK != gattClientUtilLongRead(gattConn, handleToRead, 0, (uniq_t)handleToRead, btleHidAttachPrerequisiteReadDataRxedCbk)) {
logw("Failed to read a required handle 0x%04x\n", handleToRead);
if (!btleHidDetachFromGatt(gattConn))
loge("Failed to detach from GATT conn on error in descriptors search!\n");
}
return;
out_detach:
if (!btleHidDetachWithDev(hid))
loge("Failed to detach on error in descriptors search!\n");
out_unlock:
pthread_mutex_unlock(&mHidLock);
}
/*
* FUNCTION: btleHidUtilAddCharToCharList
* USE: Add a node to a characteristic list and return pointer to it
* PARAMS: listP - pointer to where list head is stored
* RETURN: char info structure for writing into or NULL on error
* NOTES:
*/
static struct CharInfo* btleHidUtilAddCharToCharList(struct ReportsListNode **listP)
{
struct ReportsListNode *node = calloc(1, sizeof(struct ReportsListNode));
if (node) {
node->next = *listP;
*listP = node;
node->reportId = BTLE_HID_REPORT_ID_INVALID;
return &node->chr;
}
return NULL;
}
/*
* FUNCTION: btleHidAttachAddDiscoveredChar
* USE: Called once per discovered characteristic
* PARAMS: hid - the connection structure
* chr - info on a character
* RETURN: NONE
* NOTES: caleld with mHidLock held
*/
static void btleHidAttachAddDiscoveredChar(struct HidConn *hid, const struct GattTraversedServiceChar *chr)
{
bool isReport = false, isBootInputReport = false, isReportMap = false;
struct CharInfo *dst = NULL;
uint16_t uuid16;
uint32_t i;
if (!uuidToUuid16(&uuid16, &chr->uuid)) {
logw("Unexpected uuid128-named char found in HID service: "UUIDFMT"\n", UUIDCONV(chr->uuid));
return;
}
logd("HID: found char with uuid16 0x%04x\n", uuid16);
switch (uuid16) {
case BTLE_UUID_HID_CHAR_BOOT_KBD_INPUT_REPORT:
dst = &hid->bootKbdInput;
isBootInputReport = true;
break;
case BTLE_UUID_HID_CHAR_BOOT_KBD_OUTPUT_REPORT:
dst = &hid->bootKbdOutput;
break;
case BTLE_UUID_HID_CHAR_BOOT_MOUSE_INPUT_REPORT:
dst = &hid->bootMouseInput;
isBootInputReport = true;
break;
case BTLE_UUID_HID_CHAR_INFORMATION:
dst = &hid->hidInfo;
break;
case BTLE_UUID_HID_CHAR_REPORT_MAP:
dst = &hid->reportMap;
isReportMap = true;
break;
case BTLE_UUID_HID_CHAR_CONTROL_PT:
dst = &hid->controlPt;
break;
case BTLE_UUID_HID_CHAR_PROTOCOL_MODE:
dst = &hid->protocolMode;
break;
case BTLE_UUID_HID_CHAR_REPORT:
dst = btleHidUtilAddCharToCharList(&hid->reports);
isReport = true;
break;
default:
logw("Unexpected char with uuid16 0x%04x found in HID service\n", uuid16);
return;
}
if (!dst)
logw("Unexpected characteristic (uuid16 0x%04x) found in HID service. Ignoring\n", uuid16);
else if (dst->charHandle)
logw("Unexpected duplicate characteristic (uuid16 0x%04x) found in HID. Ignoring\n", uuid16);
else {
logd("HID: char recorded\n");
dst->charHandle = chr->firstHandle;
dst->valHandle = chr->valHandle;
dst->endHandle = chr->lastHandle;
dst->charProps = chr->charProps;
// handle descriptors
for (i = 0; i < chr->numDescrs; i++) {
if (!uuidToUuid16(&uuid16, &chr->descrs[i].uuid)) {
logw("Unexpected uuid128-named descr found in HID service: "UUIDFMT"\n", UUIDCONV(chr->descrs[i].uuid));
continue;
}
logd("HID: found descr with uuid16 0x%04x at handle 0x%04x\n", uuid16, chr->descrs[i].handle);
//if this char belongs to a report, record it there
if (isReport) {
if (uuid16 == GATT_UUID_CHAR_CLI_CHAR_CFG) // cccd in a report
dst->cccdHandle = chr->descrs[i].handle;
else if (uuid16 == BTLE_UUID_HID_CHARDESCR_REPORT_REF) // report ref in a report
dst->extraHandle = chr->descrs[i].handle;
else
logi("Unexpected descriptor with uuid16 0x%04x in report at 0x%04x\n", uuid16, dst->charHandle);
}
else if (isBootInputReport && uuid16 == GATT_UUID_CHAR_CLI_CHAR_CFG)
dst->cccdHandle = chr->descrs[i].handle;
else if (isReportMap && uuid16 == BTLE_UUID_HID_CHARDESCR_EXTERNAL_REPORT_REF)
dst->extraHandle = chr->descrs[i].handle;
else
logi("HID: descr in HID char is meaningless & was ignored\n");
}
}
}
/*
* FUNCTION: btleHidAttachServiceTraversalCbk
* USE: Called by GATT when the HID service is found at request of btleHidAttach()
* PARAMS: gattConn - the GATT client connection id
* trans - transaction ID (unused here as we can look up by gatt conn id)
* data - the traversal data or NULL on error
* RETURN: NONE
* NOTES:
*/
static void btleHidAttachServiceTraversalCbk(gatt_client_conn_t gattConn, uniq_t trans, const struct GattTraversedService* data)
{
const struct uuid hidUuid = BT_UUID_STATIC_16(BTLE_UUID_HID_SERVICE);
struct HidConn *hid;
ble_hid_conn_t id;
uint32_t i;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnByGattConn(gattConn);
if (!hid) {
logw("Unexpected GATT service traversal callback for unknown-to-HID connection "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (!data || !uuidCmp(&data->uuid, &hidUuid)) {
logw("Error finding or traversing HID service on GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_detach;
}
// copy basics
hid->hidServiceFirstHandle = data->firstHandle;
hid->hidServiceLastHandle = data->lastHandle;
// record characteristics
for (i = 0; i < data->numChars; i++)
btleHidAttachAddDiscoveredChar(hid, &data->chars[i]);
// verify requirements are met
if (!btleHidVerifyGattRequirementsMet(hid)) {
logw("HID device on GATT connection "GATTHANDLEFMT" did not meet requirements of HID-over-GATT spec and will not be usable!\n", GATTHANDLECNV(gattConn));
goto out_detach;
}
//go on to read required prerequisites
id = hid->id;
hid->state = BTLE_HID_CONN_STATE_READING;
pthread_mutex_unlock(&mHidLock);
mCbkState(id, BTLE_HID_CONN_STATE_READING);
btleHidAttachPerformPrerequisiteRead(gattConn);
return;
out_detach:
if (!btleHidDetachWithDev(hid))
loge("Failed to detach on error in service search!\n");
out_unlock:
pthread_mutex_unlock(&mHidLock);
}
/*
* FUNCTION: btleHidAttach
* USE: Attach HID to a GATT connection
* PARAMS: gattConn - a gatt client connection
* RETURN: hid connection id or zero on immediate failure
* NOTES: expect state callback with details
*/
ble_hid_conn_t btleHidAttach(gatt_client_conn_t gattConn)
{
const struct uuid hidUuid = BT_UUID_STATIC_16(BTLE_UUID_HID_SERVICE);
ble_hid_conn_t hidConn = 0;
struct HidConn *hid;
ble_hid_conn_t id;
hid = calloc(1, sizeof(struct HidConn));
if (!hid)
return 0;
pthread_mutex_lock(&mHidLock);
if (hidFindConnByGattConn(gattConn)) {
logw("HID is already attached to this GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
pthread_mutex_unlock(&mHidLock);
free(hid);
return 0;
}
hid->id = hidConn = uniqGetNext();
hid->gattConn = gattConn;
hid->state = BTLE_HID_CONN_STATE_GATT_DISCOVERY;
id = hid->id;
hid->next = mHidDevs;
if (mHidDevs)
mHidDevs->prev = hid;
mHidDevs = hid;
pthread_mutex_unlock(&mHidLock);
mCbkState(id, BTLE_HID_CONN_STATE_GATT_DISCOVERY);
logi("HID attempting an attachment to GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
if (GATT_CLI_STATUS_OK == gattClientUtilFindAndTraversePrimaryService(gattConn, &hidUuid, 0, btleHidAttachServiceTraversalCbk))
return hidConn;
logw("Failed to start the GATT service search\n");
if (!btleHidDetachFromGatt(hidConn))
loge("Failed to detach on error on attach!\n");
return 0;
}
/*
* FUNCTION: btleHidGetReportDescriptors
* USE: Get the report descriptors from a HID device
* PARAMS: hidId - HID device handle
* descriptorDataP - return data pointer here if not NULL
* descriptorLenP - return data length here if not NULL
* RETURN: true on success
* NOTES:
*/
bool btleHidGetReportDescriptors(ble_hid_conn_t hidId, const void **descriptorDataP, uint32_t *descriptorLenP)
{
struct HidConn *hid;
bool ret = false;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnById(hidId);
if (!hid)
logw("Attempting to get HID descriptors from a nonexistent HID device\n");
else if (hid->state != BTLE_HID_CONN_STATE_UP)
logw("Attempting to get HID descriptors from a HID device in state %u\n", hid->state);
else {
if (descriptorDataP)
*descriptorDataP = hid->reportMap.chrData->data;
if (descriptorLenP)
*descriptorLenP = hid->reportMap.chrData->dataSz;
ret = true;
}
pthread_mutex_unlock(&mHidLock);
return ret;
}
/*
* FUNCTION: btleHidGetHidInfo
* USE: Get HID info from a HID device
* PARAMS: hidId - HID device handle
* hidVerP - hid version bcd stored here if not NULL
* countryP - country code stored here if not NULL
* flagsP - HID flags stored here if not NULL
* RETURN: true on success
* NOTES:
*/
bool btleHidGetHidInfo(ble_hid_conn_t hidId, uint16_t *hidVerP, uint8_t *countryP, uint8_t *flagsP)
{
struct HidConn *hid;
bool ret = false;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnById(hidId);
if (!hid)
logw("Attempting to get HID info from a nonexistent HID device\n");
else if (hid->state != BTLE_HID_CONN_STATE_UP)
logw("Attempting to get HID info from a HID device in state %u\n", hid->state);
else {
*hidVerP = utilGetLE16(hid->hidInfo.chrData->data + 0);
if (countryP)
*countryP = utilGetLE8(hid->hidInfo.chrData->data + 2);
if (flagsP)
*flagsP = utilGetLE8(hid->hidInfo.chrData->data + 3);
ret = true;
}
pthread_mutex_unlock(&mHidLock);
return ret;
}
/*
* FUNCTION: btleHidWriteReportWriteCbk
* USE: Called by GATT when our write succeeds or fails
* PARAMS: gattConn - a gatt client connection
* trans - transaction ID (unused here as we can look up by gatt conn id)
* handle - handle we wrote
* status - GATT status
* RETURN: NONE
* NOTES: it is likely that this success or failure is not actionable for us, so we do nothing
*/
static void btleHidWriteReportWriteCbk(gatt_client_conn_t gattConn, uniq_t trans, uint16_t handle, uint8_t status)
{
if (status != GATT_CLI_STATUS_OK)
logi("HID GATT Write to handle 0x%04 failed with status 0x%02x\n", handle, status);
}
/*
* FUNCTION: btleHidWriteReport
* USE: Write to a report
* PARAMS: hidId - HID device handle
* reportId - the report we want to write
* data - data to write
* RETURN: true on success
* NOTES:
*/
bool btleHidWriteReport(ble_hid_conn_t hidId, int32_t reportId, sg data)
{
struct HidConn *hid;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnById(hidId);
if (!hid)
logw("Attempting to write to nonexistent HID device\n");
else if (hid->state != BTLE_HID_CONN_STATE_UP)
logw("Attempting to write to HID device in state %u\n", hid->state);
else {
struct ReportsListNode* node;
uint16_t handle = 0;
if (reportId == BTLE_HID_REPORT_ID_BOOT_KEYBOARD_IN && hid->bootKbdInput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_WRITE))
handle = hid->bootKbdInput.valHandle;
else if (reportId == BTLE_HID_REPORT_ID_BOOT_KEYBOARD_OUT && hid->bootKbdOutput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_WRITE))
handle = hid->bootKbdOutput.valHandle;
else if (reportId == BTLE_HID_REPORT_ID_BOOT_MOUSE_IN && hid->bootMouseInput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_WRITE))
handle = hid->bootMouseInput.valHandle;
else if (reportId >= 0 && (node = btleHidGetReportListNodeByReportId(hid, reportId)) && (node->chr.charProps & GATT_PROP_WRITE))
handle = node->chr.valHandle;
else
logw("Write to report %d is permanently impossible\n", reportId);
if (handle) {
gatt_client_conn_t conn = hid->gattConn;
pthread_mutex_unlock(&mHidLock);
return GATT_CLI_STATUS_OK == gattClientWrite(conn, handle, 0, GATT_CLI_WRITE_TYPE_WRITE, 0, data, 0, btleHidWriteReportWriteCbk);
}
}
pthread_mutex_unlock(&mHidLock);
return false;
}
/*
* FUNCTION: btleHidReadReportReadCbk
* USE: Called by GATT when our read succeeds or fails
* PARAMS: gattConn - a gatt client connection
* trans - transaction ID (unused here as we can look up by gatt conn id)
* handle - handle we wrote (char value handle)
* status - GATT status
* data - the data
* RETURN: NONE
* NOTES: it is likely that this success or failure is not actionable for us, so we do nothing
*/
static void btleHidReadReportReadCbk(gatt_client_conn_t gattConn, uniq_t trans, uint16_t handle, uint8_t status, sg data)
{
if (status != GATT_CLI_STATUS_OK || !data)
logi("HID GATT READ to handle 0x%04 failed with status 0x%02x\n", handle, status);
btleHidReportDataArrivedCbk(gattConn, handle, data, true);
}
/*
* FUNCTION: btleHidReadReport
* USE: Write to a report
* PARAMS: hidId - HID device handle
* reportId - the report we want to write
* RETURN: true on success
* NOTES:
*/
bool btleHidReadReport(ble_hid_conn_t hidId, int32_t reportId)
{
struct HidConn *hid;
pthread_mutex_lock(&mHidLock);
hid = hidFindConnById(hidId);
if (!hid)
logw("Attempting to read from nonexistent HID device\n");
else if (hid->state != BTLE_HID_CONN_STATE_UP)
logw("Attempting to read from HID device in state %u\n", hid->state);
else {
struct ReportsListNode* node;
uint16_t handle = 0;
if (reportId == BTLE_HID_REPORT_ID_BOOT_KEYBOARD_IN && hid->bootKbdInput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_READ))
handle = hid->bootKbdInput.valHandle;
else if (reportId == BTLE_HID_REPORT_ID_BOOT_KEYBOARD_OUT && hid->bootKbdOutput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_READ))
handle = hid->bootKbdOutput.valHandle;
else if (reportId == BTLE_HID_REPORT_ID_BOOT_MOUSE_IN && hid->bootMouseInput.charHandle && (hid->bootKbdInput.charProps & GATT_PROP_READ))
handle = hid->bootMouseInput.valHandle;
else if (reportId >= 0 && (node = btleHidGetReportListNodeByReportId(hid, reportId)) && (node->chr.charProps & GATT_PROP_READ))
handle = node->chr.valHandle;
else
logw("Read from report %d is permanently impossible\n", reportId);
if (handle) {
gatt_client_conn_t conn = hid->gattConn;
pthread_mutex_unlock(&mHidLock);
return GATT_CLI_STATUS_OK == gattClientRead(conn, handle, 0, 0, 0, btleHidReadReportReadCbk);
}
}
pthread_mutex_unlock(&mHidLock);
return false;
}