blob: 5f99d232cec5ccb36f4936ca3a457f50fa8a9cd6 [file] [log] [blame]
#include <string.h>
#include "gatt-builtin.h"
#include "l2cap.h"
#include "gatt.h"
#include "util.h"
#include "uuid.h"
#include "att.h"
#include "hci.h"
#include "log.h"
#include "mt.h"
/* services */
static gatt_service_t mSvcGatt = 0;
static gatt_service_t mSvcGap = 0;
/* handles */
static uint16_t mSvcGapDevName;
static uint16_t mSvcGapAppearance;
static uint16_t mSvcGapPrefConnParams;
/* data */
static pthread_mutex_t mGattBuiltinLock = PTHREAD_MUTEX_INITIALIZER;
static char mGapName[HCI_DEV_NAME_LEN + 1] = {0,};
static uint16_t mGapAppearance;
static struct {
uint16_t intMin;
uint16_t intMax;
uint16_t lat;
uint16_t timeout;
} __packed mGapPrefConnParams;
#define GATT_UUID_GAP_DEV_NAME 0x2A00
#define GATT_UUID_GAP_APPEARANCE 0x2A01
#define GATT_UUID_GAP_PREFERRED_CONN_PARAMS 0x2A04
/*
* FUNCTION: gattBuiltinReadF
* USE: GATT's callback for reading
* PARAMS: svc - service ref
* who - connection to whom this pertains to
* cid - ATT's connection ID
* transId - ATT's transaction ID
* handle - the handle that this pertains to
* byteOfst - the offset into the characteristic for the read (in bytes)
* reason - reason for the write (ATT_READ_FOR_*)
* wantedLen - the length to write
* RETURN: NONE
* NOTES: call gattSrvCbkReply() with results. returning false will send ATT_ERROR_UNLIKELY_ERROR to the client
*/
static bool gattBuiltinReadF(gatt_service_t svc, l2c_handle_t who, att_cid_t cid, att_trans_t transId, uint16_t handle, uint16_t byteOfst, uint8_t reason, uint16_t wantedLen)
{
uint8_t err = ATT_ERROR_NONE;
const uint8_t *ptr = NULL;
uint16_t len = 0;
if (svc != mSvcGap) {
logw("Unexpected read in non-GAP service\n");
return false;
}
if (handle == mSvcGapDevName) {
ptr = (const uint8_t*)mGapName;
len = strlen(mGapName);
} else if (handle == mSvcGapAppearance) {
ptr = (const uint8_t*)&mGapAppearance;
len = sizeof(mGapAppearance);
} else if (handle == mSvcGapPrefConnParams) {
ptr = (const uint8_t*)&mGapPrefConnParams;
len = sizeof(mGapPrefConnParams);
} else {
logw("Unexpected read from handle %u of svc "GATTHANDLEFMT" from conn "HANDLEFMT"\n", handle, GATTHANDLECNV(svc), HANDLECNV(who));
return false;
}
if (byteOfst > len)
err = ATT_ERROR_INVALID_OFFSET;
else {
ptr += byteOfst;
len -= byteOfst;
}
if (len > wantedLen)
len = wantedLen;
gattSrvCbkReply(cid, transId, handle, err, ptr, len);
return true;
}
/*
* FUNCTION: gattBuiltinWriteF
* USE: GATT's callback for writing
* PARAMS: svc - service ref
* who - connection to whom this pertains to
* cid - ATT's connection ID
* transId - ATT's transaction ID
* handle - the handle that this pertains to
* byteOfst - the offset into the characteristic for the write (in bytes)
* reason - reason for the write (ATT_WRITE_FOR_*)
* len - the length to write
* data - the data to write
* RETURN: NONE
* NOTES: this callback is unused here
*/
static bool gattBuiltinWriteF(gatt_service_t svc, l2c_handle_t who, att_cid_t cid, att_trans_t transId, uint16_t handle, uint16_t byteOfst, uint8_t reason, uint16_t len, const void *data)
{
/* in reality we can never get here since GATT & ATT enforce for us the read-only-ness of other handles */
logw("Unexpected write to handle %u of svc "GATTHANDLEFMT" from conn "HANDLEFMT"\n", handle, GATTHANDLECNV(svc), HANDLECNV(who));
return false;
}
/*
* FUNCTION: gattBuiltinIndF
* USE: GATT's callback for indication/notification progress
* PARAMS: svc - service ref
* who - connection to whom this pertains to
* cid - ATT's connection ID
* handle - the handle that this pertains to
* evt - what happened (GATT_SRV_EVT_*)
* ref - value originally passed to gattServiceSendInd()
* RETURN: NONE
* NOTES: this callback is unused here
*/
static void gattBuiltinIndF(gatt_service_t svc, l2c_handle_t who, att_cid_t cid, uint16_t handle, uint8_t evt, uint64_t ref) /* GATT_SRV_EVT_* */
{
logw("Unexpected ind notification for %u of svc "GATTHANDLEFMT" from conn "HANDLEFMT"\n", handle, GATTHANDLECNV(svc), HANDLECNV(who));
return;
}
/*
* FUNCTION: gattBuiltinDiscF
* USE: GATT's callback for connection closure
* PARAMS: svc - service ref
* who - connection to whom this pertains to
* RETURN: NONE
* NOTES: this callback is unused here
*/
static void gattBuiltinDiscF(gatt_service_t svc, l2c_handle_t who)
{
return;
}
/*
* FUNCTION: gattBuiltinInit
* USE: Init the gatt builtin profiles
* PARAMS: NONE
* RETURN: success
* NOTES:
*/
bool gattBuiltinInit(void)
{
struct uuid uuid;
/* make "GATT" service */
uuidFromUuid16(&uuid, GATT_UUID_SVC_GATT_SVR);
mSvcGatt = gattServiceCreate(&uuid, true, 1, gattBuiltinReadF, gattBuiltinWriteF, gattBuiltinIndF, gattBuiltinDiscF);
if (!mSvcGatt) {
loge("Failed to register GATT.GATT\n");
goto out_fail;
}
/* make "GAP" service */
uuidFromUuid16(&uuid, GATT_UUID_SVC_GAP);
mSvcGap = gattServiceCreate(&uuid, true, 7, gattBuiltinReadF, gattBuiltinWriteF, gattBuiltinIndF, gattBuiltinDiscF);
if (!mSvcGap) {
loge("Failed to register GATT.GAP\n");
goto out_gatt_destroy;
}
/* add the "Device Name" characteristic */
uuidFromUuid16(&uuid, GATT_UUID_GAP_DEV_NAME);
mSvcGapDevName = gattServiceAddChar(mSvcGap, &uuid, GATT_PROP_READ, GATT_PERM_ALL_READ);
if (!mSvcGapDevName) {
loge("Failed to add GATT.GAP.DevName char\n");
goto out_gap_destroy;
}
/* add the "Appearance" characteristic */
uuidFromUuid16(&uuid, GATT_UUID_GAP_APPEARANCE);
mSvcGapAppearance = gattServiceAddChar(mSvcGap, &uuid, GATT_PROP_READ, GATT_PERM_ALL_READ);
if (!mSvcGapAppearance) {
loge("Failed to add GATT.GAP.Appearance char\n");
goto out_gap_destroy;
}
/* add the "Preferred connection params" characteristic */
uuidFromUuid16(&uuid, GATT_UUID_GAP_PREFERRED_CONN_PARAMS);
mSvcGapPrefConnParams = gattServiceAddChar(mSvcGap, &uuid, GATT_PROP_READ, GATT_PERM_ALL_READ);
if (!mSvcGapPrefConnParams) {
loge("Failed to add GATT.GAP.PrefConnParams char\n");
goto out_gap_destroy;
};
if (!gattServiceStart(mSvcGatt, true, true)) {
loge("Failed to start GATT.GATT\n");
goto out_gap_destroy;
}
if (!gattServiceStart(mSvcGap, true, true)) {
loge("Failed to start GATT.GAP\n");
goto out_gatt_stop;
}
logd("GATT.GATT created as svc "GATTHANDLEFMT" at handle 0x%04X\n", GATTHANDLECNV(mSvcGatt), gattServiceGetHandleBaseBySvc(mSvcGatt));
logd("GATT.GAP created as svc "GATTHANDLEFMT" at handle 0x%04X\n", GATTHANDLECNV(mSvcGap), gattServiceGetHandleBaseBySvc(mSvcGap));
return true;
out_gatt_stop:
gattServiceStop(mSvcGatt);
out_gap_destroy:
gattServiceDestroy(mSvcGap);
out_gatt_destroy:
gattServiceDestroy(mSvcGatt);
out_fail:
return false;
}
/*
* FUNCTION: gattBuiltinDeinit
* USE: Deinit the gatt builtin profiles
* PARAMS: NONE
* RETURN: NONE
* NOTES:
*/
void gattBuiltinDeinit(void)
{
gattServiceStop(mSvcGap);
gattServiceStop(mSvcGatt);
gattServiceDestroy(mSvcGap);
gattServiceDestroy(mSvcGatt);
mSvcGatt = mSvcGap = 0;
}
/*
* FUNCTION: gattBuiltinSetDevName
* USE: Set device name visible on GAP
* PARAMS: name - the bt name in the usual format (HCI_DEV_NAME_LEN chars, NULL terminated unless exactly HCI_DEV_NAME_LEN chars long)
* RETURN: NONE
* NOTES: can be called before gattBuiltinInit or after gattBuiltinDeinit. Must handle that!
*/
void gattBuiltinSetDevName(const char *name)
{
uint8_t len = strnlen(name, HCI_DEV_NAME_LEN);
pthread_mutex_lock(&mGattBuiltinLock);
memcpy(mGapName, name, len);
mGapName[len] = 0;
pthread_mutex_unlock(&mGattBuiltinLock);
}
/*
* FUNCTION: gattBuiltinSetPreferredConnParamsWhenSlave
* USE: Set preferred conenction params visible on GAP
* PARAMS: minInt - preferred minimum interval
* maxInt - preferred max interval
* lat - preferred latency
* to - preferred timeout
* RETURN: NONE
* NOTES: units for all params are standard LE connection units/multiples
* can be called before gattBuiltinInit or after gattBuiltinDeinit. Must handle that!
*/
void gattBuiltinSetPreferredConnParamsWhenSlave(uint16_t minInt, uint16_t maxInt, uint16_t lat, uint16_t to)
{
pthread_mutex_lock(&mGattBuiltinLock);
utilSetLE16(&mGapPrefConnParams.intMin, minInt);
utilSetLE16(&mGapPrefConnParams.intMax, maxInt);
utilSetLE16(&mGapPrefConnParams.lat, lat);
utilSetLE16(&mGapPrefConnParams.timeout, to);
pthread_mutex_unlock(&mGattBuiltinLock);
}
/*
* FUNCTION: gattBuiltinSetAppearance
* USE: Set "device appearance" as visible on GAP
* PARAMS: appearance - GAP_APPEAR_*
* RETURN: NONE
* NOTES: can be called before gattBuiltinInit or after gattBuiltinDeinit. Must handle that!
*/
void gattBuiltinSetAppearance(uint16_t appearance)
{
pthread_mutex_lock(&mGattBuiltinLock);
utilSetLE16(&mGapAppearance, appearance);
pthread_mutex_unlock(&mGattBuiltinLock);
}