Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,761 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_ad_parse.h"
#include "applib/applib_malloc.auto.h"
#include "syscall/syscall.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/net.h"
#include <btutil/bt_uuid.h>
// -----------------------------------------------------------------------------
//! Internal parsed advertisement data structures.
// -----------------------------------------------------------------------------
//! AD TYPE Values as specified by the Bluetooth 4.0 Spec.
//! See "Appendix C (Normative): EIR and AD Formats" in Core_v4.0.pdf
typedef enum {
BLEAdTypeFlags = 0x01,
BLEAdTypeService16BitUUIDPartial = 0x02,
BLEAdTypeService16BitUUIDComplete = 0x03,
BLEAdTypeService32BitUUIDPartial = 0x04,
BLEAdTypeService32BitUUIDComplete = 0x05,
BLEAdTypeService128BitUUIDPartial = 0x06,
BLEAdTypeService128BitUUIDComplete = 0x07,
BLEAdTypeLocalNameShortened = 0x08,
BLEAdTypeLocalNameComplete = 0x09,
BLEAdTypeTxPowerLevel = 0x0a,
BLEAdTypeManufacturerSpecific = 0xff,
} BLEAdType;
// -----------------------------------------------------------------------------
//! AD DATA element header
typedef struct __attribute__((__packed__)) {
uint8_t length;
BLEAdType type:8;
} BLEAdElementHeader;
typedef struct __attribute__((__packed__)) {
BLEAdElementHeader header;
uint8_t data[];
} BLEAdElement;
// -----------------------------------------------------------------------------
// Consuming BLEAdData:
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed Service UUIDs element.
//! @param uuids The array with Service UUIDs
//! @param count The number of Service UUIDs the array contains
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseServicesCallback)(const Uuid uuids[], uint8_t count,
void *cb_data);
// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed Local Name element.
//! @param local_name_bytes This is a *NON* zero terminated UTF-8 string.
//! @param length The length of local_name_bytes
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseLocalNameCallback)(const uint8_t *local_name_bytes,
uint8_t length, void *cb_data);
// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a parsed TX Power Level element.
//! @param tx_power_level The TX Power Level value.
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseTXPowerLevelCallback)(int8_t tx_power_level,
void *cb_data);
// -----------------------------------------------------------------------------
//! Internal parser callback. Gets called for a Manufacturer Specific data elem.
//! @param company_id The Company ID
//! @param data The Manufacturer Specific data
//! @param length The length in bytes of data
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
//! @return true to continue parsing or false to stop after returning.
typedef bool (*BLEAdParseManufacturerSpecificCallback)(uint16_t company_id,
const uint8_t *data,
uint8_t length,
void *cb_data);
typedef struct {
BLEAdParseServicesCallback services_cb;
BLEAdParseLocalNameCallback local_name_cb;
BLEAdParseTXPowerLevelCallback tx_power_level_cb;
BLEAdParseManufacturerSpecificCallback manufacturer_cb;
} BLEAdParseCallbacks;
// -----------------------------------------------------------------------------
//! Parser for Services List data elements.
static bool parse_services_list(const BLEAdElement *elem,
const BLEAdParseCallbacks *callbacks,
void *cb_data) {
const uint8_t uuid_type = elem->header.type / 2;
// Most common is probably 128-bit UUID, then 16-bit, then 32-bit:
const size_t uuid_width = (uuid_type == 3) ? 16 : ((uuid_type == 1) ? 2 : 4);
const size_t num_uuids = (elem->header.length - 1 /* Type byte */)
/ uuid_width;
if (!num_uuids) {
return true; // continue parsing
}
Uuid uuids[num_uuids];
// Iterate through the list, expanding each UUID to 128-bit equivalents,
// then copying them into the uuids[] array:
const uint8_t *uuid_data = elem->data;
for (size_t i = 0; i < num_uuids; ++i) {
switch (uuid_type) {
case 1: { // 16 bit
uint16_t u16 = *(uint16_t *) uuid_data;
uuids[i] = bt_uuid_expand_16bit(u16);
uuid_data += sizeof(uint16_t);
break;
}
case 2: { // 32 bit
uint32_t u32 = *(uint32_t *) uuid_data;
uuids[i] = bt_uuid_expand_32bit(u32);
uuid_data += sizeof(uint32_t);
break;
}
case 3: // 128-bit
uuids[i] = UuidMakeFromLEBytes(uuid_data);
uuid_data += sizeof(Uuid);
break;
}
}
// Call back to client with parsed data:
return callbacks->services_cb(uuids, num_uuids, cb_data);
}
// -----------------------------------------------------------------------------
//! Parser for Local Name data element.
static bool parse_local_name(const BLEAdElement *elem,
const BLEAdParseCallbacks *callbacks,
void *cb_data) {
// Length of the raw string:
const uint8_t raw_length = elem->header.length - 1 /* -1 Type byte */;
if (!raw_length) {
return true; // continue parsing
}
// Call back to client with parsed data:
return callbacks->local_name_cb(elem->data, raw_length, cb_data);
}
// -----------------------------------------------------------------------------
//! Parser for TX Power Level data element.
static bool parse_power_level(const BLEAdElement *elem,
const BLEAdParseCallbacks *callbacks,
void *cb_data) {
if (elem->header.length != 2) {
// In case the length is not what it should be, do not add data element.
return true; // continue parsing
}
const int8_t tx_power_level = *(int8_t *)elem->data;
// Call back to client with parsed data:
return callbacks->tx_power_level_cb(tx_power_level, cb_data);
}
// -----------------------------------------------------------------------------
//! Parser for Manufacturer Specific data element.
static bool parse_manufact_spec(const BLEAdElement *elem,
const BLEAdParseCallbacks *callbacks,
void *cb_data) {
if (elem->header.length < 3) {
// The first 2 octets should be the Company Identifier Code
// (+1 for Type byte)
return true;
}
if (callbacks->manufacturer_cb) {
// Little-endian:
const uint16_t *company_id = (uint16_t *) elem->data;
// Call back to client with parsed data:
const uint8_t manufacturer_data_size =
elem->header.length - sizeof(*company_id) - 1 /* -1 Type Byte */;
return callbacks->manufacturer_cb(ltohs(*company_id),
elem->data + sizeof(*company_id),
manufacturer_data_size,
cb_data);
}
return true; // continue parsing
}
// -----------------------------------------------------------------------------
//! @param ad_data The advertising data and scan response data to parse.
//! @param callbacks Callbacks for each of the types of data the client wants
//! to receive parse callbacks for. You can leave a callback NULL if you are not
//! interested in receiving callbacks for that type of data.
//! @param cb_data Pointer to client data that is passed into the callback.
static void ble_ad_parse_ad_data(const BLEAdData *ad_data,
const BLEAdParseCallbacks *callbacks,
void *cb_data) {
const uint8_t *cursor = ad_data->data;
const uint8_t *end = cursor + ad_data->ad_data_length +
ad_data->scan_resp_data_length;
while (cursor < end) {
const BLEAdElement *elem = (const BLEAdElement *) cursor;
if (elem->header.length == 0) {
// We've hit a padding zero. We should be done, or this packet is corrupt.
return;
}
if (cursor + elem->header.length + 1 /* +1 length byte */ > end) {
return; // corrupted
}
bool (*parse_func)(const BLEAdElement *,
const BLEAdParseCallbacks *, void *) = NULL;
switch (elem->header.type) {
case BLEAdTypeService16BitUUIDPartial ... BLEAdTypeService128BitUUIDComplete:
if (callbacks->services_cb) {
parse_func = parse_services_list;
}
break;
case BLEAdTypeLocalNameShortened ... BLEAdTypeLocalNameComplete:
if (callbacks->local_name_cb) {
parse_func = parse_local_name;
}
break;
case BLEAdTypeTxPowerLevel:
if (callbacks->tx_power_level_cb) {
parse_func = parse_power_level;
}
break;
case BLEAdTypeManufacturerSpecific:
if (callbacks->manufacturer_cb) {
parse_func = parse_manufact_spec;
}
break;
default: // parse_func == NULL
break;
} // switch()
if (parse_func) {
if (!parse_func(elem, callbacks, cb_data)) {
// The callback indicated we should not continue parsing
return;
}
}
// The Length byte itself is not counted, so +1:
cursor += elem->header.length + 1;
}
}
// -----------------------------------------------------------------------------
//! ble_ad_includes_service() wrapper and helper function:
struct IncludesServiceCtx {
const Uuid *service_uuid;
bool included;
};
static bool includes_service_parse_cb(const Uuid uuids[], uint8_t count,
void *cb_data) {
struct IncludesServiceCtx *ctx = (struct IncludesServiceCtx *) cb_data;
for (int i = 0; i < count; ++i) {
if (uuid_equal(ctx->service_uuid, &uuids[i])) {
// Found!
ctx->included = true;
return false; // stop parsing
}
}
return true; // continue parsing
}
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid) {
struct IncludesServiceCtx ctx = {
.service_uuid = service_uuid,
.included = false,
};
const BLEAdParseCallbacks callbacks = (const BLEAdParseCallbacks) {
.services_cb = includes_service_parse_cb,
};
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
return ctx.included;
}
// -----------------------------------------------------------------------------
//! ble_ad_copy_service_uuids() wrapper and helper function:
struct CopyServiceUUIDsCtx {
Uuid *uuids_out;
const uint8_t max;
uint8_t copied;
uint8_t total;
};
static bool copy_services_parse_cb(const Uuid uuids[], uint8_t count,
void *cb_data) {
struct CopyServiceUUIDsCtx *ctx = (struct CopyServiceUUIDsCtx *)cb_data;
for (int i = 0; i < count; ++i) {
if (ctx->copied < ctx->max) {
// Still space left, so copy:
const Uuid *uuid = &uuids[i];
memcpy(&ctx->uuids_out[ctx->copied++], uuid, sizeof(Uuid));
}
++ctx->total;
}
return false; // stop parsing, only one Services UUID element allowed by spec
}
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
Uuid *uuids_out,
uint8_t num_uuids) {
struct CopyServiceUUIDsCtx ctx = {
.uuids_out = uuids_out,
.max = num_uuids,
};
const BLEAdParseCallbacks callbacks = {
.services_cb = copy_services_parse_cb,
};
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
return ctx.total;
}
// -----------------------------------------------------------------------------
//! ble_ad_get_tx_power_level() wrapper and helper function:
struct TxPowerLevelCtx {
int8_t *tx_power_level_out;
bool included;
};
static bool tx_power_level_cb(int8_t tx_power_level,
void *cb_data) {
struct TxPowerLevelCtx *ctx = (struct TxPowerLevelCtx *)cb_data;
*ctx->tx_power_level_out = tx_power_level;
ctx->included = true;
return false; // stop parsing
}
bool ble_ad_get_tx_power_level(const BLEAdData *ad,
int8_t *tx_power_level_out) {
struct TxPowerLevelCtx ctx = {
.tx_power_level_out = tx_power_level_out,
.included = false,
};
const BLEAdParseCallbacks callbacks = {
.tx_power_level_cb = tx_power_level_cb,
};
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
return ctx.included;
}
// -----------------------------------------------------------------------------
//! ble_ad_copy_local_name() wrapper and helper function:
struct LocalNameCtx {
char *buffer;
const uint8_t size;
size_t copied_size;
};
static bool copy_local_name_parse_cb(const uint8_t *local_name_bytes,
uint8_t length, void *cb_data) {
struct LocalNameCtx *ctx = (struct LocalNameCtx *)cb_data;
const uint8_t copied_size = MIN(ctx->size, length + 1 /* zero terminator */);
memcpy(ctx->buffer, local_name_bytes, copied_size - 1);
ctx->buffer[copied_size - 1] = 0; // zero terminator
ctx->copied_size = copied_size;
return false; // stop parsing
}
size_t ble_ad_copy_local_name(const BLEAdData *ad, char *buffer, size_t size) {
struct LocalNameCtx ctx = {
.buffer = buffer,
.size = MIN(size, 0xff),
.copied_size = 0,
};
const BLEAdParseCallbacks callbacks = {
.local_name_cb = copy_local_name_parse_cb,
};
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
return ctx.copied_size;
}
// -----------------------------------------------------------------------------
//! ble_ad_get_raw_data_size() wrapper and helper function:
size_t ble_ad_get_raw_data_size(const BLEAdData *ad) {
return ad->ad_data_length + ad->scan_resp_data_length;
}
// -----------------------------------------------------------------------------
//! ble_ad_copy_raw_data() wrapper and helper function:
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size) {
const size_t size_to_copy = ble_ad_get_raw_data_size(ad);
if (size < size_to_copy) {
return 0;
}
memcpy(buffer, ad->data, size_to_copy);
return size_to_copy;
}
// -----------------------------------------------------------------------------
//! ble_ad_get_manufacturer_specific_data() wrapper and helper function:
struct ManufacturerSpecificCtx {
uint16_t company_id;
uint8_t *buffer;
const uint8_t size;
size_t copied_size;
};
static bool copy_manufacturer_specific_parse_cb(uint16_t company_id,
const uint8_t *data,
uint8_t length,
void *cb_data) {
struct ManufacturerSpecificCtx *ctx =
(struct ManufacturerSpecificCtx *) cb_data;
const uint8_t copied_size = MIN(ctx->size, length);
memcpy(ctx->buffer, data, copied_size);
ctx->copied_size = copied_size;
ctx->company_id = company_id;
return false; // stop parsing
}
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
uint16_t *company_id,
uint8_t *buffer, size_t size) {
struct ManufacturerSpecificCtx ctx = {
.size = size,
.buffer = buffer,
};
const BLEAdParseCallbacks callbacks = {
.manufacturer_cb = copy_manufacturer_specific_parse_cb,
};
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
if (company_id) {
*company_id = ctx.company_id;
}
return ctx.copied_size;
}
// -----------------------------------------------------------------------------
// Creating BLEAdData:
// -----------------------------------------------------------------------------
//! Magic high bit used as scan_resp_data_length to indicate that the ad_data
//! has been finalized and the next write should be counted towards the scan
//! response payload. The maximum scan_resp_data_length is 31 bytes, so this
//! value lies outside of the valid range. This is basically a memory savings
//! optimization, saving another "finalized" bool.
#define BLE_AD_DATA_FINALIZED ((uint8_t) 0x80)
bool prv_ad_is_finalized(const BLEAdData *ad_data) {
// Scan response data has already been added / started
return (ad_data->scan_resp_data_length != 0);
}
void ble_ad_start_scan_response(BLEAdData *ad_data) {
if (prv_ad_is_finalized(ad_data)) {
// Already finalized
return;
}
ad_data->scan_resp_data_length = BLE_AD_DATA_FINALIZED;
}
// -----------------------------------------------------------------------------
//! Helper to calculate whether a number of bytes will still fit when appended.
//! @param length Pointer to the length of the part for which to try to fit in
//! size_to_write number of bytes.
//! @param size_to_write The number of bytes to that need to be appended.
//! @return Pointer to length if it could still appends size_to_write bytes or
//! NULL if not.
static uint8_t *prv_length_ptr_if_fits_or_null(uint8_t *length,
size_t size_to_write) {
// Unset finalized bit:
const uint8_t used = *length & (~BLE_AD_DATA_FINALIZED);
const uint8_t left = GAP_LE_AD_REPORT_DATA_MAX_LENGTH - used;
// Return pointer to the pointer if size_to_write will fit, or NULL otherwise:
return (left >= size_to_write) ? length : NULL;
}
// -----------------------------------------------------------------------------
//! @return Pointer to the length that is incremented when writing size_to_write
//! number of bytes, or NULL if there is not enough space left.
static uint8_t* prv_length_to_increase(BLEAdData *ad_data,
size_t size_to_write) {
if (ad_data->scan_resp_data_length) {
// The scan response part is already being populated:
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
size_to_write);
} else {
// The advertisement is still being populated:
uint8_t *length = prv_length_ptr_if_fits_or_null(&ad_data->ad_data_length,
size_to_write);
if (length) {
// Hurray, the size_to_write fits in the advertisement part:
return length;
}
// Last resort, try fitting into scan response part:
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
size_to_write);
}
}
// -----------------------------------------------------------------------------
bool prv_write_element_to_ad_data(BLEAdData *ad_data,
const BLEAdElement *element) {
if (!ad_data || !element) {
return false;
}
const size_t size_to_write = element->header.length + 1 /* Length Byte */;
uint8_t* length = prv_length_to_increase(ad_data, size_to_write);
if (!length) {
// Not enough space...
return false;
}
// Undo the magic number trick:
if (*length == BLE_AD_DATA_FINALIZED) {
*length = 0;
}
// Append the element to the end:
uint8_t * const end = ad_data->data +
ad_data->ad_data_length +
ad_data->scan_resp_data_length;
memcpy(end, (const uint8_t *) element, size_to_write);
// Length book-keeping:
*length += size_to_write;
return true;
}
// -----------------------------------------------------------------------------
BLEAdData * ble_ad_create(void) {
const size_t max_ad_data_size = sizeof(BLEAdData) +
(GAP_LE_AD_REPORT_DATA_MAX_LENGTH * 2);
BLEAdData *ad_data = applib_malloc(max_ad_data_size);
if (ad_data) {
memset(ad_data, 0, sizeof(BLEAdData));
}
return ad_data;
}
// -----------------------------------------------------------------------------
void ble_ad_destroy(BLEAdData *ad) {
applib_free(ad);
}
// -----------------------------------------------------------------------------
//! The smallest UUID width, by reducing the width when a UUID is based on the
//! Bluetooth base UUID. @see bt_uuid_expand_16bit @see bt_uuid_expand_32bit
static uint8_t prv_smallest_bt_uuid_width_in_bytes(const Uuid *uuid) {
const Uuid bt_uuid_base = bt_uuid_expand_16bit(0);
// The bytes after the first 4 contain the Bluetooth base.
// Check if the uuid is based off of the Bluetooth base UUID:
const bool is_bt_uuid_based = (memcmp(&bt_uuid_base.byte4, &uuid->byte4,
sizeof(Uuid) - offsetof(Uuid, byte4)) == 0);
if (!is_bt_uuid_based) {
// Not based on the Bluetooth base UUID, so use 128-bits:
return sizeof(Uuid);
}
if (uuid->byte0 || uuid->byte1) {
// If byte0 and byte1 not zero: 32-bit UUID, Bluetooth base UUID based:
return sizeof(uint32_t);
}
// If byte0 and byte1 are zero: 16-bit UUID, Bluetooth base UUID based:
return sizeof(uint16_t);
}
// -----------------------------------------------------------------------------
//! Finds the largest common UUID width. For UUIDs that are based on the
//! Bluetooth base UUID, a reduced width will be taken of either 16-bits or
//! 32-bits.
static uint8_t prv_largest_common_bt_uuid_width(const Uuid uuids[],
uint8_t num_uuids) {
uint8_t max_width_bytes = sizeof(uint16_t);
for (unsigned int i = 0; i < num_uuids; ++i) {
const Uuid *uuid = &uuids[i];
const uint8_t width_bytes = prv_smallest_bt_uuid_width_in_bytes(uuid);
max_width_bytes = MAX(width_bytes, max_width_bytes);
}
return max_width_bytes;
}
// -----------------------------------------------------------------------------
//! Helper to reduces a 128-bit UUID to 16-bits. Note: this function does not
//! check whether the original UUID is based on the Bluetooth base.
static uint16_t prv_convert_to_16bit_uuid(const Uuid *uuid) {
uint16_t uuid_16bits = 0;
// Use bytes 2-3 of the Uuid:
for (int i = 2; i < 4; ++i) {
uuid_16bits <<= 8;
uuid_16bits += ((const uint8_t *) uuid)[i];
}
return uuid_16bits;
}
// -----------------------------------------------------------------------------
//! Helper to reduces a 128-bit UUID to 32-bits. Note: this function does not
//! check whether the original UUID is based on the Bluetooth base.
static uint32_t prv_convert_to_32bit_uuid(const Uuid *uuid) {
uint32_t uuid_32bits = 0;
// Use bytes 0-3 of the Uuid:
for (int i = 0; i < 4; ++i) {
uuid_32bits <<= 8;
uuid_32bits += ((const uint8_t *) uuid)[i];
}
return uuid_32bits;
}
// -----------------------------------------------------------------------------
bool ble_ad_set_service_uuids(BLEAdData *ad,
const Uuid uuids[], uint8_t num_uuids) {
struct __attribute__((__packed__)) BLEAdElementService {
BLEAdElementHeader header;
union {
Uuid uuid_128[0];
uint32_t uuid_32[0];
uint16_t uuid_16[0];
};
};
const uint8_t max_width_bytes = prv_largest_common_bt_uuid_width(uuids,
num_uuids);
// Allocate buffer:
const size_t buffer_size = sizeof(struct BLEAdElementService) +
(max_width_bytes * num_uuids);
uint8_t element_buffer[buffer_size];
struct BLEAdElementService *element = (struct BLEAdElementService *) element_buffer;
// Set header fields (assume Complete):
switch (max_width_bytes) {
case 16: element->header.type = BLEAdTypeService128BitUUIDComplete; break;
case 4: element->header.type = BLEAdTypeService32BitUUIDComplete; break;
case 2: element->header.type = BLEAdTypeService16BitUUIDComplete; break;
default:
WTF;
}
element->header.length = buffer_size - 1 /* -1 Length byte */;
// Copy UUIDs:
for (unsigned int i = 0; i < num_uuids; ++i) {
switch (max_width_bytes) {
case 16: element->uuid_128[i] = uuids[i]; break;
case 4: element->uuid_32[i] = prv_convert_to_32bit_uuid(&uuids[i]); break;
case 2: element->uuid_16[i] = prv_convert_to_16bit_uuid(&uuids[i]); break;
default:
WTF;
}
}
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
}
// -----------------------------------------------------------------------------
bool ble_ad_set_local_name(BLEAdData *ad,
const char *local_name) {
if (!local_name) {
return false;
}
const size_t length = strlen(local_name);
uint8_t element_buffer[sizeof(BLEAdElement) + length];
BLEAdElement *element = (BLEAdElement *) &element_buffer;
element->header.length = length + 1 /* +1 Type byte */;
element->header.type = BLEAdTypeLocalNameComplete; /* assume Complete */
// Note: *not* zero terminated by design
memcpy(element->data, local_name, length);
return prv_write_element_to_ad_data(ad, element);
}
// -----------------------------------------------------------------------------
bool ble_ad_set_tx_power_level(BLEAdData *ad) {
uint8_t element_buffer[sizeof(BLEAdElement) + sizeof(int8_t)];
BLEAdElement *element = (BLEAdElement *) element_buffer;
element->header.length = sizeof(int8_t) + 1 /* +1 Type byte */;
element->header.type = BLEAdTypeTxPowerLevel;
*((int8_t *) element->data) = sys_ble_get_advertising_tx_power();
return prv_write_element_to_ad_data(ad, element);
}
// -----------------------------------------------------------------------------
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad, uint16_t company_id,
const uint8_t *data, size_t size) {
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
BLEAdElementHeader header;
uint16_t company_id;
uint8_t data[];
};
uint8_t element_buffer[sizeof(struct BLEAdElementManufacturerSpecific) + size];
struct BLEAdElementManufacturerSpecific *element =
(struct BLEAdElementManufacturerSpecific *) element_buffer;
element->header.length = sizeof(struct BLEAdElementManufacturerSpecific)
- 1 /* -1 Length byte */ + size;
element->header.type = BLEAdTypeManufacturerSpecific;
element->company_id = ltohs(company_id);
memcpy(element->data, data, size);
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
}
// -----------------------------------------------------------------------------
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags) {
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
BLEAdElementHeader header;
uint8_t flags;
} element = {
.header = {
.length = sizeof(struct BLEAdElementManufacturerSpecific)
- 1 /* -1 Length byte */,
.type = BLEAdTypeFlags,
},
.flags = flags,
};
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) &element);
}

View file

@ -0,0 +1,206 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! @file ble_ad_parse.h
//! API to serialize and deserialize advertisment and scan response payloads.
//!
//! Inbound payloads, as received using the ble_scan.h public API, can be
//! consumed/deserialized using the functions below.
//!
//! Outbound payloads can be created/serialized and then advertised using the
//! gap_le_advert.h functions. At the moment, there is no public API.
// -----------------------------------------------------------------------------
// Consuming BLEAdData:
// -----------------------------------------------------------------------------
//! Searching the advertisement data to check whether a given service UUID is
//! included.
//! @param ad The advertisement data
//! @param service_uuid The UUID of the service to look for
//! @return true if the service UUID was found, false if not
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid);
//! If present, copies the Service UUIDs from the advertisement data.
//! @param ad The advertisement data
//! @param[out] uuids_out An array of Uuid`s into which the found Service UUIDs
//! will be copied.
//! @param num_uuids The size of the uuids_out array.
//! @return The total number of found Service UUIDs. This might be a larger
//! number than num_uuids, if the passed array was not large enough to hold all
//! the UUIDs.
//! @note All UUIDs from advertisement data will be converted to their 128-bit
//! equivalents using the Bluetooth Base UUID using bt_uuid_expand_16bit or
//! bt_uuid_expand_32bit.
//! @see ble_ad_get_number_of_service_uuids
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
Uuid *uuids_out,
uint8_t num_uuids);
//! If present, returns the number of Service UUIDs the advertisement data
//! contains.
//! @param ad The advertisement data
//! @return If Service UUIDs data is present, the number of UUIDs is contains,
//! or zero otherwise.
uint8_t ble_ad_get_number_of_service_uuids(const BLEAdData *ad);
//! If present, gets the TX Power Level from the advertisement data.
//! @param ad The advertisement data
//! @param[out] tx_power_level_out Will contain the TX Power Level if the return
//! value is true.
//! @return true if the TX Power Level was found and assigned.
bool ble_ad_get_tx_power_level(const BLEAdData *ad, int8_t *tx_power_level_out);
//! If present, copies the Local Name from the advertisement data.
//! If the Local Name is bigger than the size of the buffer, only the part that
//! fits will be copied. For convenience, the copied c-string will always be
//! zero terminated for you.
//! @param ad The advertisement data
//! @param buffer The buffer into which to copy the Local Name, if found.
//! @param size The size of the buffer
//! @return The size of the Local Name in bytes, *including* zero terminator.
//! Note that this might be more than the size of the provided buffer.
//! If the Local Name was not found, the return value will be zero.
size_t ble_ad_copy_local_name(const BLEAdData *ad,
char *buffer, size_t size);
//! If the Local Name is present in the advertisment data, returns the number
//! of bytes a C-string needs to be to hold the full name.
//! @param ad The advertisement data
//! @return The size of the Local Name in bytes, *including* zero terminator.
//! If the Local Name is not present, zero is returned.
size_t ble_ad_get_local_name_buffer_size(const BLEAdData *ad);
//! If present, copies the Manufacturer Specific data from the advertisement
//! data. If the provided buffer is smaller than the size of the data, only
//! the data up that fits the buffer will be copied.
//! @param ad The advertisement data
//! @param[out] company_id_out Out: The Company Identifier Code, see
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
//! This will only get written, if there was Manufacturer Specific data in the
//! advertisement. In case you are not interested in getting the company ID,
//! NULL can be passed in.
//! @param buffer The buffer into which to copy the Manufacturer Specific Data,
//! if found.
//! @param size The size of the buffer
//! @return The size of the Manufacturer Specific data in bytes. Note that this
//! can be larger than the size of the provided buffer. If the Manufacturer
//! Specific data was not found, the return value will be zero.
//! @see ble_ad_get_manufacturer_specific_data_size
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
uint16_t *company_id_out,
uint8_t *buffer, size_t size);
//! Gets the size in bytes of Manufacturer Specific data in the advertisment.
//! @param ad The advertisement data
//! @return The size of the data, in bytes. If the Manufacturer Specific data is
//! not present, zero is returned.
size_t ble_ad_get_manufacturer_specific_data_size(const BLEAdData *ad);
//! Gets the size in bytes of the raw advertisement and scan response data.
//! @param ad The advertisement data
//! @return The size of the raw advertisement and scan response data in bytes.
size_t ble_ad_get_raw_data_size(const BLEAdData *ad);
//! Copies the raw bytes of advertising and scan response data into a buffer.
//! If there was scan response data, it will be concatenated directly after the
//! advertising data.
//! @param ad The advertisement data
//! @param buffer The buffer into which to copy the raw bytes
//! @param size The size of the buffer
//! @return The number of bytes copied.
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size);
// -----------------------------------------------------------------------------
// Creating BLEAdData:
// -----------------------------------------------------------------------------
//! Creates a blank, mutable advertisement and scan response payload.
//! It can contain up to 31 bytes of advertisement data and up to 31 bytes of
//! scan response data. The underlying storage for this is automatically
//! allocated.
//! @note When adding elements to the BLEAdData, using the ble_ad_set_...
//! functions, elements will be added to the advertisement data part first.
//! If the element to add does not fit, the scan response is automatically
//! used. Added elements cannot span across the two parts.
//! @return The blank payload object.
//! @note BLEAdData created with this function must be destroyed at some point
//! in time using ble_ad_destroy()
//! @note Use the ble_ad_set... functions to write data into the object. The
//! data written to it will occupy the advertisement payload until there is not
//! enough space left, in which case all following data is written into the scan
//! response. @see ble_ad_start_scan_response()
BLEAdData* ble_ad_create(void);
//! Destroys an advertisement payload that was created earlier with
//! ble_ad_create().
void ble_ad_destroy(BLEAdData *ad);
//! Marks the start of the scan response and finalizes the advertisement
//! payload. This forces successive writes to be written to the scan response,
//! even though it would have fit into the advertisement payload.
void ble_ad_start_scan_response(BLEAdData *ad_data);
//! Writes the Service UUID list to the advertisement or scan response payload.
//! The list is assumed to be the complete list of Service UUIDs.
//! @param ad The advertisement payload as created earlier by ble_ad_create()
//! @param uuids Array of UUIDs to add to the list. If the UUIDs are all derived
//! from the Bluetooth SIG base UUID, this function will automatically use
//! a smaller Service UUID size if possible.
//! @see bt_uuid_expand_16bit
//! @see bt_uuid_expand_32bit
//! @param num_uuids Number of UUIDs in the uuids array.
//! @return true if the data was successfully written or false if not.
bool ble_ad_set_service_uuids(BLEAdData *ad,
const Uuid uuids[], uint8_t num_uuids);
//! Writes the Local Name to the advertisement or scan response payload.
//! @param ad The advertisement payload as created earlier by ble_ad_create()
//! @param local_name Zero terminated, UTF-8 string with the Local Name. The
//! name is assumed to be complete and not abbreviated.
//! @return true if the data was successfully written or false if not.
bool ble_ad_set_local_name(BLEAdData *ad,
const char *local_name);
//! Writes the TX Power Level to advertisement or scan response payload.
//! The actual transmission power level value is set automatically, based on the
//! value as used by the Bluetooth hardware.
//! @param ad The advertisement payload as created earlier by ble_ad_create()
//! @return true if the data was successfully written or false if not.
bool ble_ad_set_tx_power_level(BLEAdData *ad);
//! Writes Manufacturer Specific Data to advertisement or scan response payload.
//! @param ad The advertisement payload as created earlier by ble_ad_create()
//! @param company_id The Company Identifier Code, see
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
//! @param data The data
//! @param size The size of data in bytes
//! @return true if the data was successfully written or false if not.
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad,
uint16_t company_id,
const uint8_t *data, size_t size);
//! @internal -- Do not export
//! Writes the Flags AD Type to the advertisement or scan response payload.
//! @param ad The advertisement payload as created earlier by ble_ad_create()
//! @param flags The flags to write. See Core_v4.0.pdf Vol 3, Appendix C, 18.1.
//! @return true if the data was successfully written or false if not.
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags);

View file

@ -0,0 +1,66 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_app_support.h"
#include "comm/ble/gap_le_scan.h"
#include "comm/ble/gap_le_connect.h"
#include "comm/ble/gatt_client_operations.h"
#include "comm/ble/gatt_client_subscriptions.h"
#include "process_state/app_state/app_state.h"
//! @see ble_scan.c
extern void ble_scan_handle_event(PebbleEvent *e, void *context);
//! @see ble_central.c
extern void ble_central_handle_event(PebbleEvent *e, void *context);
//! @see ble_client.c
extern void ble_client_handle_event(PebbleEvent *e, void *context);
void ble_init_app_state(void) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
*ble_app_state = (const BLEAppState) {
// ble_scan_...:
.scan_service_info = (const EventServiceInfo) {
.type = PEBBLE_BLE_SCAN_EVENT,
.handler = ble_scan_handle_event,
},
// ble_central_...:
.connection_service_info = (const EventServiceInfo) {
.type = PEBBLE_BLE_CONNECTION_EVENT,
.handler = ble_central_handle_event,
},
// ble_client_...:
.gatt_client_service_info = (const EventServiceInfo) {
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
.handler = ble_client_handle_event,
},
};
}
void ble_app_cleanup(void) {
// Gets run on the KernelMain task.
// All kernel / shared resources for BLE that were allocated on behalf of the
// app must be freed / released here:
gap_le_stop_scan();
gap_le_connect_cancel_all(GAPLEClientApp);
gatt_client_subscriptions_cleanup_by_client(GAPLEClientApp);
gatt_client_op_cleanup(GAPLEClientApp);
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "ble_scan.h"
#include "ble_central.h"
#include "ble_client.h"
#include "applib/event_service_client.h"
typedef struct {
// Scanning
EventServiceInfo scan_service_info;
BLEScanHandler scan_handler;
// Connecting
EventServiceInfo connection_service_info;
BLEConnectionHandler connection_handler;
// GATT Client
EventServiceInfo gatt_client_service_info;
BLEClientServiceChangeHandler gatt_service_change_handler;
BLEClientReadHandler gatt_characteristic_read_handler;
BLEClientWriteHandler gatt_characteristic_write_handler;
BLEClientSubscribeHandler gatt_characteristic_subscribe_handler;
BLEClientReadDescriptorHandler gatt_descriptor_read_handler;
BLEClientWriteDescriptorHandler gatt_descriptor_write_handler;
uint8_t gatt_client_num_handlers;
} BLEAppState;
//! Initializes the static BLE state for the currently running app.
void ble_init_app_state(void);
//! Must be called by the kernel, after an app is killed to stop any ongoing
//! BLE activity that was running on behalf of the app.
void ble_app_cleanup(void);

View file

@ -0,0 +1,57 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_central.h"
#include "process_state/app_state/app_state.h"
#include "comm/ble/gap_le_connect.h"
static BTErrno prv_bt_errno_for_event(const PebbleBLEConnectionEvent *e) {
if (e->connected) {
return BTErrnoConnected;
}
// FIXME: PBL-35506 We need to re-evaluate what error code to actually use here
return e->hci_reason;
}
void ble_central_handle_event(PebbleEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
if (!ble_app_state->connection_handler) {
return;
}
const PebbleBLEConnectionEvent *conn_event = &e->bluetooth.le.connection;
const BTDeviceInternal device = PebbleEventToBTDeviceInternal(conn_event);
ble_app_state->connection_handler(device.opaque,
prv_bt_errno_for_event(conn_event));
}
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
const bool is_subscribed = (ble_app_state->connection_handler != NULL);
ble_app_state->connection_handler = handler;
if (handler) {
if (!is_subscribed) {
event_service_client_subscribe(&ble_app_state->connection_service_info);
}
} else {
if (is_subscribed) {
event_service_client_unsubscribe(&ble_app_state->connection_service_info);
}
}
return BTErrnoOK;
}

View file

@ -0,0 +1,86 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Callback that is called for each connection and disconnection event.
//! @param device The device that got (dis)connected
//! @param connection_status BTErrnoConnected if connected, otherwise the
//! reason for the disconnection: BTErrnoConnectionTimeout,
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
//! BTErrnoLocallyTerminatedByApp.
//! @note See additional notes with ble_central_set_connection_handler()
typedef void (*BLEConnectionHandler)(BTDevice device,
BTErrno connection_status);
//! Registers the connection event handler of the application.
//! This event handler will be called when connections and disconnection occur,
//! for devices for which ble_central_connect() has been called by the
//! application.
//! Only for successful connections and complete disconnections will the event
//! handler be called. Transient issues that might happen during connection
//! establishment will be not be reported to the application. Instead, the
//! system will attempt to initiate a connection to the device again.
//! If this is called again, the previous handler will be unregistered.
//! @param handler The connection event handler of the application
//! @return Always returns BTErrnoOK.
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler);
//! Attempts to initiate a connection from the application to another device.
//! In case there is no Bluetooth connection to the device yet, this function
//! configures the Bluetooth subsystem to scan for the specified
//! device and instructs it to connect to it as soon as a connectable
//! advertisement has been received. The application will need to register a
//! connection event handler prior to calling ble_central_connect(), using
//! ble_central_set_connection_handler().
//! Outstanding (auto)connection attempts can be cancelled using
//! ble_central_cancel_connect().
//! @note Connections are virtualized. This means that your application needs to
//! call the ble_central_connect, even though the device might already have an
//! actual Bluetooth connection already. This can be the case when connecting
//! to the user's phone: it is likely the system has created a Bluetooth
//! connection already, still the application has to connect internally in order
//! to use the connection.
//! @param device The device to connect to
//! @param auto_reconnect Pass in true to automatically attempt to reconnect
//! again if the connection is lost. The BLEConnectionHandler will be called for
//! each time the device is (re)connected.
//! Pass in false, if the system should connect once only.
//! The BLEConnectionHandler will be called up to one time only.
//! @param is_pairing_required If the application requires that the Bluetooth
//! traffic is encrypted, is_pairing_required can be set to true to let the
//! system transparently set up pairing, or reestablish encryption, if the
//! device is already paired. Only after this security procedure is finished,
//! the BLEConnectionHandler will be called. Note that not all devices support
//! pairing and one of the BTErrnoPairing... errors can result from a failed
//! pairing process. If the application does not require pairing, set to false.
//! @note It is possible that encryption is still enabled, even if the
//! application did not require this.
//! @return BTErrnoOK if the intent to connect was processed successfully, or
//! ... TODO
BTErrno ble_central_connect(BTDevice device,
bool auto_reconnect,
bool is_pairing_required);
//! Attempts to cancel the connection, as initiated by ble_central_connect().
//! The underlying Bluetooth connection might not be disconnected if the
//! connection is still in use by the system. However, as far as the application
//! is concerned, the device is disconnected and the connection handler will
//! be called with BTErrnoLocallyTerminatedByApp.
//! @return BTErrnoOK if the cancelling was successful, or ... TODO
BTErrno ble_central_cancel_connect(BTDevice device);

View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_characteristic.h"
#include "syscall/syscall.h"
bool ble_characteristic_is_readable(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
BLEAttributePropertyRead);
}
bool ble_characteristic_is_writable(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
BLEAttributePropertyWrite);
}
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
BLEAttributePropertyWriteWithoutResponse);
}
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
(BLEAttributePropertyNotify | BLEAttributePropertyIndicate));
}
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
BLEAttributePropertyNotify);
}
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic) {
return (sys_ble_characteristic_get_properties(characteristic) &
BLEAttributePropertyIndicate);
}

View file

@ -0,0 +1,99 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Gets the UUID for a characteristic.
//! @param characteristic The characteristic for which to get the UUID
//! @return The UUID of the characteristic
Uuid ble_characteristic_get_uuid(BLECharacteristic characteristic);
//! Gets the properties bitset for a characteristic.
//! @param characteristic The characteristic for which to get the properties
//! @return The properties bitset
BLEAttributeProperty ble_characteristic_get_properties(BLECharacteristic characteristic);
//! Gets whether the characteristic is readable or not.
//! @param characteristic The characteristic for which to get its readability.
//! @return true if the characteristic is readable, false if it is not.
bool ble_characteristic_is_readable(BLECharacteristic characteristic);
//! Gets whether the characteristic is writable or not.
//! @param characteristic The characteristic for which to get its write-ability.
//! @return true if the characteristic is writable, false if it is not.
bool ble_characteristic_is_writable(BLECharacteristic characteristic);
//! Gets whether the characteristic is writable without response or not.
//! @param characteristic The characteristic for which to get its write-ability.
//! @return true if the characteristic is writable without response, false if it is not.
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic);
//! Gets whether the characteristic is subscribable or not.
//! @param characteristic The characteristic for which to get its subscribability.
//! @return true if the characteristic is subscribable, false if it is not.
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic);
//! Gets whether the characteristic is notifiable or not.
//! @param characteristic The characteristic for which to get its notifiability.
//! @return true if the characteristic is notifiable, false if it is not.
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic);
//! Gets whether the characteristic is indicatable or not.
//! @param characteristic The characteristic for which to get its indicatability.
//! @return true if the characteristic is indicatable, false if it is not.
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic);
//! Gets the service that the characteristic belongs to.
//! @param characteristic The characteristic for which to find the service it
//! belongs to.
//! @return The service owning the characteristic
BLEService ble_characteristic_get_service(BLECharacteristic characteristic);
//! Gets the device that the characteristic belongs to.
//! @param characteristic The characteristic for which to find the device it
//! belongs to.
//! @return The device owning the characteristic.
BTDevice ble_characteristic_get_device(BLECharacteristic characteristic);
//! Gets the descriptors associated with the characteristic.
//! @param characteristic The characteristic for which to get the descriptors
//! @param[out] descriptors_out An array of pointers to descriptors, into which
//! the associated descriptors will be copied.
//! @param num_descriptors The size of the descriptors_out array.
//! @return The total number of descriptors for the service. This might be a
//! larger number than num_descriptors will contain, if the passed array was
//! not large enough to hold all the pointers.
//! @note For convenience, the services are owned by the system and references
//! to services, characteristics and descriptors are guaranteed to remain valid
//! *until the BLEClientServiceChangeHandler is called again* or until
//! application is terminated.
uint8_t ble_characteristic_get_descriptors(BLECharacteristic characteristic,
BLEDescriptor descriptors_out[],
uint8_t num_descriptors);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// (FUTURE / LATER / NOT SCOPED)
// Just to see how symmetric the Server APIs would be:
BLECharacteristic ble_characteristic_create(const Uuid *uuid,
BLEAttributeProperty properties);
BLECharacteristic ble_characteristic_create_with_descriptors(const Uuid *uuid,
BLEAttributeProperty properties,
BLEDescriptor descriptors[],
uint8_t num_descriptors);

View file

@ -0,0 +1,305 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define FILE_LOG_COLOR LOG_COLOR_BLUE
#include "ble_client.h"
#include "ble_app_support.h"
#include "applib/applib_malloc.auto.h"
#include "process_state/app_state/app_state.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include <stdint.h>
// TODO:
// - device name
// - connection speed (?)
// - app_info.json
// - airplane mode service / BT HW state
// - API to detect that accessory is currently already connected to the phone?
// - How to identify? iOS SDK does not expose addresses. Use DIS info? Fall
// back to device name?
static void prv_handle_services_added(
BLEClientServiceChangeHandler handler, BTDeviceInternal device, BTErrno status) {
BLEService services[BLE_GATT_MAX_SERVICES_CHANGED];
uint8_t num_services = sys_ble_client_copy_services(device, services,
BLE_GATT_MAX_SERVICES_CHANGED);
if (num_services != 0) {
handler(device.opaque, BLEClientServicesAdded, services, num_services, status);
}
}
// TODO (PBL-22086): We should really make this easier to do ...
// We can't directly dereference the service discovery info pointer from third
// party apps because it was malloc'ed from the kernel heap. Instead copy the
// info that is used onto a variable which has been allocated from the stack.
DEFINE_SYSCALL(void, sys_get_service_discovery_info, const PebbleBLEGATTClientServiceEvent *e,
PebbleBLEGATTClientServiceEventInfo *info) {
if (PRIVILEGE_WAS_ELEVATED) {
// Note: if we start storing services, we will need to update the size
syscall_assert_userspace_buffer(info, sizeof(*info));
}
*info = (PebbleBLEGATTClientServiceEventInfo) {
.type = e->info->type,
.device = e->info->device,
.status = e->info->status
};
}
static void prv_handle_service_change(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
const BLEClientServiceChangeHandler handler =
ble_app_state->gatt_service_change_handler;
if (!handler) {
return;
}
PebbleBLEGATTClientServiceEventInfo info;
sys_get_service_discovery_info((const PebbleBLEGATTClientServiceEvent *)e, &info);
switch (info.type) {
case PebbleServicesAdded:
prv_handle_services_added(handler, info.device, info.status);
break;
case PebbleServicesRemoved:
// TODO (PBL-22087): This is suboptimal. Before we release the BLE API, we should
// either communicate to the App all the handles which have changed or
// allow the getters for removed services to still work for the duration
// of the callback. For now just force a full handle flush and then resync the app
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
prv_handle_services_added(handler, info.device, info.status);
break;
case PebbleServicesInvalidateAll:
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
break;
default:
WTF;
}
}
typedef void (*GenericReadHandler)(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
uint16_t value_offset,
BLEGATTError error);
static void prv_consume_read_response(const PebbleBLEGATTClientEvent *e,
GenericReadHandler handler) {
uint8_t *value = NULL;
uint16_t value_length = e->value_length;
const uintptr_t object_ref = e->object_ref;
BLEGATTError gatt_error = e->gatt_error;
// Read Responses / Notifications with 0 length data must not be attempted to be consumed
if (value_length) {
value = (uint8_t *) applib_malloc(value_length);
if (!value) {
gatt_error = BLEGATTErrorLocalInsufficientResources;
value_length = 0;
}
// If there is a read response, we *must* consume it,
// otherwise the events and associated awaiting responses will
// get out of sync with each other.
sys_ble_client_consume_read(object_ref, value, &value_length);
}
if (handler) {
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
}
applib_free(value);
}
static void prv_consume_notifications(const PebbleBLEGATTClientEvent *e,
GenericReadHandler handler) {
uint8_t *value = NULL;
BLEGATTError gatt_error = e->gatt_error;
uint16_t heap_buffer_size = 0;
uint16_t value_length = 0;
bool has_more = sys_ble_client_get_notification_value_length(&value_length);
while (has_more) {
if (heap_buffer_size < value_length) {
const uint16_t new_heap_buffer_size = MIN(value_length, 64 /* arbitrary min size.. */);
applib_free(value);
value = (uint8_t *) applib_malloc(new_heap_buffer_size);
heap_buffer_size = value ? new_heap_buffer_size : 0;
}
if (!value) {
gatt_error = BLEGATTErrorLocalInsufficientResources;
value_length = 0;
}
uintptr_t object_ref;
// Consume, even if we didn't have enough memory, this will eat the notification and free up
// the space in the buffer.
const uint16_t next_value_length = sys_ble_client_consume_notification(&object_ref,
value, &value_length,
&has_more);
if (handler) {
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
}
value_length = next_value_length;
}
applib_free(value);
}
static void prv_handle_notifications(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
prv_consume_notifications(e, ble_app_state->gatt_characteristic_read_handler);
}
static void prv_handle_characteristic_read(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
prv_consume_read_response(e, ble_app_state->gatt_characteristic_read_handler);
}
static void prv_handle_characteristic_write(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
const BLEClientWriteHandler handler = ble_app_state->gatt_characteristic_write_handler;
if (handler) {
handler(e->object_ref, e->gatt_error);
}
}
static void prv_handle_characteristic_subscribe(const PebbleBLEGATTClientEvent *e) {
PBL_LOG(LOG_LEVEL_DEBUG, "TODO: GATT Client Event, subtype=%u", e->subtype);
}
static void prv_handle_descriptor_read(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
prv_consume_read_response(e, ble_app_state->gatt_descriptor_read_handler);
}
static void prv_handle_descriptor_write(const PebbleBLEGATTClientEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
const BLEClientWriteDescriptorHandler handler = ble_app_state->gatt_descriptor_write_handler;
if (handler) {
handler(e->object_ref, e->gatt_error);
}
}
static void prv_handle_buffer_empty(const PebbleBLEGATTClientEvent *e) {
// TODO
}
typedef void(*PrvHandler)(const PebbleBLEGATTClientEvent *);
static PrvHandler prv_handler_for_subtype(
PebbleBLEGATTClientEventType event_subtype) {
if (event_subtype >= PebbleBLEGATTClientEventTypeNum) {
WTF;
}
// MT: This is a bit smaller in code size than a switch():
static const PrvHandler handler[] = {
[PebbleBLEGATTClientEventTypeServiceChange] = prv_handle_service_change,
[PebbleBLEGATTClientEventTypeCharacteristicRead] = prv_handle_characteristic_read,
[PebbleBLEGATTClientEventTypeNotification] = prv_handle_notifications,
[PebbleBLEGATTClientEventTypeCharacteristicWrite] = prv_handle_characteristic_write,
[PebbleBLEGATTClientEventTypeCharacteristicSubscribe] = prv_handle_characteristic_subscribe,
[PebbleBLEGATTClientEventTypeDescriptorRead] = prv_handle_descriptor_read,
[PebbleBLEGATTClientEventTypeDescriptorWrite] = prv_handle_descriptor_write,
[PebbleBLEGATTClientEventTypeBufferEmpty] = prv_handle_buffer_empty,
};
return handler[event_subtype];
}
// Exported for ble_app_support.c
void ble_client_handle_event(PebbleEvent *e) {
const PebbleBLEGATTClientEvent *gatt_event = &e->bluetooth.le.gatt_client;
prv_handler_for_subtype(gatt_event->subtype)(gatt_event);
}
static BTErrno prv_set_handler(void *new_handler, off_t struct_offset_bytes) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
typedef void (*BLEGenericHandler)(void);
BLEGenericHandler *handler_storage =
(BLEGenericHandler *)(((uint8_t *) ble_app_state) + struct_offset_bytes);
const bool had_previous_handler = (*handler_storage == NULL);
*handler_storage = (BLEGenericHandler) new_handler;
if (had_previous_handler) {
if (new_handler) {
if (ble_app_state->gatt_client_num_handlers++ == 0) {
// First GATT handler to be registered.
// Subscribe to GATT Client event service:
event_service_client_subscribe(&ble_app_state->gatt_client_service_info);
}
}
} else {
if (!new_handler) {
if (--ble_app_state->gatt_client_num_handlers == 0) {
// Last GATT handler to be de-registered.
// Unsubscribe from GATT Client event service:
event_service_client_unsubscribe(&ble_app_state->gatt_client_service_info);
}
}
}
return BTErrnoOK;
}
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
uint8_t num_uuids) {
// TODO
return 0;
}
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_service_change_handler);
return prv_set_handler(handler, offset);
}
BTErrno ble_client_set_read_handler(BLEClientReadHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_characteristic_read_handler);
return prv_set_handler(handler, offset);
}
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_characteristic_write_handler);
return prv_set_handler(handler, offset);
}
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_characteristic_subscribe_handler);
return prv_set_handler(handler, offset);
}
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler) {
// TODO
return BTErrnoOther;
}
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_descriptor_write_handler);
return prv_set_handler(handler, offset);
}
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler handler) {
const off_t offset = offsetof(BLEAppState, gatt_descriptor_read_handler);
return prv_set_handler(handler, offset);
}

View file

@ -0,0 +1,345 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
typedef enum {
BLEClientServicesAdded,
BLEClientServicesRemoved,
BLEClientServicesInvalidateAll
} BLEClientServiceChangeUpdate;
//! Callback that is called when the services on a remote device that are
//! available to the application have changed. It gets called when:
//! + the initial discovery of services completes
//! + a new service has been discovered on the device after initial discovery
//! + a service has been removed
//! + the remote device has disconnected
//!
//! @note For convenience, the services are owned by the system and references
//! to services, characteristics and descriptors are guaranteed to remain valid
//! *until the service exposing the characteristic or descriptor is removed or
//! all services are invalidated* or until application is terminated.
//! @note References to services, characteristics and descriptors for the
//! specified device, that have been obtained in a previous callback to the
//! handler, are no longer valid. The application is responsible for cleaning up
//! these references.
//!
//! @param device The device associated with the service discovery process
//! @param update_type Defines what type of service update is being received
//! @param services Array of pointers to the discovered services. The array will
//! only contain Service UUIDs that matches the filter that was passed into the
//! ble_client_discover_services_and_characteristics() call.
//! @param num_services The number of discovered services matching the criteria.
//! @param status BTErrnoOK if the service discovery was completed successfully.
//! If the device was disconnected, BTErrnoConnectionTimeout,
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
//! BTErrnoLocallyTerminatedByApp will specify the reason of the disconnection.
//! The number of services will be zero upon a disconnection.
typedef void (*BLEClientServiceChangeHandler)(BTDevice device,
BLEClientServiceChangeUpdate update_type,
const BLEService services[],
uint8_t num_services,
BTErrno status);
//! Registers the callback that handles service changes. After the call to
//! this function returns, the application should be ready to handle calls to
//! the handler.
//! @param handler Pointer to the function that will handle the service changes
//! @return BTErrnoOK if the handler was registered successfully,
//! or TODO....
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler);
//! Registers the filter list of Service UUIDs.
//! @param service_uuids An array of the Service UUIDs that the application is
//! interested in and the system should filter by. Passing NULL will discover
//! all services on the device.
//! @param num_uuids The number of Uuid`s in the service_uuids array. Ignored
//! when NULL is passed for the service_uuids argument.
//! @return BTErrnoOK if the filter was set up successfully,
//! or TODO....
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
uint8_t num_uuids);
//! Starts a discovery of services and characteristics on a remote device.
//! The discovered services will be delivered to the application through the
//! BLEClientServiceChangeHandler. The results will be filtered with the list
//! of Service UUIDs as configured with ble_client_set_service_filter().
//! @param device The device for which to perform service discovery.
//! @return BTErrnoOK if the service discovery started successfully,
//! BTErrnoInvalidParameter if the device was not connected,
//! BTErrnoInvalidState if service discovery was already on-going, or
//! an internal error otherwise (>= BTErrnoInternalErrorBegin).
BTErrno ble_client_discover_services_and_characteristics(BTDevice device);
//! Different subscription types that can be used with ble_client_subscribe()
typedef enum {
//! No subscription.
BLESubscriptionNone = 0,
//! Notification subscription.
BLESubscriptionNotifications = (1 << 0),
//! Indication subscription.
BLESubscriptionIndications = (1 << 1),
//! Any subscription. Use this value with ble_client_subscribe(), in case
//! the application does not care about the type of subscription. If both
//! types are supported by the server, the notification subscription type
//! will be used.
BLESubscriptionAny = BLESubscriptionNotifications | BLESubscriptionIndications,
} BLESubscription;
//! Callback to receive the characteristic value, resulting from either
//! ble_client_read() and/or ble_client_subscribe().
//! @param characteristic The characteristic of the received value
//! @param value Byte-array containing the value
//! @param value_length The number of bytes the byte-array contains
//! @param value_offset The offset in bytes from the start of the characteristic
//! value that has been read.
//! @param error The error or status as returned by the remote server. If the
//! read was successful, this remote server is supposed to send
//! BLEGATTErrorSuccess.
typedef void (*BLEClientReadHandler)(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length,
uint16_t value_offset,
BLEGATTError error);
//! Callback to handle the response to a written characteristic, resulting from
//! ble_client_write().
//! @param characteristic The characteristic that was written to.
//! @param error The error or status as returned by the remote server. If the
//! write was successful, this remote server is supposed to send
//! BLEGATTErrorSuccess.
typedef void (*BLEClientWriteHandler)(BLECharacteristic characteristic,
BLEGATTError error);
//! Callback to handle the confirmation of a subscription or unsubscription to
//! characteristic value changes (notifications or indications).
//! @param characteristic The characteristic for which the client is now
//! (un)subscribed.
//! @param subscription_type The type of subscription. If the client is now
//! unsubscribed, the type will be BLESubscriptionNone.
//! @param The error or status as returned by the remote server. If the
//! (un)subscription was successful, this remote server is supposed to send
//! BLEGATTErrorSuccess.
typedef void (*BLEClientSubscribeHandler)(BLECharacteristic characteristic,
BLESubscription subscription_type,
BLEGATTError error);
//! Callback to handle the event that the buffer for outbound data is empty.
typedef void (*BLEClientBufferEmptyHandler)(void);
//! Registers the handler for characteristic value read operations.
//! @param read_handler Pointer to the function that will handle callbacks
//! with read characteristic values as result of calls to ble_client_read().
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_read_handler(BLEClientReadHandler read_handler);
//! Registers the handler for characteristic value write (with response)
//! operations.
//! @param write_handler Pointer to the function that will handle callbacks
//! for written characteristic values as result of calls to ble_client_write().
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler write_handler);
//! Registers the handler for characteristic value subscribe operations.
//! @param subscribe_handler Pointer to the function that will handle callbacks
//! for (un)subscription confirmations as result of calls to
//! ble_client_subscribe().
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler subscribe_handler);
//! Registers the handler to get called back when the buffer for outbound
//! data is empty again.
//! @param empty_handler Pointer to the function that will handle callbacks
//! for "buffer empty" events.
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler);
//! Gets the maximum characteristic value size that can be written. This size
//! can vary depending on the connected device.
//! @param device The device for which to get the maximum characteristic value
//! size.
//! @return The maximum characteristic value size that can be written.
uint16_t ble_client_get_maximum_value_length(BTDevice device);
//! Read the value of a characteristic.
//! A call to this function will result in a callback to the registered
//! BLEClientReadHandler handler. @see ble_client_set_read_handler.
//! @param characteristic The characteristic for which to read the value
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
BTErrno ble_client_read(BLECharacteristic characteristic);
//! Write the value of a characterstic.
//! A call to this function will result in a callback to the registered
//! BLEClientWriteHandler handler. @see ble_client_set_write_response_handler.
//! @param characteristic The characteristic for which to write the value
//! @param value Buffer with the value to write
//! @param value_length Number of bytes to write
//! @note Values must not be longer than ble_client_get_maximum_value_length().
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
BTErrno ble_client_write(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length);
//! Write the value of a characterstic without response.
//! @param characteristic The characteristic for which to write the value
//! @param value Buffer with the value to write
//! @param value_length Number of bytes to write
//! @note Values must not be longer than ble_client_get_maximum_value_length().
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
//! If the buffer for outbound data was full, BTErrnoNotEnoughResources will
//! be returned. When the buffer is emptied, the handler that is registered
//! using ble_client_set_buffer_empty_handler() will be called.
BTErrno ble_client_write_without_response(BLECharacteristic characteristic,
const uint8_t *value,
size_t value_length);
//! Subscribe to be notified or indicated of value changes of a characteristic.
//!
//! The value updates are delivered to the application through the
//! BLEClientReadHandler, which should be registered before calling this
//! function using ble_client_set_read_handler().
//!
//! There are two types of subscriptions: notifications and indications.
//! For notifications there is no flow-control. This means that notifications
//! can get dropped if the rate at which they are sent is too high. Conversely,
//! each indication needs an acknowledgement from the receiver before the next
//! one can get sent and is thus more reliable. The system performs
//! acknowledgements to indications automatically. Applications do not need to
//! worry about this, nor can they affect this.
//! @param characteristic The characteristic to subscribe to.
//! @param subscription_type The type of subscription to use.
//! If BLESubscriptionAny is used as subscription_type and both types are
//! supported by the server, the notification subscription type will be used.
//! @note This call does not block and returns quickly. A callback to
//! the BLEClientSubscribeHandler will happen at a later point in time, to
//! report the success or failure of the subscription. This handler should be
//! registered before calling this function using
//! ble_client_set_subscribe_handler().
//! @note Under the hood, this API writes to the Client Characteristic
//! Configuration Descriptor's Notifications or Indications enabled/disabled
//! bit.
//! @return BTErrnoOK if the subscription request was sent sucessfully, or
//! TODO...
BTErrno ble_client_subscribe(BLECharacteristic characteristic,
BLESubscription subscription_type);
//! Callback to receive the descriptor value, resulting from a call to
//! ble_client_read_descriptor().
//! @param descriptor The descriptor of the received value
//! @param value Byte-array containing the value
//! @param value_length The number of bytes the byte-array contains
//! @param value_offset The offset in bytes from the start of the descriptor
//! value that has been read.
//! @param error The error or status as returned by the remote server. If the
//! read was successful, this remote server is supposed to send
//! BLEGATTErrorSuccess.
typedef void (*BLEClientReadDescriptorHandler)(BLEDescriptor descriptor,
const uint8_t *value,
size_t value_length,
uint16_t value_offset,
BLEGATTError error);
//! Callback to handle the response to a written descriptor, resulting from
//! ble_client_write_descriptor().
//! @param descriptor The descriptor that was written to.
//! @param error The error or status as returned by the remote server. If the
//! write was successful, this remote server is supposed to send
//! BLEGATTErrorSuccess.
typedef void (*BLEClientWriteDescriptorHandler)(BLEDescriptor descriptor,
BLEGATTError error);
//! Registers the handlers for descriptor value write operations.
//! @param write_handler Pointer to the function that will handle callbacks
//! for written descriptor values as result of calls to
//! ble_client_write_descriptor().
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler write_handler);
//! Registers the handlers for descriptor value read operations.
//! @param read_handler Pointer to the function that will handle callbacks
//! with read descriptor values as result of calls to
//! ble_client_read_descriptor().
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler read_handler);
//! Write the value of a descriptor.
//! A call to this function will result in a callback to the registered
//! BLEClientWriteDescriptorHandler handler.
//! @see ble_client_set_descriptor_write_handler.
//! @param descriptor The descriptor for which to write the value
//! @param value Buffer with the value to write
//! @param value_length Number of bytes to write
//! @note Values must not be longer than ble_client_get_maximum_value_length().
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
BTErrno ble_client_write_descriptor(BLEDescriptor descriptor,
const uint8_t *value,
size_t value_length);
//! Read the value of a descriptor.
//! A call to this function will result in a callback to the registered
//! BLEClientReadDescriptorHandler handler.
//! @see ble_client_set_descriptor_read_handler.
//! @param descriptor The descriptor for which to read the value
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
BTErrno ble_client_read_descriptor(BLEDescriptor descriptor);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// (FUTURE / LATER / NOT SCOPED)
// Just to see how symmetric the Server APIs would be:
//! Opaque ATT request context
typedef void * BLERequest;
typedef void (*BLEServerWriteHandler)(BLERequest request,
BLECharacteristic characteristic,
BTDevice remote_device,
const uint8_t *value,
size_t value_length,
uint16_t value_offset);
typedef void (*BLEServerReadHandler)(BLECharacteristic characteristic,
BTDevice remote_device,
uint16_t value_offset);
typedef void (*BLEServerSubscribeHandler)(BLECharacteristic characteristic,
BTDevice remote_device,
BLESubscription subscription_type);
BTErrno ble_server_set_handlers(BLEServerReadHandler read_handler,
BLEServerWriteHandler write_handler,
BLEServerSubscribeHandler subscription_handler);
BTErrno ble_server_start_service(BLEService service);
BTErrno ble_server_stop_service(BLEService service);
BTErrno ble_server_respond_to_write(BLERequest request, BLEGATTError error);
BTErrno ble_server_respond_to_read(BLERequest request, BLEGATTError error,
const uint8_t *value, size_t value_length,
uint16_t value_offset);
BTErrno ble_server_send_update(BLECharacteristic characteristic,
const uint8_t *value, size_t value_length);
BTErrno ble_server_send_update_selectively(BLECharacteristic characteristic,
const uint8_t *value, size_t value_length,
const BTDevice *devices, uint8_t num_devices);

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Gets the UUID for a descriptor.
//! @param descriptor The descriptor for which to get the UUID.
//! @return The UUID of the descriptor
Uuid ble_descriptor_get_uuid(BLEDescriptor descriptor);
//! Gets the characteristic for a descriptor.
//! @param descriptor The descriptor for which to get the characteristic.
//! @return The characteristic
//! @note For convenience, the services are owned by the system and references
//! to services, characteristics and descriptors are guaranteed to remain valid
//! *until the BLEClientServiceChangeHandler is called again* or until
//! application is terminated.
BLECharacteristic ble_descriptor_get_characteristic(BLEDescriptor descriptor);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// (FUTURE / LATER / NOT SCOPED)
// Just to see how symmetric the Server APIs would be:
BLEDescriptor ble_descriptor_create(const Uuid *uuid,
BLEAttributeProperty properties);
BTErrno ble_descriptor_destroy(BLEDescriptor descriptor);

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_device.h"

View file

@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Copies the devices that are known to the system. This set includes all
//! paired devices (connected or not) and devices for which there is a Bluetooth
//! connection to the system (but not necessarily paired and not necessarily
//! connected to the application).
//! @param[out] devices_out An array of BTDevice`s into which the known
//! devices will be copied.
//! @param[in,out] num_devices_out In: the size of the devices_out array.
//! Out: the number of BTDevice`s that were copied.
//! @return The total number of known devices. This might be a larger
//! number than num_devices_out will contain, if the passed array was not large
//! enough to hold all the connected devices.
uint8_t ble_device_copy_known_devices(BTDevice *devices_out,
uint8_t *num_devices_out);

View file

@ -0,0 +1,145 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_ibeacon.h"
#include "applib/applib_malloc.auto.h"
#include "util/net.h"
#include <string.h>
// -----------------------------------------------------------------------------
//! Apple's iBeacon AD DATA format.
//! The byte-order of Apple's fields (uuid, major and minor) is Big Endian (!!!)
//! @see Apple's docs for more info: http://goo.gl/iOrnpj
//! @see StackOverflow distance/accuracy calculations: http://goo.gl/yH0ubM
static const uint16_t COMPANY_ID_APPLE = 0x004c;
static const uint8_t APPLE_TYPE_IBEACON = 0x02;
static const uint8_t APPLE_IBEACON_LENGTH = 0x15;
typedef struct __attribute__((__packed__)) {
//! @see APPLE_AD_TYPE_IBEACON
uint8_t type;
//! @see APPLE_IBEACON_LENGTH
uint8_t length;
//! The application "proximityUUID" of the iBeacon. Generally, multiple
//! iBeacons share one UUID and an (iOS) app scans for one particular UUID.
uint8_t uuid[16];
//! The most significant value in the beacon.
uint16_t major;
//! The least significant value in the beacon.
uint16_t minor;
//! The calibrated transmit power.
int8_t calibrated_tx_power;
} AdDataManufacturerSpecificAppleiBeacon;
// -----------------------------------------------------------------------------
// Accessors
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon) {
return ibeacon->uuid;
}
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon) {
return ibeacon->major;
}
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon) {
return ibeacon->minor;
}
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon) {
return ibeacon->distance_cm;
}
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
int8_t rssi) {
// Note, not yet exported to 3rd party apps so no padding necessary
BLEiBeacon *ibeacon = applib_malloc(sizeof(BLEiBeacon));
if (ibeacon && !ble_ibeacon_parse(ad, rssi, ibeacon)) {
// Failed to parse.
applib_free(ibeacon);
ibeacon = NULL;
}
return ibeacon;
}
void ble_ibeacon_destroy(BLEiBeacon *ibeacon) {
applib_free(ibeacon);
}
// -----------------------------------------------------------------------------
// Below is the iBeacon advertisement parsing code.
static uint16_t calculate_distance_cm(int8_t tx_power, int8_t rssi) {
return 0; // TODO
}
// -----------------------------------------------------------------------------
//! iBeacon Advertisement Data parser
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
BLEiBeacon *ibeacon_out) {
uint16_t company_id = 0;
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon;
const size_t size_copied =
ble_ad_copy_manufacturer_specific_data(ad, &company_id,
(uint8_t *) &raw_ibeacon,
sizeof(raw_ibeacon));
if (size_copied != sizeof(raw_ibeacon)) {
return false;
}
if (company_id == COMPANY_ID_APPLE &&
raw_ibeacon.type == APPLE_TYPE_IBEACON &&
raw_ibeacon.length == APPLE_IBEACON_LENGTH) {
const int8_t tx_power = raw_ibeacon.calibrated_tx_power;
*ibeacon_out = (const BLEiBeacon) {
.uuid = UuidMakeFromBEBytes(raw_ibeacon.uuid),
.major = ntohs(raw_ibeacon.major),
.minor = ntohs(raw_ibeacon.minor),
.distance_cm = calculate_distance_cm(tx_power, rssi),
.rssi = rssi,
.calibrated_tx_power = tx_power,
};
return true;
}
return false;
}
// -----------------------------------------------------------------------------
//! iBeacon Advertisement Data composer
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out) {
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon = {
.type = APPLE_TYPE_IBEACON,
.length = APPLE_IBEACON_LENGTH,
// Major/Minor are part of Apple's iBeacon spec and are Big Endian!
.major = htons(ibeacon_in->major),
.minor = htons(ibeacon_in->minor),
.calibrated_tx_power = ibeacon_in->calibrated_tx_power,
};
// Uuid is stored Big Endian on Pebble, so just copy over:
memcpy(&raw_ibeacon.uuid, &ibeacon_in->uuid, sizeof(Uuid));
return ble_ad_set_manufacturer_specific_data(ad_out, COMPANY_ID_APPLE,
(uint8_t *) &raw_ibeacon,
sizeof(raw_ibeacon));
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
#include "applib/bluetooth/ble_ad_parse.h"
//! Size in bytes of the iBeacon advertisement data, including the length and
//! AD Type bytes.
#define IBEACON_ADVERTISEMENT_DATA_SIZE (27)
//! Data structure representing an iBeacon advertisement.
typedef struct {
//! The application UUID that the iBeacon advertised. In iOS' CoreBluetooth,
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
Uuid uuid;
//! Custom value, most significant part.
uint16_t major;
//! Custom value, least significant part.
uint16_t minor;
//! Estimated distance to the iBeacon in centimeters. In iOS' CoreBluetooth,
//! this corresponds to the "accuracy" property of instances of CLBeacon.
uint16_t distance_cm;
//! The received signal strength from the iBeacon, in decibels.
int8_t rssi;
//! The calibrated power of the iBeacon. This is the RSSI measured at 1 meter
//! distance from the iBeacon. The iBeacon transmits this information in its
//! advertisment. Using this and the actual RSSI, the distance is estimated.
int8_t calibrated_tx_power;
} BLEiBeacon;
//! Gets the UUID of the iBeacon.
//! @param The iBeacon
//! @return The UUID that the iBeacon advertised. In iOS' CoreBluetooth,
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon);
//! Gets the major value of the iBeacon.
//! @param The iBeacon
//! @return The major, custom value.
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon);
//! Gets the minor value of the iBeacon.
//! @param The iBeacon
//! @return The minor, custom value.
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon);
//! Gets the estimated distance to the iBeacon, in centimeters.
//! @param The iBeacon
//! @return The estimated distance in centimeters.
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon);
//! Create BLEiBeacon from advertisement data.
//! @param ad Advertisement data, as acquired from the BLEScanHandler callback.
//! @param rssi The RSSI of the advertisement, as acquired from the
//! BLEScanHandler callback.
//! @return BLEiBeacon object if iBeacon data is found, or NULL if the
//! advertisement data did not contain valid iBeacon data.
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
int8_t rssi);
//! Destroys an BLEiBeacon object and frees its resources that were allocated
//! earlier by ble_ibeacon_create_from_ad_data().
//! @param ibeacon Reference to the BLEiBeacon to destroy.
void ble_ibeacon_destroy(BLEiBeacon *ibeacon);
// -----------------------------------------------------------------------------
//! Internal iBeacon Advertisement Data parser
//! @param ad The raw advertisement data
//! @param rssi The RSSI of the advertisement
//! @param[out] ibeacon_out Will contain the parsed iBeacon data if the call
//! returns true.
//! @return true if the data element was succesfully parsed as iBeacon,
//! false if the data element could not be parsed as iBeacon.
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
BLEiBeacon *ibeacon_out);
// -----------------------------------------------------------------------------
//! Internal iBeacon Advertisement Data serializer
//! @param ibeacon_in The iBeacon structure to serialize. The rssi and
//! distance_cm fields are ignored because they are only valid for received
//! iBeacon packets.
//! @param[out] ad_out The advertisement payload to write the data into.
//! @return true if the iBeacon data was written successfully.
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out);

View file

@ -0,0 +1,109 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_scan.h"
#include "ble_ad_parse.h"
#include "applib/app_logging.h"
#include "applib/applib_malloc.auto.h"
#include "process_state/app_state/app_state.h"
#include "comm/ble/gap_le_scan.h"
#include "kernel/events.h"
#include "syscall/syscall.h"
void ble_scan_handle_event(PebbleEvent *e) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
if (!ble_app_state->scan_handler) {
return;
}
// Use the same buffer size as the kernel itself:
uint8_t *buffer = (uint8_t *) applib_malloc(GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
if (!buffer) {
APP_LOG(LOG_LEVEL_ERROR, "Need %u bytes of heap for ble_scan_start()",
GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
return;
}
uint16_t size = GAP_LE_SCAN_REPORTS_BUFFER_SIZE;
sys_ble_consume_scan_results(buffer, &size);
if (size == 0) {
goto finally;
}
// Walk all the reports in the buffer:
const uint8_t *cursor = buffer;
while (cursor < buffer + size) {
const GAPLERawAdReport *report = (GAPLERawAdReport *)cursor;
const BTDeviceInternal device = (const BTDeviceInternal) {
.address = report->address.address,
.is_classic = false,
.is_random_address = report->is_random_address,
};
// Call the scan handler for each report:
ble_app_state->scan_handler(device.opaque, report->rssi, &report->payload);
const size_t report_length = sizeof(GAPLERawAdReport) +
report->payload.ad_data_length +
report->payload.scan_resp_data_length;
cursor += report_length;
}
finally:
applib_free(buffer);
}
BTErrno ble_scan_start(BLEScanHandler handler) {
if (!handler) {
return (BTErrnoInvalidParameter);
}
BLEAppState *ble_app_state = app_state_get_ble_app_state();
if (ble_app_state->scan_handler) {
return (BTErrnoInvalidState);
}
const bool result = sys_ble_scan_start();
if (!result) {
return BTErrnoOther;
}
ble_app_state->scan_handler = handler;
event_service_client_subscribe(&ble_app_state->scan_service_info);
return BTErrnoOK;
}
BTErrno ble_scan_stop(void) {
BLEAppState *ble_app_state = app_state_get_ble_app_state();
if (!ble_app_state->scan_handler) {
return (BTErrnoInvalidState);
}
const bool result = sys_ble_scan_stop();
if (!result) {
return BTErrnoOther;
}
event_service_client_unsubscribe(&ble_app_state->scan_service_info);
ble_app_state->scan_handler = NULL;
return BTErrnoOK;
}
bool ble_scan_is_scanning(void) {
return sys_ble_scan_is_scanning();
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Callback that is called for each advertisment that is found while scanning
//! using ble_scan_start().
//! @param device The device from which the advertisment originated.
//! @param rssi The RSSI (Received Signal Strength Indication) of the
//! advertisement.
//! @param advertisement_data The payload of the advertisement. When there was
//! a scan response, this payload will contain the data of the scan response
//! as well. The information in the payload can be accessed using the
//! ble_ad_... functions, @see for example ble_ad_copy_local_name() and
//! ble_ad_includes_service().
//! @note The advertisement_data is cleaned up by the system automatically
//! immediately after returning from this callback. Do not keep around
//! any long-lived references around to the advertisement_data.
//! @note Do not use ble_ad_destroy() on the advertisement_data.
typedef void (*BLEScanHandler)(BTDevice device,
int8_t rssi,
const BLEAdData *advertisement_data);
//! Start scanning for advertisements. Pebble will scan actively, meaning it
//! will perform scan requests whenever the advertisement is scannable.
//! @param handler The callback to handle the found advertisments. It must not
//! be NULL.
//! @return BTErrnoOK if scanning started successfully, BTErrnoInvalidParameter
//! if the handler was invalid or BTErrnoInvalidState if scanning had already
//! been started.
BTErrno ble_scan_start(BLEScanHandler handler);
//! Stop scanning for advertisements.
//! @return BTErrnoOK if scanning stopped successfully, or TODO...
BTErrno ble_scan_stop(void);
//! @return True if the system is scanning for advertisements or false if not.
bool ble_scan_is_scanning(void);

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_security.h"

View file

@ -0,0 +1,94 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//------------------------------------------------------------------------------
// Out-Of-Band additions
//! "Out-of-Band" (OOB) is one of the mechanisms to exchange a shared secret
//! during a pairing procedure between two devices. "PIN" and "Just Works" are
//! the two other exchange mechanisms that the Bluetooth 4.0 Specification
//! defines, but both are susceptible to eavesdropping of the exchanged keys.
//! OOB provides better protection against this, by offering a way to exchange
//! the shared secret via a communications channel other than Bluetooth itself
//! (hence the name "Out-of-Band"). Of course, this is only more secure if the
//! channel through which the OOB data is exchanged itself is harder to
//! eavesdrop.
//!
//! The exchanged OOB data is used as Temporary-Key (TK) to encrypt the
//! connection during the one-time pairing information exchange. Part of this
//! information exchange are Long-Term-Key(s) (LTK) that will be used upon
//! successive reconnections. For more details, see Bluetooth 4.0 Specification,
//! Volume 3, Part H, 2.3.5, "Pairing Algorithms".
//!
//! The OOB APIs enable the application to provide the system with OOB data.
//! The application will need to indicate to the system for what devices it
//! is capable of providing OOB data. Later, when a pairing procedure takes
//! place with an OOB-enabled device, the system will ask the application to
//! provide that OOB data.
//!
//! It is up to the application and the manufacturer of the device how the OOB
//! data is exchanged between the application and the remote device. Examples of
//! how this can be done:
//! - The application could generate the OOB data and show a QR code containing
//! the data on the screen of the Pebble that is then read by the device.
//! - If the device is connected to the Internet, the OOB data could be
//! provisioned to Pebble via a web service. The application would use the
//! JavaScript APIs to fetch the data from the web service and transfer the
//! data to the application on the watch using the AppMessage APIs.
//! Pointer to a function that can provide Out-Of-Band keys.
//! @see ble_security_set_oob_handler() and ble_security_enable_oob()
//! @param device The device for which the OOB key needs to be provided
//! @param oob_key_buffer_out The buffer into which the OOB key should be
//! written.
//! @param oob_key_buffer_size The size of the buffer in bytes. Currently only
//! keys of 128-bit (16 byte) size are supported.
//! @return true if the OOB key was written or false if no OOB data could be
//! provided for the device.
typedef bool (*BLESecurityOOBHandler)(BTDevice device,
uint8_t *oob_key_buffer_out,
size_t oob_key_buffer_size);
//! Registers a permanent callback function that is responsible for providing
//! Out-Of-Band (OOB) keys. The callback is guaranteed to get called only for
//! devices for which the application has enabled OOB using
//! ble_security_enable_oob(). The callback will get called by the system during
//! a pairing procedure, but only if the remote device indicated to have OOB
//! data as well.
//! @param oob_handler Pointer to the function that will provide OOB key data.
//! @return BTErrnoOK if the call was successful, or TODO...
BTErrno ble_security_set_oob_handler(BLESecurityOOBHandler oob_handler);
//! Enable or disable Out-Of-Band pairing for the device.
//! This function is a way to indicate to the system that the application has
//! Out-Of-Band data that can be used when pairing with a particular device.
//! @note The application is encouraged to configure OOB as soon as possible,
//! *before* connecting to any devices. If the application supports OOB, but
//! enables is *after* connecting, there is a chance that the remote requests to
//! start pairing before your application has had the chance to enable OOB.
//! @note After terminating the application, the system will automatically
//! disable OOB for any devices it had enabled OOB for. Upon re-launching the
//! application, it will need to re-enable OOB if required.
//! @param device The device for which to enable or disable OOB
//! @param enable Pass in true to enable OOB for the device, or false to disable
//! @return BTErrnoOK if the call was successful, or TODO...
BTErrno ble_security_enable_oob(BTDevice device, bool enable);

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ble_service.h"

View file

@ -0,0 +1,88 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bluetooth/bluetooth_types.h>
//! Gets the characteristics associated with a service.
//! @param service The service for which to get the characteristics
//! @param[out] characteristics_out An array of pointers to characteristics,
//! into which the associated characteristics will be copied.
//! @param num_characteristics The size of the characteristics_out array.
//! @return The total number of characteristics for the service. This might be a
//! larger number than num_in_out will contain, if the passed array was not
//! large enough to hold all the pointers.
//! @note For convenience, the services are owned by the system and references
//! to services, characteristics and descriptors are guaranteed to remain valid
//! *until the BLEClientServiceChangeHandler is called again* or until
//! application is terminated.
uint8_t ble_service_get_characteristics(BLEService service,
BLECharacteristic characteristics_out[],
uint8_t num_characteristics);
//! Gets the Service UUID of a service.
//! @param service The service for which to get the Service UUID.
//! @return The 128-bit Service UUID, or UUID_INVALID if the service reference
//! was invalid.
//! @note The returned UUID is always a 128-bit UUID, even if the device
//! its interal GATT service database uses 16-bit or 32-bit Service UUIDs.
//! @see bt_uuid_expand_16bit for a macro that converts 16-bit UUIDs to 128-bit
//! equivalents.
//! @see bt_uuid_expand_32bit for a macro that converts 32-bit UUIDs to 128-bit
//! equivalents.
Uuid ble_service_get_uuid(BLEService service);
//! Gets the device that hosts the service.
//! @param service The service for which to find the device it belongs to.
//! @return The device hosting the service, or an invalid device if the service
//! reference was invalid. Use bt_device_is_invalid() to test whether the
//! returned device is invalid.
BTDevice ble_service_get_device(BLEService service);
//! Gets the services that are references by a service as "Included Service".
//! @param service The service for which to get the included services
//! @param[out] included_services_out An array of pointers to services,
//! into which the included services will be copied.
//! @param num_services the size of the included_services_out array.
//! @return The total number of included services for the service. This might be
//! a larger number than included_services_out can contain, if the passed array
//! was not large enough to hold all the pointers.
//! @note For convenience, the services are owned by the system and references
//! to services, characteristics and descriptors are guaranteed to remain valid
//! *until the BLEClientServiceChangeHandler is called again* or until
//! application is terminated.
uint8_t ble_service_get_included_services(BLEService service,
BLEService included_services_out[],
uint8_t num_services);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// (FUTURE / LATER / NOT SCOPED)
// Just to see how symmetric the Server APIs could be:
// creates + adds to GATT DB (?)
// Services aren't supposed to change. Pass everything into the 'create' call:
BLEService ble_service_create(const Uuid *service_uuid,
BLECharacteristic characteristics[],
uint8_t num_characteristics);
void ble_service_set_included_services(BLEService service,
BLEService included_services[],
uint8_t num_included_services);
// removes from GATT DB (?) + destroys
void ble_service_destroy(BLEService service);