blob: 230a44c0867039360093e578a5dee238bc31665c [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 "gattSvcMiscInfo.h"
#include "gatt.h"
#include "util.h"
#include "log.h"
#include "mt.h"
//16-bit UUID(s) for Device Info Profile
#define BTLE_UUID_MISC_INFO_SVC_CHAR_MANUF_NAME_STR 0x2A29 // org.bluetooth.characteristic.manufacturer_name_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_MODEL_NUM_STR 0x2A24 // org.bluetooth.characteristic.model_number_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_SERIAL_NUM_STR 0x2A25 // org.bluetooth.characteristic.serial_number_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_HW_REV_STR 0x2A27 // org.bluetooth.characteristic.hardware_revision_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_FW_REV_STR 0x2A26 // org.bluetooth.characteristic.firmware_revision_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_SW_REV_STR 0x2A28 // org.bluetooth.characteristic.hasoftware_revision_string
#define BTLE_UUID_MISC_INFO_SVC_CHAR_SYSTEM_ID 0x2A23 // org.bluetooth.characteristic.system_id
#define BTLE_UUID_MISC_INFO_SVC_CHAR_IEEE_CERT_LIST 0x2A2A // org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list
#define BTLE_UUID_MISC_INFO_SVC_CHAR_PNP_ID 0x2A50 // org.bluetooth.characteristic.pnp_id
#define BTLE_UUID_MISC_INFO_SVC_CHAR_DEV_NAME 0x2A00 // org.bluetooth.characteristic.gap.device_name
#define BTLE_UUID_MISC_INFO_SVC_CHAR_APPEARANCE 0x2A01 // org.bluetooth.characteristic.gap.appearance
static const uint16_t mInfoTypeToUuidMap[] = {
[GattMiscInfoTypeManufNameString] = BTLE_UUID_MISC_INFO_SVC_CHAR_MANUF_NAME_STR,
[GattMiscInfoTypeModelNumberString] = BTLE_UUID_MISC_INFO_SVC_CHAR_MODEL_NUM_STR,
[GattMiscInfoTypeSerialNumberString] = BTLE_UUID_MISC_INFO_SVC_CHAR_SERIAL_NUM_STR,
[GattMiscInfoTypeHardwareRevisionString] = BTLE_UUID_MISC_INFO_SVC_CHAR_HW_REV_STR,
[GattMiscInfoTypeFirmwareRevisionString] = BTLE_UUID_MISC_INFO_SVC_CHAR_FW_REV_STR,
[GattMiscInfoTypeSoftwareRevisionString] = BTLE_UUID_MISC_INFO_SVC_CHAR_SW_REV_STR,
[GattMiscInfoTypeSystemID] = BTLE_UUID_MISC_INFO_SVC_CHAR_SYSTEM_ID,
[GattMiscInfoTypeIeee11073_20601regCertList] = BTLE_UUID_MISC_INFO_SVC_CHAR_IEEE_CERT_LIST,
[GattMiscInfoTypePnpID] = BTLE_UUID_MISC_INFO_SVC_CHAR_PNP_ID,
[GattMiscInfoTypeDeviceName] = BTLE_UUID_MISC_INFO_SVC_CHAR_DEV_NAME,
[GattMiscInfoTypeAppearance] = BTLE_UUID_MISC_INFO_SVC_CHAR_APPEARANCE,
};
static const bool mInfoTypeToIsInGap[] = {
[GattMiscInfoTypeDeviceName] = true,
[GattMiscInfoTypeAppearance] = true,
};
struct MiscInfoSvcConn {
struct MiscInfoSvcConn *prev;
struct MiscInfoSvcConn *next;
gatt_svc_misc_info_conn_t id;
gatt_client_conn_t gattConn;
uint8_t state;
bool enummedGap, enummedDevInfo;
uint16_t valHandles[GattMiscInfoEnumMax];
sg valVals[GattMiscInfoEnumMax];
};
static GattSvcMiscInfoConnStateCbk mCbkState;
static GattSvcMiscInfoResultCbk mCbkRx;
static pthread_mutex_t mMiscInfoSvcLock = PTHREAD_MUTEX_INITIALIZER;
static struct MiscInfoSvcConn *mMiscInfoSvcDevs = NULL;
/*
* FUNCTION: gattSvcMiscInfoTypeToUuid
* USE: map an info type to a UUID16
* PARAMS: type - the info type
* RETURN: uuid16 or 0 on error
* NOTES:
*/
static uint16_t gattSvcMiscInfoTypeToUuid(enum GattSvcMiscInfoType type)
{
return type < sizeof(mInfoTypeToUuidMap) / sizeof(*mInfoTypeToUuidMap) ? mInfoTypeToUuidMap[type] : 0;
}
/*
* FUNCTION: gattSvcMiscInfoUuidToType
* USE: map a UUID16 to info type
* PARAMS: uuid16 - the uuid16
* RETURN: info type or GattMiscInfoTypeInvalid
* NOTES:
*/
static enum GattSvcMiscInfoType gattSvcMiscInfoUuidToType(uint16_t uuid16)
{
uint32_t i;
for (i = 0; i < sizeof(mInfoTypeToUuidMap) / sizeof(*mInfoTypeToUuidMap); i++) {
if (mInfoTypeToUuidMap[i] == uuid16)
return i;
}
return GattMiscInfoTypeInvalid;
}
/*
* FUNCTION: gattSvcMiscInfoIsTypeInGap
* USE: check if a given info type is in the GAP service (else it is in device info service)
* PARAMS: type - the info type
* RETURN: true if it is, false if it is not or we do not know of this info type
* NOTES:
*/
static bool gattSvcMiscInfoIsTypeInGap(enum GattSvcMiscInfoType type)
{
return type < sizeof(mInfoTypeToIsInGap) / sizeof(*mInfoTypeToIsInGap) && mInfoTypeToIsInGap[type];
}
/*
* FUNCTION: gattSvcMiscInfoFindConnById
* USE: find a MiscInfoSvcConn struct given its id
* PARAMS: id - the id to find
* RETURN: the structure or NULL if not found
* NOTES: call with mMiscInfoSvcLock held
*/
static struct MiscInfoSvcConn *gattSvcMiscInfoFindConnById(gatt_svc_misc_info_conn_t id)
{
struct MiscInfoSvcConn *c;
for (c = mMiscInfoSvcDevs; c && c->id != id; c = c->next);
return c;
}
/*
* FUNCTION: gattSvcMiscInfoFindConnByGattConn
* USE: find a MiscInfoSvcConn struct given a gatt connection
* PARAMS: gattConn - the gatt connection to find
* RETURN: the structure or NULL if not found
* NOTES: call with mMiscInfoSvcLock held
*/
static struct MiscInfoSvcConn *gattSvcMiscInfoFindConnByGattConn(gatt_client_conn_t gattConn)
{
struct MiscInfoSvcConn *c;
for (c = mMiscInfoSvcDevs; c && c->gattConn != gattConn; c = c->next);
return c;
}
/*
* FUNCTION: gattSvcMiscInfoDetachWithDev
* USE: Detach from a given Misc Info Service given its connection structure
* PARAMS: svc - the connection structure
* RETURN: success
* NOTES: call with mMiscInfoSvcLock held
*/
static bool gattSvcMiscInfoDetachWithDev(struct MiscInfoSvcConn *svc)
{
uint32_t i;
if (svc->next)
svc->next->prev = svc->prev;
if (svc->prev)
svc->prev->next = svc->next;
else
mMiscInfoSvcDevs = svc->next;
for (i = 0; i < GattMiscInfoEnumMax; i++) {
if (!svc->valVals[i])
sgFree(svc->valVals[i]);
}
free(svc);
return true;
}
/*
* FUNCTION: gattSvcMiscInfoDetach
* USE: Detach from a Misc Info Service
* PARAMS: handle - MiscInfoSvc connection handle
* RETURN: the structure or NULL if not found
* NOTES:
*/
bool gattSvcMiscInfoDetach(gatt_svc_misc_info_conn_t handle)
{
struct MiscInfoSvcConn *svc;
bool ret = false;
pthread_mutex_lock(&mMiscInfoSvcLock);
svc = gattSvcMiscInfoFindConnById(handle);
if (svc)
ret = gattSvcMiscInfoDetachWithDev(svc);
else {
handle = 0;
logw("Attempting to detach Misc Info Service from a nonexistent connection\n");
}
pthread_mutex_unlock(&mMiscInfoSvcLock);
if (handle)
mCbkState(handle, BTLE_MISC_INFO_SVC_CONN_STATE_TEARDOWN);
return ret;
}
/*
* FUNCTION: gattSvcMiscInfoDetachFromGatt
* USE: Detach from a GATT connection
* PARAMS: gattConn - GATT connection
* RETURN: the structure or NULL if not found
* NOTES:
*/
bool gattSvcMiscInfoDetachFromGatt(gatt_client_conn_t gattConn)
{
gatt_svc_misc_info_conn_t id = 0;
struct MiscInfoSvcConn *svc;
bool ret = false;
pthread_mutex_lock(&mMiscInfoSvcLock);
svc = gattSvcMiscInfoFindConnByGattConn(gattConn);
if (svc) {
ret = gattSvcMiscInfoDetachWithDev(svc);
id = svc->id;
}
else
logw("Attempting to detach Misc Info Service from an unknown GATT connection\n");
pthread_mutex_unlock(&mMiscInfoSvcLock);
if (id)
mCbkState(id, BTLE_MISC_INFO_SVC_CONN_STATE_TEARDOWN);
return ret;
}
/*
* FUNCTION: gattSvcMiscInfoReadCbk
* 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)
* data - the data or NULL on error
* RETURN: NONE
* NOTES:
*/
static void gattSvcMiscInfoReadCbk(gatt_client_conn_t gattConn, uniq_t trans, sg data)
{
enum GattSvcMiscInfoType type = trans;
struct MiscInfoSvcConn *svc;
if (!gattSvcMiscInfoTypeToUuid(type)) {
logw("Got a read for an invalid type\n");
if (data)
sgFree(data);
return;
}
pthread_mutex_lock(&mMiscInfoSvcLock);
svc = gattSvcMiscInfoFindConnByGattConn(gattConn);
if (!svc) {
logw("No MiscInfoSvc instance found for GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (svc->state != BTLE_MISC_INFO_SVC_CONN_STATE_UP) {
logi("Dropping RXed data for connection that is not in UP state\n");
goto out_unlock;
}
if (svc->valVals[type] && data) {
logi("Got a duplicate read data for type %u - replacing\n", type);
sgFree(svc->valVals[type]);
}
svc->valVals[type] = data;
data = sgDup(data);
pthread_mutex_unlock(&mMiscInfoSvcLock);
mCbkRx(gattConn, type, data);
return;
out_unlock:
pthread_mutex_unlock(&mMiscInfoSvcLock);
sgFree(data);
}
/*
* FUNCTION: gattSvcMiscInfoAttachServiceTraversalCbk
* USE: Called by GATT when the Misc Info Service is found at request of gattSvcMiscInfoAttach()
* PARAMS: gattConn - the GATT client connection id
* trans - transaction ID (unused here as we can look up by MiscInfoSvc conn handle)
* data - the traversal data or NULL on error
* RETURN: NONE
* NOTES:
*/
static void gattSvcMiscInfoAttachServiceTraversalCbk(gatt_client_conn_t gattConn, uniq_t trans, const struct GattTraversedService* data)
{
const struct uuid devInfoSvcUuid = BT_UUID_STATIC_16(BTLE_UUID_DEVICE_INFO_SERVICE);
const struct uuid gapSvcUuid = BT_UUID_STATIC_16(BTLE_UUID_GAP_SERVICE);
gatt_svc_misc_info_conn_t id;
struct MiscInfoSvcConn *svc;
bool isGap = false;
uint32_t i;
pthread_mutex_lock(&mMiscInfoSvcLock);
svc = gattSvcMiscInfoFindConnByGattConn(gattConn);
if (!svc) {
logw("Unexpected GATT service traversal callback for unknown-to-MiscInfoSvc connection "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
goto out_unlock;
}
if (svc->state != BTLE_MISC_INFO_SVC_CONN_STATE_GATT_DISCOVERY) {
logw("Unxpected state %u when getting GATT discovery results for MiscInfoSvc\n", svc->state);
goto out_detach;
}
if (trans == BTLE_UUID_DEVICE_INFO_SERVICE) {
if (svc->enummedDevInfo) {
logw("Unxpected duplicate DevInfo svc enum when getting GATT discovery results for MiscInfoSvc\n");
goto out_detach;
}
svc->enummedDevInfo = true;
if (!data || !uuidCmp(&data->uuid, &devInfoSvcUuid)) {
logi("Device has no DeviceInfo service\n");
goto out_unlock;
}
}
else if (trans == BTLE_UUID_GAP_SERVICE) {
if (svc->enummedGap) {
logw("Unxpected duplicate GAP svc enum when getting GATT discovery results for MiscInfoSvc\n");
goto out_detach;
}
svc->enummedGap = true;
isGap = true;
if (!data || !uuidCmp(&data->uuid, &gapSvcUuid)) {
logi("Device has no GAP service - this is spec violation!\n");
goto out_unlock;
}
}
else {
logw("Unxpected trans ID when getting GATT discovery results for MiscInfoSvc\n");
goto out_detach;
}
// record characteristics we care about
for (i = 0; i < data->numChars; i++) {
enum GattSvcMiscInfoType type;
uint16_t uuid16;
if (!uuidToUuid16(&uuid16, &data->chars[i].uuid)) { // all the UUIDs we care for are UUID16s
logd("Ignoring attr with uuid128 "UUIDFMT"\n", UUIDCONV(data->chars[i].uuid));
continue;
}
type = gattSvcMiscInfoUuidToType(uuid16);
if (type == GattMiscInfoTypeInvalid) {
logd("Ignoring attr with uninteresting UUID 0x%04x\n", uuid16);
continue;
}
logd("Mapping uuid 0x%04x to info type %u\n", uuid16, type);
if ((gattSvcMiscInfoIsTypeInGap(type) && !isGap) || (!gattSvcMiscInfoIsTypeInGap(type) && isGap)) {
logw("UUID 0x%04x found %s GAP, while expected %s GAP - ignoring\n", uuid16, isGap ? "in" : "out of", isGap ? "out of" : "in");
continue;
}
if (svc->valHandles[type]) {
logw("Ignoring duplicate attr UUID 0x%04x (handles 0x%04x and 0x%04x)\n", uuid16, svc->valHandles[type], data->chars[i].valHandle);
continue;
}
svc->valHandles[type] = data->chars[i].valHandle;
}
// see if we're ready to go to UP state (all enums done), else just out and wait smoe more
if (!svc->enummedDevInfo || !svc->enummedGap)
goto out_unlock;
// go to UP stage
id = svc->id;
svc->state = BTLE_MISC_INFO_SVC_CONN_STATE_UP;
pthread_mutex_unlock(&mMiscInfoSvcLock);
mCbkState(id, BTLE_MISC_INFO_SVC_CONN_STATE_UP);
return;
out_detach:
if (!gattSvcMiscInfoDetachWithDev(svc))
loge("Failed to detach on error in service search!\n");
out_unlock:
pthread_mutex_unlock(&mMiscInfoSvcLock);
}
/*
* FUNCTION: gattSvcMiscInfoAttach
* USE: Attach Misc Info Service to a GATT connection
* PARAMS: gattConn - a gatt client connection
* RETURN: MiscInfoSvc connection handle or zero on immediate failure
* NOTES: expect state callback with details
*/
gatt_svc_misc_info_conn_t gattSvcMiscInfoAttach(gatt_client_conn_t gattConn)
{
const struct uuid devInfoSvcUuid = BT_UUID_STATIC_16(BTLE_UUID_DEVICE_INFO_SERVICE);
const struct uuid gapSvcUuid = BT_UUID_STATIC_16(BTLE_UUID_GAP_SERVICE);
gatt_svc_misc_info_conn_t svcConn = 0;
struct MiscInfoSvcConn *svc;
svc = calloc(1, sizeof(struct MiscInfoSvcConn));
if (!svc)
return 0;
pthread_mutex_lock(&mMiscInfoSvcLock);
if (gattSvcMiscInfoFindConnByGattConn(gattConn)) {
logw("Misc Info Service is already attached to this GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
pthread_mutex_unlock(&mMiscInfoSvcLock);
free(svc);
return 0;
}
svc->id = svcConn = uniqGetNext();
svc->gattConn = gattConn;
svc->state = BTLE_MISC_INFO_SVC_CONN_STATE_GATT_DISCOVERY;
svc->next = mMiscInfoSvcDevs;
if (mMiscInfoSvcDevs)
mMiscInfoSvcDevs->prev = svc;
mMiscInfoSvcDevs = svc;
pthread_mutex_unlock(&mMiscInfoSvcLock);
mCbkState(svcConn, BTLE_MISC_INFO_SVC_CONN_STATE_GATT_DISCOVERY);
logi("Misc Info Service attempting an attachment to GATT conn "GATTHANDLEFMT"\n", GATTHANDLECNV(gattConn));
if (GATT_CLI_STATUS_OK != gattClientUtilFindAndTraversePrimaryService(gattConn, &devInfoSvcUuid, (uniq_t)BTLE_UUID_DEVICE_INFO_SERVICE, gattSvcMiscInfoAttachServiceTraversalCbk))
goto fail;
if (GATT_CLI_STATUS_OK != gattClientUtilFindAndTraversePrimaryService(gattConn, &gapSvcUuid, (uniq_t)BTLE_UUID_GAP_SERVICE, gattSvcMiscInfoAttachServiceTraversalCbk))
goto fail;
// no failure so far...
return svcConn;
fail:
logw("Failed to start the GATT service search\n");
if (!gattSvcMiscInfoDetachFromGatt(svcConn))
loge("Failed to detach on error on attach!\n");
return 0;
}
/*
* FUNCTION: gattSvcMiscInfoRequestRead
* USE: Request an info reading
* PARAMS: handle - MiscInfoSvc connection handle
* type - info type to read
* RETURN: false on immediate error, else wait for GattSvcMiscInfoResultCbk() cbk
* NOTES: cbk MAY BE CALLED EVEN BEFORE THIS FUNC RETURNS!
*/
bool gattSvcMiscInfoRequestRead(gatt_svc_misc_info_conn_t handle, enum GattSvcMiscInfoType type)
{
struct MiscInfoSvcConn *svc;
if (!gattSvcMiscInfoTypeToUuid(type)) {
logw("Attempting to get MiscInfoSvc into type that is not known: %u\n", type);
return false;
}
pthread_mutex_lock(&mMiscInfoSvcLock);
svc = gattSvcMiscInfoFindConnById(handle);
if (!svc)
logw("Attempting to get MiscInfoSvc level from a nonexistent MiscInfoSvc instance\n");
else if (svc->state != BTLE_MISC_INFO_SVC_CONN_STATE_UP)
logw("Attempting to get MiscInfoSvc level from a MiscInfoSvc instance in state %u\n", svc->state);
else {
gatt_client_conn_t conn = svc->gattConn;
uint16_t handle = svc->valHandles[type];
sg val = svc->valVals[type];
logd("MiscInfo: requested type %u. Handle=0x%04x, valCache=%p\n", type, handle, val);
if (val)
val = sgDup(val);
pthread_mutex_unlock(&mMiscInfoSvcLock);
if (!handle) // no such attribute - tell caller right away
return false;
else if (!val) // no cache? schedule a read
return GATT_CLI_STATUS_OK == gattClientUtilLongRead(conn, handle, 0, type, gattSvcMiscInfoReadCbk);
else {
mCbkRx(handle, type, val);
return true;
}
}
pthread_mutex_unlock(&mMiscInfoSvcLock);
return false;
}
/*
* FUNCTION: gattSvcMiscInfoInit
* USE: Init MiscInfoSvc 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 gattSvcMiscInfoInit(GattSvcMiscInfoConnStateCbk stateCbk, GattSvcMiscInfoResultCbk rxCbk)
{
mCbkState = stateCbk;
mCbkRx = rxCbk;
}