Files
pebble/src/fw/applib/bluetooth/ble_client.c
2025-01-27 11:38:16 -08:00

306 lines
12 KiB
C

/*
* 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);
}