blob: 7fbe54bbba6429dc55faa3c8cbe86457af835180 [file] [log] [blame]
#include <stdlib.h>
#include <string.h>
#include "aapiGattServer.h"
#include "gatt-builtin.h"
#include "aapiGatt.h"
#include "gatt.h"
#include "log.h"
#include "bt.h"
#include "mt.h"
/* this grouping is not part of the GATT spec and its semantics are thus unclear. */
struct aapiGattServerService {
int gattSvc;
uint8_t inst;
};
struct aapiGattServer {
struct aapiGattServer *next;
struct aapiGattServer *prev;
bt_uuid_t andrUuid;
int refNum;
uint32_t numServices;
struct aapiGattServerService *services;
};
/* our state */
static btgatt_server_callbacks_t mCbks;
static pthread_mutex_t mAapiGattSrvLock = PTHREAD_MUTEX_INITIALIZER;
static struct aapiGattServer *mServers;
static bool mInited = false;
/*
* FUNCTION: aapiGattServerFindStructByRef
* USE: Find a aapiGattServer struct by reference number
* PARAMS: ref - the reference
* RETURN: the aapiGattServer struct or NULL if not found
* NOTES: call with mAapiGattSrvLock held
*/
static struct aapiGattServer* aapiGattServerFindStructByRef(int ref)
{
struct aapiGattServer *ret = mServers;
while (ret && ret->refNum != ref)
ret = ret->next;
return ret;
}
/*
* FUNCTION: aapiGattServerFindStructByUuid
* USE: Find a aapiGattServer struct by android-style uuid
* PARAMS: andrUuid - the uuid
* RETURN: the aapiGattServer struct or NULL if not found
* NOTES: call with mAapiGattSrvLock held
*/
static struct aapiGattServer* aapiGattServerFindStructByUuid(const bt_uuid_t *andrUuid)
{
struct aapiGattServer *ret = mServers;
while (ret && memcmp(&ret->andrUuid, andrUuid, sizeof(ret->andrUuid)))
ret = ret->next;
return ret;
}
/*
* FUNCTION: aapiGattServerContainsServiceWithUuid
* USE: See if a aapiGattServer contains a service with a given UUID
* PARAMS: srv - the aapiGattServer
* uuid - the uuid in question
* instIdP - if non-null, instance Id will be stored here
* RETURN: true if found, false else
* NOTES: call with mAapiGattSrvLock held
*/
static bool aapiGattServerContainsServiceWithUuid(const struct aapiGattServer *srv, const struct uuid *uuid, uint8_t *instIdP)
{
struct uuid localUuid;
uint32_t i;
for (i = 0; i < srv->numServices; i++) {
if (gattServiceGetUuid(srv->services[i].gattSvc, &localUuid))
loge("Failed to get uuid. This is not possible\n");
else if (uuidCmp(&localUuid, uuid)) {
if (instIdP)
*instIdP = srv->services[i].inst;
return true;
}
}
return false;
}
/*
* FUNCTION: aapiGattServerRegisterServer
* USE: Registers a GATT server application with the stack
* PARAMS: uuid - the uuid
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerRegisterServer(bt_uuid_t *andrUuid)
{
bt_status_t ret = BT_STATUS_FAIL;
int ref = 0;
pthread_mutex_lock(&mAapiGattSrvLock);
if (aapiGattServerFindStructByUuid(andrUuid))
loge("Refusing to create identical GATT server\n");
else {
struct aapiGattServer *srv = (struct aapiGattServer*)calloc(1, sizeof(struct aapiGattServer));
if (!srv) {
loge("Failed to allocate GATT server\n");
ret = BT_STATUS_NOMEM;
} else {
/* grab a free reference number */
srv->refNum = mServers ? (mServers->refNum + 1) : 1;
while (aapiGattServerFindStructByRef(srv->refNum))
srv->refNum++;
/* link it in */
srv->next = mServers;
if (mServers)
mServers->prev = srv;
mServers = srv;
/* save uuid */
memcpy(&srv->andrUuid, andrUuid, sizeof(srv->andrUuid));
/* prepare to announce success */
ret = BT_STATUS_SUCCESS;
ref = srv->refNum;
}
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.register_server_cb(ret, ref, andrUuid);
return ret;
}
/*
* FUNCTION: aapiGattServerUnregisterServer
* USE: Unregister a server application from the stack
* PARAMS: server_if - the server's reference number
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerUnregisterServer(int server_if)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
pthread_mutex_lock(&mAapiGattSrvLock);
srv = aapiGattServerFindStructByRef(server_if);
if (!srv)
logw("Server not found while attempting to unregister\n");
else {
uint32_t i;
if (srv->next)
srv->next->prev = srv->prev;
if (srv->prev)
srv->prev->next = srv->next;
else
mServers = srv->next;
for (i = 0; i < srv->numServices; i++) {
if (gattServiceIsRunning(srv->services[i].gattSvc)) {
logw("Unregistering server with running services!\n");
gattServiceStop(srv->services[i].gattSvc);
}
gattServiceDestroy(srv->services[i].gattSvc);
}
if (srv->services)
free(srv->services);
free(srv);
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
return ret;
}
/*
* FUNCTION: aapiGattServerConnect
* USE: Create a connection to a remote peripheral
* PARAMS: server_if - the server's reference number
* bd_addr - remote address
* is_direct - XXX: TODO: WTF??
* transport - (BT_TRANSPORT_*)
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerConnect(int server_if, const bt_bdaddr_t *bd_addr, bool is_direct, int transport)
{
//TODO
return BT_STATUS_FAIL;
}
/*
* FUNCTION: aapiGattServerDisconnect
* USE: Disconnect an established connection or cancel a pending one
* PARAMS: server_if - the server's reference number
* bd_addr - remote address
* conn_id - connection reference number
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerDisconnect(int server_if, const bt_bdaddr_t *bd_addr, int conn_id)
{
//TODO
return BT_STATUS_FAIL;
}
/*
* FUNCTION: aapiGattServerReadF
* 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 aapiGattServerReadF(int svc, l2c_handle_t who, int cid, int transId, uint16_t handle, uint16_t byteOfst, uint8_t reason, uint16_t len)
{
struct bt_addr peer;
bt_bdaddr_t andrPeerAddr;
if (!l2cApiGetBtAddr(who, &peer)) {
loge("Failed to get peer addr for read cbk\n");
return false;
}
memcpy(andrPeerAddr.address, peer.addr, sizeof(andrPeerAddr.address));
mCbks.request_read_cb(cid, transId, &andrPeerAddr, handle, byteOfst, reason == ATT_READ_FOR_READ_BLOB);
return true;
}
/*
* FUNCTION: aapiGattServerWriteF
* 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: call gattSrvCbkReply() with results. returning false will send ATT_ERROR_UNLIKELY_ERROR to the client
*/
static bool aapiGattServerWriteF(int svc, l2c_handle_t who, int cid, int transId, uint16_t handle, uint16_t byteOfst, uint8_t reason, uint16_t len, const void *data)
{
struct bt_addr peer;
bt_bdaddr_t andrPeerAddr;
if (!l2cApiGetBtAddr(who, &peer)) {
loge("Failed to get peer addr for write cbk\n");
return false;
}
mCbks.request_write_cb(cid, transId, &andrPeerAddr, handle, byteOfst, len, true, reason == ATT_WRITE_FOR_PREPARE, (uint8_t*)data);
return true;
}
/*
* FUNCTION: aapiGattServerIndF
* 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:
*/
static void aapiGattServerIndF(int svc, l2c_handle_t who, int cid, uint16_t handle, uint8_t evt, uint64_t ref)
{
/* XXX: tell java somehow? Current api has no way it seems */
}
/*
* FUNCTION: aapiGattServerAddService
* USE: Create a new service
* PARAMS: server_if - the server's reference number
* srvc_id - service identifying information
* num_handles - how many handles to allocate
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerAddService(int server_if, btgatt_srvc_id_t *srvc_id, int num_handles)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
uint16_t handle = 0;
pthread_mutex_lock(&mAapiGattSrvLock);
srv = aapiGattServerFindStructByRef(server_if);
if (!srv)
logw("Server not found while attempting to add service\n");
else {
struct aapiGattServerService *svcs;
struct uuid uuid;
int svc;
svcs = realloc(srv->services, sizeof(struct aapiGattServerService[srv->numServices + 1]));
if (!svcs)
logw("Failed to make space for the service\n");
else {
srv->services = svcs;
aapiGattUuidFromAndroidUuid(&uuid, &srvc_id->id.uuid);
svc = gattServiceCreate(&uuid, srvc_id->is_primary, num_handles, aapiGattServerReadF, aapiGattServerWriteF, aapiGattServerIndF);
if (!svc)
logw("Failed to create GATT service\n");
else {
srv->services[srv->numServices].gattSvc = svc;
srv->services[srv->numServices].inst = srvc_id->id.inst_id;
srv->numServices++;
handle = gattServiceGetHandleBaseBySvc(svc);
ret = BT_STATUS_SUCCESS;
}
}
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.service_added_cb(ret, server_if, srvc_id, handle);
return ret;
}
/*
* FUNCTION: aapiGattServerFindService
* USE: Find a server and a service given their externally-visible reference numbers
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the service
* srvP - if not NULL, we store the struct aapiGattServer* there
* svc - if not NULL, we store the service reference number there
* svcIdxInTableP - if not NULL, we store service's index in the server's "services" table
* firstOnly - if set handle is service_handle, else it is any handle in the range
* RETURN: true if all were found
* NOTES: call with mAapiGattSrvLock held
*/
static bool aapiGattServerFindService(int server_if, int handle, struct aapiGattServer **srvP, int *svcP, uint32_t *svcIdxInTableP, bool firstOnly)
{
struct aapiGattServer *srv;
uint32_t i;
int svc;
srv = aapiGattServerFindStructByRef(server_if);
if (!srv)
return false;
svc = gattServiceFindByHandle(handle, firstOnly);
if (svc < 0)
return false;
/* verify proper belonging */
for (i = 0; i < srv->numServices; i++)
if (srv->services[i].gattSvc == svc)
break;
if (i == srv->numServices) {
logw("Found service not aprt of found server!\n");
return false;
}
if (srvP)
*srvP = srv;
if (svcP)
*svcP = svc;
if (svcIdxInTableP)
*svcIdxInTableP = i;
return true;
}
/*
* FUNCTION: aapiGattServerAddIncludedService
* USE: Assign an included service to it's parent service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* included_handle - the handle of the included service
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerAddIncludedService(int server_if, int service_handle, int included_handle)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
uint16_t handle = 0;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, NULL, true))
logw("Server/Service not found while attempting to add included service\n");
else {
struct uuid uuid;
int includedSvc;
if (!aapiGattServerFindService(server_if, included_handle, NULL, &includedSvc, NULL, true) || !gattServiceGetUuid(includedSvc, &uuid))
logw("Failed to find or get UUID of included service\n");
else {
handle = gattServiceAddIncludedService(svc, &uuid);
if (handle)
ret = BT_STATUS_SUCCESS;
}
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.included_service_added_cb(ret, server_if, service_handle, handle);
return ret;
}
/*
* FUNCTION: aapiGattServerAddCharacteristic
* USE: Add a characteristic to a service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* andrUuid - the characteristic's UUID in android format
* properties - characteristic's properties (in gatt terms)
* permissions - characteristic's permissions in android terms
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerAddCharacteristic(int server_if, int service_handle, bt_uuid_t *andrUuid, int properties, int permissions)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
uint16_t handle = 0;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, NULL, true))
logw("Server/Service not found while attempting to add characteristic\n");
else {
struct uuid uuid;
aapiGattUuidFromAndroidUuid(&uuid, andrUuid);
handle = gattServiceAddChar(svc, &uuid, properties, permissions);
if (handle)
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.characteristic_added_cb(ret, server_if, andrUuid, service_handle, handle);
return ret;
}
/*
* FUNCTION: aapiGattServerAddDescriptor
* USE: Add a descriptor to a given service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* andrUuid - the descriptor's UUID in android format
* permissions - characteristic's permissions in android terms
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerAddDescriptor(int server_if, int service_handle, bt_uuid_t *andrUuid, int permissions)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
uint16_t handle = 0;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, NULL, true))
logw("Server/Service not found while attempting to add descriptor\n");
else {
struct uuid uuid;
aapiGattUuidFromAndroidUuid(&uuid, andrUuid);
handle = gattServiceAddCharDescr(svc, &uuid, permissions);
if (handle)
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.descriptor_added_cb(ret, server_if, andrUuid, service_handle, handle);
return ret;
}
/*
* FUNCTION: aapiGattServerStartService
* USE: Starts a local service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* transport - which transport to use
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerStartService(int server_if, int service_handle, int transport)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, NULL, true))
logw("Server/Service not found while attempting to start service\n");
else {
bool useLE = transport == GATT_TRANSPORT_LE || transport == GATT_TRANSPORT_LE_BR_EDR;
bool useEDR = transport == GATT_TRANSPORT_BR_EDR || transport == GATT_TRANSPORT_LE_BR_EDR;
if (!useLE && !useEDR)
logw("Cannot bring up service given no transports\n");
else {
if (gattServiceStart(svc, useLE, useEDR))
ret = BT_STATUS_SUCCESS;
}
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.service_started_cb(ret, server_if, service_handle);
return ret;
}
/*
* FUNCTION: aapiGattServerStopService
* USE: Stops a local service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerStopService(int server_if, int service_handle)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, NULL, true))
logw("Server/Service not found while attempting to stop service\n");
else {
if (gattServiceStop(svc))
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.service_stopped_cb(ret, server_if, service_handle);
return ret;
}
/*
* FUNCTION: aapiGattServerDeleteService
* USE: Delete a local service
* PARAMS: server_if - the server's reference number
* service_handle - the handle of the current service
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerDeleteService(int server_if, int service_handle)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
uint32_t idx;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, service_handle, &srv, &svc, &idx, true))
logw("Server/Service not found while attempting to delete service\n");
else {
memmove(srv->services + idx, srv->services + srv->numServices - 1, sizeof(struct aapiGattServerService));
srv->numServices--;
if (gattServiceDestroy(svc))
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
mCbks.service_deleted_cb(ret, server_if, service_handle);
return ret;
}
/*
* FUNCTION: aapiGattServerSendIndication
* USE: Send value indication to a remote device
* PARAMS: server_if - the server's reference number
* attribute_handle - the handle of the pertinent attribute
* conn_id - the connection reference number
* len - the length of the data
* confirm - need confirmation?
* p_value - the value
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerSendIndication(int server_if, int attribute_handle, int conn_id, int len, int confirm, char *p_value)
{
bt_status_t ret = BT_STATUS_FAIL;
struct aapiGattServer *srv;
int svc;
pthread_mutex_lock(&mAapiGattSrvLock);
if (!aapiGattServerFindService(server_if, attribute_handle, &srv, &svc, NULL, false))
logw("Server/service not found while attempting to send indication\n");
else {
if (gattServiceSendInd(svc, conn_id, attribute_handle, p_value, len, !!confirm, 0))
ret = BT_STATUS_SUCCESS;
}
pthread_mutex_unlock(&mAapiGattSrvLock);
/* XXX: callback here? */
return ret;
}
/*
* FUNCTION: aapiGattServerSendResponse
* USE: Send a response to a read/write operation
* PARAMS: conn_id - the connection reference number
* trans_id - transaction reference number
* status - the status to send
* response - the response to send
* RETURN: status
* NOTES: also calls callback with result if successful result here
*/
static bt_status_t aapiGattServerSendResponse(int conn_id, int trans_id, int status, btgatt_response_t *response)
{
gattSrvCbkReply(conn_id, trans_id, response->handle, status, response->attr_value.value, response->attr_value.len);
return BT_STATUS_SUCCESS;
}
/*
* FUNCTION: aapiGattServerGetProfileIface
* USE: Called to get profile pointer to the GATT server profile
* PARAMS: NONE
* RETURN: pointer to profile struct
* NOTES:
*/
void *aapiGattServerGetProfileIface(void)
{
static const btgatt_server_interface_t iface = {
.register_server = aapiGattServerRegisterServer,
.unregister_server = aapiGattServerUnregisterServer,
.connect = aapiGattServerConnect,
.disconnect = aapiGattServerDisconnect,
.add_service = aapiGattServerAddService,
.add_included_service = aapiGattServerAddIncludedService,
.add_characteristic = aapiGattServerAddCharacteristic,
.add_descriptor = aapiGattServerAddDescriptor,
.start_service = aapiGattServerStartService,
.stop_service = aapiGattServerStopService,
.delete_service = aapiGattServerDeleteService,
.send_indication = aapiGattServerSendIndication,
.send_response = aapiGattServerSendResponse,
};
return (void*)&iface;
}
/*
* FUNCTION: aapiGattServerInit
* USE: Init the GATT server profile
* PARAMS: cbks - callbacks back into java
* RETURN: status
* NOTES:
*/
bt_status_t aapiGattServerInit(const btgatt_server_callbacks_t *cbks)
{
bt_status_t ret;
memcpy(&mCbks, cbks, sizeof(mCbks));
pthread_mutex_lock(&mAapiGattSrvLock);
if (!mInited) {
if (!attInit()) {
loge("ATT init failed\n");
} else if (!gattProfileInit()) {
attDeinit();
loge("GATT init failed\n");
} else if (!gattBuiltinInit()) {
gattProfileDeinit();
attDeinit();
loge("GATT.GAP init failed\n");
} else
mInited = true;
}
ret = mInited ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
pthread_mutex_unlock(&mAapiGattSrvLock);
return ret;
}
/*
* FUNCTION: aapiGattServerDeinit
* USE: Deinit the GATT server profile
* PARAMS: NONE
* RETURN: NONE
* NOTES:
*/
void aapiGattServerDeinit(void)
{
pthread_mutex_lock(&mAapiGattSrvLock);
if (mInited) {
mInited = false;
gattBuiltinDeinit();
gattProfileDeinit();
attDeinit();
}
pthread_mutex_unlock(&mAapiGattSrvLock);
}