blob: 988c088a2602bc315fd77328b63039a8453c5160 [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 "gatt-builtin.h"
#include "vendorLib.h"
#include "persist.h"
#include "timer.h"
#include "l2cap.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];
};
static uint8_t gStatus = 0;
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 struct bt_addr mTargetAddr = {
.type = BT_ADDR_TYPE_LE_RANDOM,
.addr = {0x50, 0x8E, 0xFC, 0x0A, 0x11, 0x11}
};
uniq_t mObserverId = 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;
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;
const uint8_t *ptr = NULL;
uint16_t len = 0;
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);
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;
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;
}
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)
{
if (svc != mSvcTime) {
logw("Unexpected notification callback in Current Time service\n");
return;
}
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 seconds;
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;
}
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));
}
void pairObserverCallback(void *observerData, const void *pairStateChange, uniq_t observerId) {
struct smPairStateChange state;
if (observerId != mObserverId || !pairStateChange)
return;
state = *(struct smPairStateChange *)pairStateChange;
if (!isSameAddr(&state.peerAddr, &mTargetAddr))
return;
logi("Pairing state with "ADDRFMT" changed to %d with err %d\n", ADDRCONV(mTargetAddr),
state.pairState, state.pairErr);
// Unpair with the target device once it's paired
if (state.pairState == SM_PAIR_STATE_PAIRED) {
sleep(5);
smUnpair(&mTargetAddr);
}
}
int main(int argc, char** argv)
{
static const uint8_t mac[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
hci_adv_set_t advSet[3] = {0,};
sem_t sem;
fprintf(stderr, "NewBlue test\n");
logi("LOG UP\n");
if (sem_init(&sem, 0, 0)) {
loge("sem init failed\n");
return -1;
}
#ifdef TEST_DEBUG
vendorLogEnable(true);
#endif
persistLoad();
timersInit();
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(HCI_DISP_CAP_NONE)) {
loge("Failed to init SM\n");
goto cleanup;
}
if (argc == 2 && !strcmp(argv[1], "scan")) {
/*
* Option: scan
* Use: start LE scanning for a while and terminate
*/
uniq_t h;
struct LeDiscDev *leDevices = NULL, *t;
logi("Trying some LE discovery...\n");
h = hciDiscoverLeStart(leDiscoveryCbk, &leDevices, true, false);
if (h) {
sleep(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);
if (t->haveRsp) {
logData(str, t->rspData, t->rspLen);
logi("\tRSP %s\n", str);
}
free(t);
}
} else
loge("Failed to do LE discovery\n");
}
else if (argc == 2 && !strcmp(argv[1], "pair")) {
/*
* Option: pair
* Use: set up a GATT connection with the device with mTargetAddr and start pairing
*/
bool found = false;
uniq_t scanId;
unsigned i, round = 3;
struct LeDiscDev *leDevices = NULL, *t;
struct smPairSecurityRequirements secReqs = {.bond = true, .mitm = false};
// register as an observer to pairing state changes
mObserverId = smRegisterPairObserver(NULL, pairObserverCallback);
if (!mObserverId) {
loge("Failed to register as an observer to pairing state changes\n");
goto cleanup;
}
logi("Scanning for device "ADDRFMT" ...\n", ADDRCONV(mTargetAddr));
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(mTargetAddr.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(mTargetAddr));
smPair(&mTargetAddr, &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
*/
unsigned i;
int8_t txPower = 0;
// advSet[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=%d\n", mSvcTime);
if (!gattServiceStart(mSvcTime, true, true))
loge("Failed to start service: %d\n", mSvcTime);
logd("Current time service created as svc %u at handle 0x%04X\n", 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);
//now let's try some multi-adv
for (i = 0; i < sizeof(advSet) / sizeof(*advSet); 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 */};
struct bt_addr myRandomMac = {.type = BT_ADDR_TYPE_LE_RANDOM, .addr = {0xDD, 0x00, 0xAA, 0x00, 0xBB, 0xBB + i}};
advSet[i] = hciAdvSetAllocate();
if (!advSet[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(
advSet[i], !i ? 0x0020 : 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(advSet[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(advSet[i], true, scanData, sizeof(scanData)))
loge("Failed to set LE adv set %u scan data\n", i);
if (!hciAdvSetOwnRandomAddr(advSet[i], !i ? &mTargetAddr : &myRandomMac))
loge("Failed to set LE adv set %u random addr\n", i);
if (!hciAdvSetEnable(advSet[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(advSet) / sizeof(*advSet); i++) {
int8_t power;
if (!advSet[i])
continue;
if (!hciAdvSetGetCurTxPowerLevel(advSet[i], &power))
loge("Failed to get adv power for set %u\n", i);
else
logi(" adv set %u is at %d dBm\n", i, power);
}
logi("waiting...press a key\n");
getchar();
//disable and free adv sets
for (i = 0; i < sizeof(advSet) / sizeof(*advSet); i++) {
if (!advSet[i])
continue;
if (!hciAdvSetDisable(advSet[i]))
loge("Failed to disable adv %u\n", i);
if (!hciAdvSetFree(advSet[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");
smDeinit();
gattBuiltinDeinit();
gattProfileDeinit();
attDeinit();
l2cDeinit();
hciDown();
timersDeinit();
persistStore();
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");
}