blob: 8282c2869ec58d1888c3d992ac0e80ca2a8606f9 [file] [log] [blame]
/*
* Copyright 2017 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <regex.h>
#include "newblue-macros.h"
#include "gattSvcMiscInfo.h"
#include "gattSvcBattery.h"
#include "gatt-builtin.h"
#include "vendorLib.h"
#include "btleHid.h"
#include "l2cap.h"
#include "timer.h"
#include "aapi.h"
#include "util.h"
#include "uuid.h"
#include "gatt.h"
#include "att.h"
#include "hci.h"
#include "log.h"
#include "bt.h"
#include "mt.h"
struct LeDiscDev {
struct LeDiscDev *next;
uint8_t mac[BT_MAC_LEN];
bool randomAddr;
bool haveRsp;
int8_t rssi;
uint8_t advType;
uint8_t advLen;
uint8_t rspLen;
uint8_t advData[31];
uint8_t rspData[31];
};
struct EdrDiscDev {
struct EdrDiscDev *next;
uint8_t mac[BT_MAC_LEN];
uint32_t devCls;
int8_t rssi;
bool haveEir;
bool haveName;
uint8_t eir[240];
char name[249];
};
struct ServiceList {
struct ServiceList* next;
uint16_t start, end;
struct uuid uuid;
};
static sem_t mGattTestSem;
static struct ServiceList *mGattSvcs = NULL;
static ble_hid_conn_t mHidConn = 0;
static gatt_svc_batt_conn_t mBattSvcCon = 0;
static gatt_svc_misc_info_conn_t mMiscInfoSvcCon = 0;
static const char* getAppearanceTypeStr(uint32_t appVal);
static const char* getAppearanceSubtypeStr(uint32_t appVal);
static void testHciScoRxCbk(hci_conn_t aclConn, const void *data, uint8_t len, uint8_t lossAmount)
{
static const char *lossAmtStr[] = {"none", "some", "unknown", "all"};
logi("TEST SCO RX %ub, loss: %s\n", len, lossAmtStr[lossAmount]);
}
static void leDiscoveryCbk(void *cbkData, const struct bt_addr *addr, int8_t rssi, uint8_t replyType, const void *eir, uint8_t eirLen)
{
struct LeDiscDev **leDevicesP = (struct LeDiscDev**)cbkData, *t;
char randomAddr = addr->type == BT_ADDR_TYPE_LE_RANDOM;
/* see if this mac exists */
t = *leDevicesP;
while (t && (t->randomAddr != randomAddr || memcmp(t->mac, addr->addr, sizeof(t->mac))))
t = t->next;
if (!t) {// not found
t = (struct LeDiscDev*)calloc(1, sizeof(struct LeDiscDev));
if (!t)
return;
t->randomAddr = randomAddr;
memmove(t->mac, addr->addr, BT_MAC_LEN);
t->next = *leDevicesP;
*leDevicesP = t;
fprintf(stderr, ".");
}
t->rssi = rssi;
if (replyType != HCI_ADV_TYPE_SCAN_RSP) {
t->advType = replyType;
memcpy(t->advData, eir, eirLen);
t->advLen = eirLen;
} else {
if (!t->haveRsp)
fprintf(stderr, ",");
t->haveRsp = true;
memcpy(t->rspData, eir, eirLen);
t->rspLen = eirLen;
}
}
static void readyForUpCbk() {
logi("callback stack is ready for up\n");
}
#define DEVICE_CLASS_SERVICE_RENDERING 0x040000 //PRINTER, SPEAKER
#define DEVICE_CLASS_SERVICE_AUDIO 0x200000 //SPEAKER, MIC, HEADSET
#define DEVICE_CLASS_SERVICE_INFORMATION 0x800000 //WEB-server, WAP-server
#define DEVICE_CLASS_MAJOR_SHIFT 8
#define DEVICE_CLASS_MAJOR_AV 4
#define DEVICE_CLASS_MINOR_AV_SHIFT 2
#define DEVICE_CLASS_MINOR_AV_PORTBL_AUDIO 7
void logData(char* dst, const void *buf, uint8_t len)
{
const uint8_t *data = (const uint8_t*)buf;
unsigned i;
sprintf(dst, "(%ub):", len);
for (i = 0; i < len; i++)
sprintf(dst + strlen(dst), " %02X", data[i]);
}
static gatt_service_t mSvcTime = 0;
static att_cid_t mConnTime = 0;
static bool stopTimeService = false;
uint16_t mGattHandleCurTime = 0;
uint16_t mGattHandleCurTimeCfg = 0;
uint16_t mGattHandleLocalTimeInfo = 0;
uint16_t mGattHandleReferenceTimeInfo = 0;
static uniq_t terminateTimerId = 0;
static bool terminate = false;
static hci_adv_set_t mAdvSet[3] = {0,};
static struct bt_addr mAddrs[3];
static struct bt_addr mInputDevAddr;
uniq_t mPairStateObserverId = 0;
uniq_t mPasskeyDisplayObserverId = 0;
uniq_t mPasskeyRequestObserverId = 0;
struct dateTime {
uint16_t year; // used in LE service with little endian.
uint8_t month;
uint8_t day;
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
} __packed;
struct dayOfWeek {
uint8_t dayOfWeek;
} __packed;
struct dayDateTime {
struct dateTime dateTime;
struct dayOfWeek dayOfWeek;
} __packed;
struct exactTime256 {
struct dayDateTime dayDateTime;
uint8_t fractions256;
} __packed;
struct {
struct exactTime256 exactTime256;
uint8_t adjustReason;
}__packed mGattCurTime;
struct {
uint16_t cCC; // Client Characteristic Configuration descriptor
}__packed mGattCurTimeCfg;
struct {
int8_t timeZone; // offset from UTC in 15-min increments
uint8_t dstOffset; // daylight saving time offset to add
}__packed mGattLocalTimeInfo;
bool isSameAddr(const struct bt_addr *a, const struct bt_addr *b) {
if (!a || !b)
return false;
return !memcmp(a, b, sizeof(struct bt_addr));
}
static bool testGattSrvValueReadCbk(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, i;
const uint8_t *ptr = NULL;
uint16_t len = 0;
struct bt_addr srvAddr;
if (svc != mSvcTime) {
logw("Unexpected read in Current Time service\n");
return false;
}
if (handle == mGattHandleCurTime) {
ptr = (const uint8_t *)&mGattCurTime;
len = sizeof(mGattCurTime);
} else if (handle == mGattHandleCurTimeCfg) {
ptr = (const uint8_t *)&mGattCurTimeCfg;
len = sizeof(mGattCurTimeCfg);
} else if (handle == mGattHandleLocalTimeInfo) {
ptr = (const uint8_t *)&mGattLocalTimeInfo;
len = sizeof(mGattLocalTimeInfo);
} else if (handle == mGattHandleReferenceTimeInfo) {
ptr = (const uint8_t *)&mGattHandleReferenceTimeInfo;
len = sizeof(mGattHandleReferenceTimeInfo);
} else {
logw("Unexpected read from handle %u of svc "GATTHANDLEFMT" from conn "HANDLEFMT"d\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;
logi("testGattSrvValueReadCbk: cid: "UNIQ_FMT"d, transId: "ATT_TRANS_FMT"d, handle: handle: %d\n", ATT_CID_CONV(cid), ATT_TRANS_CONV(transId), handle);
/* In order to verify the pairing process, ATT_ERROR_INSUFFICIENT_AUTH is
* returned intentionally so that the remote GATT client will initiate
* pairing. */
gattSrvCbkReply(cid, transId, handle, ATT_ERROR_INSUFFICIENT_AUTH, ptr, len);
/* In order to verify the re-connection to the target device, we immediately
* re-enable the adv set */
if (l2cApiGetSelfBtAddr(attSrvCidResolve(cid), &srvAddr)) {
for (i = 0; i < sizeof(mAddrs); ++i) {
if (isSameAddr(&srvAddr, &mAddrs[i])) {
logd("Re-enable adv set %d\n", i);
hciAdvSetEnable(mAdvSet[i]);
}
}
}
return true;
}
static bool testGattSrvValueWriteCbk(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)
{
uint8_t err = ATT_ERROR_NONE, i;
struct bt_addr srvAddr;
logi("testGattSrvValueWriteCbk: cid: "UNIQ_FMT"d, transId: "ATT_TRANS_FMT"d, handle: handle: %d\n", ATT_CID_CONV(cid), ATT_TRANS_CONV(transId), handle);
if (handle == mGattHandleCurTimeCfg) {
mGattCurTimeCfg.cCC = *(const uint8_t *)data;
mConnTime = cid;
logi("data: %x, len: %d\n", mGattCurTimeCfg.cCC, len);
gattSrvCbkReply(cid, transId, handle, err, NULL, 0);
} else if (handle == mGattHandleLocalTimeInfo) {
memcpy(&mGattLocalTimeInfo, data, len);
gattSrvCbkReply(cid, transId, handle, err, NULL, 0);
} else {
loge("Unrecognized handle: %d\n", handle);
return false;
}
/* In order to verify the re-connection to the target device, we immediately
* re-enable the adv set */
if (l2cApiGetSelfBtAddr(attSrvCidResolve(cid), &srvAddr)) {
for (i = 0; i < sizeof(mAddrs); ++i) {
if (isSameAddr(&srvAddr, &mAddrs[i])) {
logd("Re-enable adv set %d\n", i);
hciAdvSetEnable(mAdvSet[i]);
}
}
}
return true;
}
static void testGattSrvIndCbk(gatt_service_t svc, l2c_handle_t who, att_cid_t cid, uint16_t handle, uint8_t evt, uint64_t ref)
{
int i;
struct bt_addr srvAddr;
if (svc != mSvcTime) {
logw("Unexpected notification callback in Current Time service\n");
return;
}
/* In order to verify the re-connection to the target device, we immediately
* re-enable the adv set */
if (l2cApiGetSelfBtAddr(attSrvCidResolve(cid), &srvAddr)) {
for (i = 0; i < sizeof(mAddrs); ++i) {
if (isSameAddr(&srvAddr, &mAddrs[i])) {
logd("Re-enable adv set %d\n", i);
hciAdvSetEnable(mAdvSet[i]);
}
}
}
logi("time notification sent: cid: "UNIQ_FMT"d, handle: handle: %d\n", ATT_CID_CONV(cid), handle);
}
static void testGattSrvDiscCbk(gatt_service_t svc, l2c_handle_t who)
{
//unused
}
static void testGattInitTime() {
mGattCurTime.exactTime256.dayDateTime.dateTime.year = 2017;
mGattCurTime.exactTime256.dayDateTime.dateTime.month = 5;
mGattCurTime.exactTime256.dayDateTime.dateTime.day = 1;
mGattCurTime.exactTime256.dayDateTime.dateTime.hours = 12;
mGattCurTime.exactTime256.dayDateTime.dateTime.minutes = 30;
mGattCurTime.exactTime256.dayDateTime.dateTime.seconds = 5;
mGattCurTime.adjustReason = 0;
mGattCurTime.exactTime256.fractions256 = 0;
mGattCurTime.exactTime256.dayDateTime.dayOfWeek.dayOfWeek = 3;
mGattCurTimeCfg.cCC = 0;
mGattLocalTimeInfo.timeZone = 32; // UTC+8 hours
mGattLocalTimeInfo.dstOffset = 4; // daylight time +1 hour
}
static void testIndCb() {
if (mGattCurTimeCfg.cCC == 0)
return;
const uint8_t *ptr = NULL;
uint16_t len = 0;
ptr = (const uint8_t *)&mGattCurTime;
len = sizeof(mGattCurTime);
gattServiceSendInd(mSvcTime, mConnTime, mGattHandleCurTime, ptr, len, false, 0);
}
static void *testGattSetTime(void *data) {
uint8_t TICK = 5;
while (!stopTimeService) {
sleep(TICK);
mGattCurTime.exactTime256.dayDateTime.dateTime.seconds += TICK;
if (mGattCurTime.exactTime256.dayDateTime.dateTime.seconds >= 60) {
mGattCurTime.exactTime256.dayDateTime.dateTime.seconds -= 60;
mGattCurTime.exactTime256.dayDateTime.dateTime.minutes += 1;
}
logi("time: %d\n", mGattCurTime.exactTime256.dayDateTime.dateTime.seconds);
testIndCb();
}
return NULL;
}
// Sets up current time service as a sample.
static int gattSetup(void)
{
struct uuid uuid;
int svc;
uuidFromUuid16(&uuid, 0x1805); //"current time" service
svc = gattServiceCreate(&uuid, true, 8, testGattSrvValueReadCbk, testGattSrvValueWriteCbk, testGattSrvIndCbk, testGattSrvDiscCbk);
if (!svc) {
loge("failed to alloc svc\n");
goto out;
}
uuidFromUuid16(&uuid, 0x2A2B); //"current time" characteristic
// GATT_PROP_READ and GATT_PROP_NOTIFY are mandatory.
// GATT_PROP_WRITE is optional.
mGattHandleCurTime = gattServiceAddChar(svc, &uuid, GATT_PROP_READ | GATT_PROP_NOTIFY, GATT_PERM_ALL_READ);
if (!mGattHandleCurTime) {
loge("Failed to add cur time char\n");
goto out;
}
uuidFromUuid16(&uuid, GATT_UUID_CHAR_CLI_CHAR_CFG); //char config descriptor
mGattHandleCurTimeCfg = gattServiceAddCharDescr(svc, &uuid, GATT_PERM_ALL_READ | GATT_PERM_ALL_WRITE);
if (!mGattHandleCurTimeCfg) {
loge("Failed to add cur time char cfg\n");
goto out;
}
uuidFromUuid16(&uuid, 0x2A0F); //"local time info" characteristic
mGattHandleLocalTimeInfo = gattServiceAddChar(svc, &uuid, GATT_PROP_READ | GATT_PROP_WRITE, GATT_PERM_ALL_READ | GATT_PERM_ALL_WRITE);
if (!mGattHandleLocalTimeInfo) {
loge("Failed to add local time info char\n");
goto out;
}
uuidFromUuid16(&uuid, 0x2A14); //"reference time info" characteristic
mGattHandleReferenceTimeInfo = gattServiceAddChar(svc, &uuid, GATT_PROP_READ, GATT_PERM_ALL_READ);
if (!mGattHandleReferenceTimeInfo) {
loge("Failed to add reference time info char\n");
goto out;
}
out:
return svc;
}
void pairStateObserverCallback(void *observerData, const void *pairStateChange, uniq_t observerId) {
struct smPairStateChange state;
if (observerId != mPairStateObserverId || !pairStateChange)
return;
state = *(struct smPairStateChange *)pairStateChange;
logi("Pairing state with "ADDRFMT" changed to %d with err %d\n", ADDRCONV(state.peerAddr),
state.pairState, state.pairErr);
// Unpair with the target device once it's paired
if (state.pairState == SM_PAIR_STATE_PAIRED) {
sleep(5);
smUnpair(&state.peerAddr);
}
}
void passkeyDisplayObserverCallback(void *observerData,
const struct smPasskeyDisplay *passkeyDisplay,
uniq_t observerId) {
if (observerId != mPasskeyDisplayObserverId || !passkeyDisplay)
return;
if (passkeyDisplay->valid)
logi("Please enter %06d with the keyboard with "ADDRFMT"\n", passkeyDisplay->passkey,
ADDRCONV(passkeyDisplay->peerAddr));
else
logi("The passkey section expired with the keyboard with "ADDRFMT"\n",
ADDRCONV(passkeyDisplay->peerAddr));
}
void passkeyRequestObserverCallback(void *observerData,
const struct smPasskeyRequest *passkeyRequest,
uniq_t observerId) {
uint32_t passkey = 0;
if (observerId != mPasskeyRequestObserverId || !passkeyRequest)
return;
if (terminateTimerId) {
timerCancel(terminateTimerId);
terminateTimerId = 0;
}
if (passkeyRequest->valid) {
logi("Please enter the passkey with the keyboard to pair with "ADDRFMT":\n",
ADDRCONV(passkeyRequest->peerAddr));
if (scanf("%u", &passkey) != 1)
return;
smProvidePasskey(&passkeyRequest->peerAddr, true, passkey);
} else {
logi("The passkey section expired with the keyboard with "ADDRFMT"\n",
ADDRCONV(passkeyRequest->peerAddr));
}
sleep(3);
terminate = true;
}
static void gattSvcTraversed(gatt_client_conn_t conn, uniq_t trans, const struct GattTraversedService* data)
{
uint32_t i, j;
if (!data)
logi("Traversal failed\n");
else {
logi("SERVICE "UUIDFMT" in range 0x%04x - 0x%04x:\n", UUIDCONV(data->uuid), data->firstHandle, data->lastHandle);
if (data->numInclSvcs) {
logi(" INCLUDED SERVICES (%u):\n", data->numInclSvcs);
for (i = 0; i < data->numInclSvcs; i++)
logi(" iSVC "UUIDFMT" with range 0x%04x - 0x%04x (defined at 0x%04x)\n", UUIDCONV(data->inclSvcs[i].uuid), data->inclSvcs[i].firstHandle, data->inclSvcs[i].lastHandle, data->inclSvcs[i].includeDefHandle);
}
if (data->numChars) {
logi(" CHARACTERISTICS (%u):\n", data->numChars);
for (i = 0; i < data->numChars; i++) {
logi(" CHAR "UUIDFMT" with range 0x%04x - 0x%04x (value at 0x%04x)\n", UUIDCONV(data->chars[i].uuid), data->chars[i].firstHandle, data->chars[i].lastHandle, data->chars[i].valHandle);
if (data->chars[i].numDescrs) {
logi(" DESCRIPTORS (%u):\n", data->chars[i].numDescrs);
for (j = 0; j < data->chars[i].numDescrs; j++)
logi(" DESCR "UUIDFMT" at 0x%04x\n", UUIDCONV(data->chars[i].descrs[j].uuid), data->chars[i].descrs[j].handle);
}
}
}
}
sem_post(&mGattTestSem);
}
static void gattSvcFoundCbk(gatt_client_conn_t conn, uniq_t trans, const struct uuid *uuid, bool primary, uint16_t firstHandle, uint16_t numHandles, uint8_t status)
{
struct ServiceList *t;
if (status != GATT_CLI_STATUS_OK) {
logi("enum status %u\n", status);
return;
}
if (!uuid) {
logi("enumeration ends\n");
sem_post(&mGattTestSem);
return;
}
logi("Found %s service "UUIDFMT" with handles 0x%04x+0x%04x\n", primary ? "primary" : "secondary", UUIDCONV(*uuid), firstHandle, numHandles);
t = calloc(sizeof(struct ServiceList), 1);
t->uuid = *uuid;
t->start = firstHandle;
t->end = firstHandle + numHandles - 1;
t->next = mGattSvcs;
mGattSvcs = t;
}
static void gattConnectedCbkGattEnum(gatt_client_conn_t conn, uint8_t status)
{
logi("Connect status: %u\n", status);
if (status != GATT_CLI_STATUS_OK)
return;
//list services
mGattSvcs = NULL;
logi("service enum = %u\n", gattClientEnumServices(conn, true, 0, gattSvcFoundCbk));
}
static void gattConnectedCbkHidTest(gatt_client_conn_t conn, uint8_t status)
{
logi("GATT for HID connect status: %u\n", status);
if (status == GATT_CLI_STATUS_OK) {
mHidConn = btleHidAttach(conn);
mBattSvcCon = gattSvcBattAttach(conn);
mMiscInfoSvcCon = gattSvcMiscInfoAttach(conn);
}
//either way unblock main thread
sem_post(&mGattTestSem);
}
static void btleHidConnStateCbk(ble_hid_conn_t hidId, uint8_t state)
{
logi("HID state: %u\n", state);
//if terminal state, unblock main thread
if (state == BTLE_HID_CONN_STATE_UP || state == BTLE_HID_CONN_STATE_TEARDOWN)
sem_post(&mGattTestSem);
}
static void btleHidReportRxCbk(ble_hid_conn_t hidId, int32_t reportId, sg data, bool byRequest)
{
logi("HID report ID %d RXed, %u bytes%s\n", reportId, sgLength(data), byRequest ? " by request" : "");
sgFree(data);
}
static void battSvcStateCbk(gatt_svc_batt_conn_t handle, uint8_t state)
{
logi("BattSvc state: %u\n", state);
if (state == BTLE_BATT_SVC_CONN_STATE_UP) {
logi(" BattSvc async support: %s\n", gattSvcBattIsNotifSupported(mBattSvcCon) ? "YES" : "NO");
logi(" BattSvc read requested: %s\n", gattSvcBattRequestRead(mBattSvcCon) ? "OK" : "ERROR");
}
}
static void battSvcRxCbk(gatt_svc_batt_conn_t handle, uint8_t percent, bool byRequest)
{
logi("Batt state: %u%% (%s)\n", percent, byRequest ? "by request" : "async update");
}
static void miscInfoSvcStateCbk(gatt_svc_misc_info_conn_t handle, uint8_t state)
{
uint32_t i;
logi("Misc Info state: %u\n", state);
if (state == BTLE_MISC_INFO_SVC_CONN_STATE_UP) {
for (i = 0; i < GattMiscInfoEnumMax; i++) // we expect first to fail (as it is invalid - this tests that)
logi("Misc Info Request %u: %s\n", i, gattSvcMiscInfoRequestRead(handle, i) ? "YES" : "NO");
}
}
static void showMiscInfoStr(const char *name, sg data)
{
char* t;
if (!data)
return;
t = calloc(1, sgLength(data) + 1);
sgSerialize(data, 0, sgLength(data), t);
sgFree(data);
fprintf(stderr, " %s: '%s'\n", name, t);
free(t);
}
static void miscInfoSvcRxCbk(gatt_svc_misc_info_conn_t handle, enum GattSvcMiscInfoType type, sg data)
{
uint8_t buf[16];
if (!data)
return;
switch(type) {
case GattMiscInfoTypeManufNameString:
showMiscInfoStr("Manufacturer Name", data);
return;
case GattMiscInfoTypeModelNumberString:
showMiscInfoStr("Model Number", data);
return;
case GattMiscInfoTypeSerialNumberString:
showMiscInfoStr("Serial Number", data);
return;
case GattMiscInfoTypeHardwareRevisionString:
showMiscInfoStr("HW Revision", data);
return;
case GattMiscInfoTypeFirmwareRevisionString:
showMiscInfoStr("FW Revision", data);
return;
case GattMiscInfoTypeSoftwareRevisionString:
showMiscInfoStr("SW Revision", data);
return;
case GattMiscInfoTypeDeviceName:
showMiscInfoStr("Device Name", data);
return;
case GattMiscInfoTypeSystemID:
if (!sgSerializeCutFront(data, buf, sizeof(uint64_t)) || sgLength(data))
fprintf(stderr, " Invalid SystemID value format\n");
else
fprintf(stderr, " System ID: 0x%016llx\n", (unsigned long long)utilGetLE64(buf));
break;
case GattMiscInfoTypeIeee11073_20601regCertList:
fprintf (stderr, " I have no idea how to show IEEE cert lists, but we got one %u bytes long\n", sgLength(data));
break;
case GattMiscInfoTypePnpID:
if (!sgSerializeCutFront(data, buf, 7) || sgLength(data))
fprintf(stderr, " Invalid PnP ID value format\n");
else
fprintf(stderr, " PnP ID: {src=%u,vid=0x%04x,pid=0x%04x,ver=0x%04x}\n", buf[0], utilGetLE16(buf + 1), utilGetLE16(buf + 3), utilGetLE16(buf + 5));
break;
case GattMiscInfoTypeAppearance:
if (!sgSerializeCutFront(data, buf, 2) || sgLength(data))
fprintf(stderr, " Invalid appearance value format\n");
else
fprintf(stderr, " Appearance: 0x%04x (%s,%s)\n", utilGetLE16(buf), getAppearanceTypeStr(utilGetLE16(buf)), getAppearanceSubtypeStr(utilGetLE16(buf)));
break;
default:
fprintf(stderr, " Got unkwnon dev info type %u\n", type);
break;
}
sgFree(data);
}
static bool eirNameMatch(const uint8_t *data, uint32_t len, const char *name)
{
while (len) {
logi(" len is %u\n", len);
uint8_t typ, pieceLen = *data++;
len--;
logi(" len is %u pieceLen is %u\n", len, pieceLen);
if (len < pieceLen || !pieceLen)
break;
typ = *data++;
len--;
pieceLen--;
logi(" adv chunk type 0x%x with %u bytes payload\n", typ, pieceLen);
if ((typ == 8 || typ == 9) && pieceLen == strlen(name) && !memcmp(data, name, pieceLen))
return true;
len -= pieceLen;
data += pieceLen;
}
return false;
}
static void terminateCbk(uniq_t which, uint64_t cbkData) {
if (which != terminateTimerId)
return;
terminate = true;
}
//for details see https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml
static const char* mLeAppearanceClasses[] =
{
"Unknown", "Phone", "Computer", "Watch", "Clock", "Display", "Remote Control", "Eyeglasses",
"Tag", "Keyring", "Media Player", "Barcode Scanner", "Thermometer", "Heart Rate Sensor", "Blood Pressure", "HID",
"Glucose Meter", "Running / Walking Sensor", "Cycling",
[49] = "Pulse Oximeter",
[50] = "Weight Scale",
[51] = "Personal Mobility Device",
[52] = "Continuous Glucose Monitor",
[53] = "Insulin Pump",
[54] = "Medication Delivery",
[81] = "Outdoor Sports Activity",
};
static const char* getAppearanceTypeStr(uint32_t appVal)
{
uint32_t cls = appVal >> 6;
return (cls >= sizeof(mLeAppearanceClasses) / sizeof(*mLeAppearanceClasses) || !mLeAppearanceClasses[cls]) ? "Unknown" : mLeAppearanceClasses[cls];
}
static const char* getAppearanceSubtypeStr(uint32_t appVal)
{
static const char* hidSubclasses[] = {"Generic", "Keyboard", "Mouse", "Joystick", "Gamepad", "Digitizer Tablet", "Card Reader", "Digital Pen", "Barcode Scanner", NULL};
static const char* outdoorSubclasses[] = {"Generic", "Location Display Device", "Location and Navigation Display Device", "Location Pod", "Location and Navigation Pod", NULL};
static const char** subclasses[] = {[15] = hidSubclasses, [81] = outdoorSubclasses,};
const char** t;
uint32_t cls = appVal >> 6;
uint32_t subclass = appVal & 0x3F;
if (cls >= sizeof(subclasses) / sizeof(*subclasses) || !subclasses[cls])
return "Unknown";
//search for sentinel value or proper place
for (t = subclasses[cls]; *t && subclass; t++, subclass--);
return *t ? *t : "Unknown";
}
static void interpAvdData(const uint8_t *data, uint32_t len)
{
//for details, see https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
while (len >= 2) {
uint32_t val;
uint8_t i, typ, sz;
struct uuid uuid;
char *buf;
sz = *data++;
if (!sz || sz > len - 1)
break;
typ = *data++;
len -= 2;
sz--;
switch (typ) {
case 0x01:
if (sz != 1)
goto out;
logi("\t\tFLAGS: 0x%02x\n", data[0]);
break;
case 0x02:
case 0x03:
logi("\t\tUUID16s (%scomplete):\n", (typ & 1) ? "" : "in");
if (sz % SIZEOF_UUID_16)
goto out;
for (i = 0; i < sz; i += SIZEOF_UUID_16)
logi("\t\t\t0x%04x\n", utilGetLE16(data + i));
break;
case 0x04:
case 0x05:
logi("\t\tUUID32s (%scomplete):\n", (typ & 1) ? "" : "in");
if (sz % SIZEOF_UUID_32)
goto out;
for (i = 0; i < sz; i += SIZEOF_UUID_32)
logi("\t\t\t0x%08x\n", utilGetLE32(data + i));
break;
case 0x06:
case 0x07:
logi("\t\tUUID128s (%scomplete):\n", (typ & 1) ? "" : "in");
if (sz % sizeof(struct uuid))
goto out;
for (i = 0; i < sz; i += sizeof(struct uuid)) {
uuidReadLE(&uuid, data + i);
logi("\t\t\t"UUIDFMT"\n", UUIDCONV(uuid));
}
break;
case 0x08:
case 0x09:
buf = malloc(sz + 1);
memcpy(buf, data, sz);
buf[sz] = 0;
logi("\t\tNAME (%scomplete): <<%s>>\n", (typ & 1) ? "" : "in", buf);
free(buf);
break;
case 0x0a:
if (sz != 1)
goto out;
logi("\t\tTX PWR: %d dBm\n", (int8_t)data[0]);
break;
case 0x0d:
if (sz != 3)
goto out;
logi("\t\tCLASS: 0x%06x\n", utilGetLE24(data));
break;
case 0x11:
if (sz != 1)
goto out;
logi("\t\tSEC MGR FLAGS: 0x%02x\n", data[0]);
break;
case 0x12:
if (sz != 4)
goto out;
logi("\t\tSLAVE CONNECTION INTERVAL PREFERENCE: 0x%04x - 0x%04x\n", utilGetLE16(data + 0), utilGetLE16(data + 2));
break;
case 0x19:
if (sz != 2)
goto out;
val = utilGetLE16(data);
logi("\t\tAPPEARANCE: 0x%04x (%s,%s)\n", val, getAppearanceTypeStr(val), getAppearanceSubtypeStr(val));
break;
case 0x1b:
if (sz != 7)
goto out;
logi("\t\tADDRESS: %s %02x:%02x:%02x:%02x:%02x:%02x\n", data[0] ? (data[0] == 1 ? "Random" : "Unknown") : "Public", data[6], data[5], data[4], data[3], data[2], data[1]);
break;
case 0x16:
case 0xff:
if (sz < 2)
goto out;
logi("\t\t%s 0x%04x (%u bytes)\n", (typ == 0xFF) ? "MANUF DATA FOR MANUF" : "SERVICE DATA FOR SVC", utilGetLE16(data), sz - 2);
i = 2;
showdata:
buf = malloc(sz * 4);
buf[0] = 0;
for (; i < sz; i++)
sprintf(buf + strlen(buf), "%02x%s", data[i], (i == sz - 1) ? "" : ", ");
logi("\t\t\t%s\n", buf);
free(buf);
break;
default:
logi("\t\tTYPE 0x%02x, %u bytes:\n", typ, sz);
i = 0;
goto showdata;
}
len -= sz;
data += sz;
}
out:
if (len)
logi("\tLEFTOVER %u bytes!\n", len);
}
int main(int argc, char** argv)
{
static const uint8_t mac[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
static const struct bt_addr addr = {
.type = BT_ADDR_TYPE_LE_RANDOM, .addr = {0xFF, 0x8E, 0xFC, 0x0A, 0x11, 0xFF}}; //adv set 0
sem_t sem;
fprintf(stderr, "NewBlue test\n");
logi("LOG UP\n");
if (sem_init(&mGattTestSem, 0, 0)) {
loge("sem init failed\n");
return -1;
}
if (sem_init(&sem, 0, 0)) {
loge("sem init failed\n");
return -1;
}
#ifdef TEST_DEBUG
vendorLogEnable(true);
#endif
if (!hciUp(mac, readyForUpCbk, NULL)) {
loge("HCI up fail\n");
return -1;
}
logi("waiting for stack up\n");
while(!hciIsUp())
sleep(1);
logi("stack is up\n");
l2cInit();
if (!attInit())
loge("Failed to init ATT\n");
if (!gattProfileInit())
loge("Failed to init GATT\n");
if (!gattBuiltinInit())
loge("Failed to init GATT services\n");
if (!smInit()) {
loge("Failed to init SM\n");
goto cleanup;
}
timersInit();
//initialize addresses for adv sets
mAddrs[0] = addr;
if (!smGenResolvableAddr(&mAddrs[1])) {
loge("Failed to generate resolvable private address for adv set\n");
goto cleanup;
}
if (!smGenNonResolvableAddr(&mAddrs[2])) {
loge("Failed to generate non-resolvable private address for adv set\n");
goto cleanup;
}
if (argc == 2 && (!strcmp(argv[1], "scan") || !strcmp(argv[1], "scanconnect") || !strcmp(argv[1], "hidtest"))) {
/*
* Option: scan, scanconnect, hidtest
* Use: start LE scanning for a while and terminate. in case of scanconnect, connect and enumerate GATT things, for "hidtest", try the hid test
*/
bool foundDev = false, wantConnect = !strcmp(argv[1], "scanconnect"), doHidTest = !strcmp(argv[1], "hidtest");
struct LeDiscDev *leDevices = NULL, *t;
gatt_client_conn_t con = 0;
struct bt_addr connTo;
uniq_t h;
logi("Trying some LE discovery%s...\n", (wantConnect || doHidTest) ? " and connection" : "");
h = hciDiscoverLeStart(leDiscoveryCbk, &leDevices, true, false);
if (h) {
sleep((wantConnect || doHidTest) ? 5 : 20);
fprintf(stderr, "\n");
logi("discovery stop = %s\n", hciDiscoverLeStop(h) ? "OK" : "FAIL");
while (leDevices) {
char str[128];
t = leDevices;
leDevices = leDevices->next;
logi("Device %c %02X:%02X:%02X:%02X:%02X:%02X\n", t->randomAddr ? 'R' : 'P',
t->mac[5], t->mac[4], t->mac[3], t->mac[2], t->mac[1], t->mac[0]);
if (t->rssi != HCI_RSSI_UNKNOWN)
logi("\tRSSI: %d dBm\n", t->rssi);
else
logi("\tRSSI unknown\n");
logi("\tADV_TYPE: %d, RSP? %s\n", t->advType, t->haveRsp ? "YES" : "NO");
logData(str, t->advData, t->advLen);
logi("\tADV %s\n", str);
interpAvdData(t->advData, t->advLen);
if (t->haveRsp) {
logData(str, t->rspData, t->rspLen);
logi("\tRSP %s\n", str);
interpAvdData(t->rspData, t->rspLen);
}
if (wantConnect || doHidTest) { //is this the one we shoudl connect to???
static const char *connNameMatch = "PC120A";
if (eirNameMatch(t->advData, t->advLen, connNameMatch) || (t->haveRsp && eirNameMatch(t->rspData, t->rspLen, connNameMatch))) {
foundDev = true;
memcpy(connTo.addr, t->mac, BT_MAC_LEN);
connTo.type = t->randomAddr ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC;
logi("Found device to connect to: "ADDRFMT"\n", ADDRCONV(connTo));
}
}
free(t);
}
if (!foundDev)
logi("Desired device not found. Sorry\n");
else if (doHidTest) {
gattSvcBattInit(battSvcStateCbk, battSvcRxCbk);
btleHidInit(btleHidConnStateCbk, btleHidReportRxCbk);
gattSvcMiscInfoInit(miscInfoSvcStateCbk, miscInfoSvcRxCbk);
con = gattClientConnect(&connTo, gattConnectedCbkHidTest);
if (!con)
logw("Failed to try to form a GATT connection!\n");
else {
logi("Gatt connection handle: "UNIQ_FMT"x\n", UNIQ_CONV(con));
sem_wait(&mGattTestSem);
if (mHidConn) { //connection in progress - wait some more
sem_wait(&mGattTestSem);
logi("HID connected. press a key to exit\n");
getchar();
btleHidDetachFromGatt(con);
gattClientDisconnect(con);
}
}
}
else if (wantConnect) {
con = gattClientConnect(&connTo, gattConnectedCbkGattEnum);
if (!con)
logw("Failed to try to form a GATT connection!\n");
else {
logi("Gatt connection handle: "UNIQ_FMT"x\n", UNIQ_CONV(con));
//service enum will start in gattConnectedCbkGattEnum() and we'll wait for it here
sem_wait(&mGattTestSem);
logi("Service enum ends...\n");
//list details of each service
while (mGattSvcs) {
struct ServiceList *svc = mGattSvcs;
mGattSvcs = mGattSvcs->next;
logi("ENUMERATING SERVICE "UUIDFMT" (known to be in range 0x%04x - 0x%04x): %u\n", UUIDCONV(svc->uuid), svc->start, svc->end, gattClientUtilFindAndTraversePrimaryService(con, &svc->uuid, 0, gattSvcTraversed));
free(svc);
sem_wait(&mGattTestSem);
}
}
}
} else
loge("Failed to do LE discovery\n");
}
else if ((argc == 2 && !strcmp(argv[1], "pair")) || (argc == 3 && !strcmp(argv[1], "pair"))) {
/*
* Option: pair
* Use: set up a GATT connection with the device with mAddrs[0] or the provided address
* and start pairing
*/
bool found = false;
uniq_t scanId;
unsigned i, round = 3;
struct LeDiscDev *leDevices = NULL, *t;
struct smPairSecurityRequirements secReqs = {.bond = true, .mitm = true};
regex_t regex;
if (argc == 3) {
if (regcomp(&regex, "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$", REG_EXTENDED) != 0 ||
regexec(&regex, argv[2], 0, NULL, 0) != 0) {
loge("Invalid mac address provided\n");
goto cleanup;
}
regfree(&regex);
sscanf(argv[2], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mInputDevAddr.addr[5],
&mInputDevAddr.addr[4], &mInputDevAddr.addr[3], &mInputDevAddr.addr[2],
&mInputDevAddr.addr[1], &mInputDevAddr.addr[0]);
mInputDevAddr.type = BT_ADDR_TYPE_LE_RANDOM;
}
// register as an observer to respond to passkey display
mPasskeyDisplayObserverId =
smRegisterPasskeyDisplayObserver(NULL, passkeyDisplayObserverCallback);
if (!mPasskeyDisplayObserverId) {
loge("Failed to register as an observer to passkey display\n");
goto cleanup;
}
// register as an observer to pairing state changes
mPairStateObserverId = smRegisterPairStateObserver(NULL, pairStateObserverCallback);
if (!mPairStateObserverId) {
loge("Failed to register as an observer to pairing state changes\n");
goto cleanup;
}
logi("Scanning for device "ADDRFMT" ...\n",
ADDRCONV(argc == 3 ? mInputDevAddr : mAddrs[0]));
for (i = 0; i < round; ++i) {
scanId = hciDiscoverLeStart(leDiscoveryCbk, &leDevices, true, false);
if (!scanId) {
loge("Failed to do LE discovery\n");
break;
}
// scan for 5 seconds
sleep(5);
fprintf(stderr, "\n");
logi("discovery stop = %s\n", hciDiscoverLeStop(scanId) ? "OK" : "FAIL");
// find the target device to connect to and pair with it
while (leDevices) {
t = leDevices;
leDevices = leDevices->next;
logi("Device %c %02X:%02X:%02X:%02X:%02X:%02X\n", t->randomAddr ? 'R' : 'P',
t->mac[5], t->mac[4], t->mac[3], t->mac[2], t->mac[1], t->mac[0]);
if ((found = !memcmp((argc == 3 ? mInputDevAddr.addr : mAddrs[0].addr), t->mac,
sizeof(t->mac)))) {
found = true;
break;
} else {
free(t);
continue;
}
}
if (found) {
logi("Target device found, try to pair with device "ADDRFMT" ...\n",
ADDRCONV(argc == 3 ? mInputDevAddr : mAddrs[0]));
smPair((argc == 3 ? &mInputDevAddr : &mAddrs[0]), &secReqs);
break;
}
// reset discovery session ID
scanId = 0;
}
logi("waiting...press a key\n");
getchar();
}
else if (argc == 1) {
/*
* Option: none
* Use: set up GATT server with Current Time Service and start 3 advertisements and wait
* for either pairing or connection request
*/
unsigned i;
int8_t txPower = 0;
// mAdvSet[0]
static const uint8_t data[] = {
2, HCI_EIR_TYPE_FLAGS, 2, /* flags - general discoverable */
5, HCI_EIR_TYPE_FULL_NAME, 'N', 'B', 't', '2', /* name - "NBt2" */
3, HCI_EIR_TYPE_INCOMPL_LIST_UUID16, 0x05, 0x18, /* incomplete uuid list - {time service} */
5, HCI_EIR_FLAVE_CONN_INTS, 0x10, 0x00, 0xff, 0x00, /* relatively large range of relatively large connection intervals accepted */
};
mSvcTime = gattSetup();
logi("gattSetup: svc="GATTHANDLEFMT"\n", GATTHANDLECNV(mSvcTime));
if (!gattServiceStart(mSvcTime, true, true))
loge("Failed to start service: "GATTHANDLEFMT"\n", GATTHANDLECNV(mSvcTime));
logd("Current time service created as svc "GATTHANDLEFMT" at handle 0x%04X\n", GATTHANDLECNV(mSvcTime), gattServiceGetHandleBaseBySvc(mSvcTime));
testGattInitTime();
pthread_t setTimeThread;
if (pthread_create(&setTimeThread, NULL, testGattSetTime, NULL)) {
loge("Failed to create pthread for current time service.\n");
return -1;
}
gattBuiltinSetDevName("SuperLongDeviceNameThatWillProbablyNotFitIntoASinglePacketEver");
gattBuiltinSetPreferredConnParamsWhenSlave(20, 30, 40, 400);
gattBuiltinSetAppearance(GAP_APPEAR_GENERIC_WATCH);
// register as an observer to respond to passkey display
mPasskeyRequestObserverId =
smRegisterPasskeyRequestObserver(NULL, passkeyRequestObserverCallback);
if (!mPasskeyRequestObserverId) {
loge("Failed to register as an observer to passkey request\n");
goto cleanup;
}
//now let's try some multi-adv
for (i = 0; i < sizeof(mAdvSet) / sizeof(*mAdvSet); i++) {
static const uint8_t advData[] = {2, HCI_EIR_TYPE_FLAGS, 2, /* flags - general discoverable */};
uint8_t scanData[] = {6, HCI_EIR_TYPE_FULL_NAME, 'N', 'B', 't', '1', '0' + i, /* name - "NBt10" and up */};
mAdvSet[i] = hciAdvSetAllocate();
if (!mAdvSet[i]) {
loge("Failed to create multi set %u\n", i);
break;
}
if (!i)
txPower = -10;
else if (i == 1)
txPower = HCI_ADV_TX_PWR_LVL_DONT_CARE;
else
txPower = -10;
if (!hciAdvSetSetAdvParams(
mAdvSet[i], 0x0040, 0x0100, HCI_ADV_TYPE_ADV_IND,
HCI_ADV_OWN_ADDR_TYPE_RANDOM, NULL,
HCI_ADV_CHAN_MAP_USE_CHAN_37 | HCI_ADV_CHAN_MAP_USE_CHAN_38 | HCI_ADV_CHAN_MAP_USE_CHAN_39,
HCI_ADV_FILTER_POL_SCAN_ALL_CONNECT_ALL, txPower))
loge("Failed to set LE adv set %u params\n", i);
if (!hciAdvSetConfigureData(mAdvSet[i], false, !i ? data : advData, !i ? sizeof(data) : sizeof(advData)))
loge("Failed to set LE adv set %u adv data\n", i);
if (i && !hciAdvSetConfigureData(mAdvSet[i], true, scanData, sizeof(scanData)))
loge("Failed to set LE adv set %u scan data\n", i);
if (!hciAdvSetOwnRandomAddr(mAdvSet[i], &mAddrs[i]))
loge("Failed to set LE adv set %u random addr\n", i);
if (!hciAdvSetEnable(mAdvSet[i]))
loge("Failed to try to enable adv set %u\n", i);
}
//print tx power for each adv in a set
for (i = 0; i < sizeof(mAdvSet) / sizeof(*mAdvSet); i++) {
int8_t power;
if (!mAdvSet[i])
continue;
if (!hciAdvSetGetCurTxPowerLevel(mAdvSet[i], &power))
loge("Failed to get adv power for set %u\n", i);
else
logi(" adv set %u is at %d dBm with addr "ADDRFMT"\n", i, power, ADDRCONV(mAddrs[i]));
}
//if there is no passkey request callback, the timer will be fired after 20 seconds
terminateTimerId = timerSet(20000 /* 20 secs */, terminateCbk, 0);
if (!terminateTimerId) {
logw("Failed to set timer for termination");
terminate = true;
}
//wait for either timer with terminateTimerId to fire or the passkey request callback
while (!terminate)
sleep(1);
logi("waiting...press a key\n");
getchar();
//disable and free adv sets
for (i = 0; i < sizeof(mAdvSet) / sizeof(*mAdvSet); i++) {
if (!mAdvSet[i])
continue;
if (!hciAdvSetDisable(mAdvSet[i]))
loge("Failed to disable adv %u\n", i);
if (!hciAdvSetFree(mAdvSet[i]))
loge("Failed to free adv %u\n", i);
}
stopTimeService = true;
if (pthread_join(setTimeThread, NULL))
loge("Failed to join the pthread of current time service.\n");
logi("The pthread of current time service was terminated.\n");
if (mSvcTime) {
gattServiceStop(mSvcTime);
gattServiceDestroy(mSvcTime);
}
}
cleanup:
logi("bringing down\n");
timersDeinit();
smDeinit();
gattBuiltinDeinit();
gattProfileDeinit();
attDeinit();
l2cDeinit();
hciDown();
logi("done\n");
return 0;
}
void aapiAdapterStateChanged(bool on)
{
logd("aapiAdapterStateChanged(%d)\n", on);
}
void aapiAdapterProperties(bt_status_t status, int num_properties, const bt_property_t *properties)
{
logd("aapiAdapterProperties(...)\n");
}
void aapiRemoteDevProperties(bt_status_t status, const bt_bdaddr_t *bd_addr, int num_properties, const bt_property_t *properties)
{
logd("aapiRemoteDevProperties(...)\n");
}
void aapiDevDiscoveredCbk(int num_properties, const bt_property_t *properties)
{
logd("aapiDevDiscoveredCbk(...)\n");
}
void aapiDiscoveryStateChanged(bool on)
{
logd("aapiDiscoveryStateChanged(%d)\n", on);
}
void aapiBondStateChangedCbk(const struct bt_addr *peer, uint8_t state)
{
logd("aapiBondStateChangedCbk(...)\n");
}
void aapiAclStateChanged(bt_status_t status, const struct bt_addr *peer, bool up)
{
logd("aapiAclStateChanged(..., %d)\n", up);
}
void aapiDutModeEventCbk(uint16_t opcode, const uint8_t *buf, uint8_t len)
{
logd("aapiDutModeEventCbk(...)\n");
}
void aapiLeTestModeEventCbk(bt_status_t status, uint16_t num_packets)
{
logd("aapiLeTestModeEventCbk(...)\n");
}