Files
pebble/src/fw/comm/ble/gatt_service_changed.c
T

214 lines
8.1 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.
*/
#include "gatt_service_changed.h"
#include "gap_le_connection.h"
#include "comm/bt_lock.h"
#include "kernel/pbl_malloc.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "util/net.h"
#include "system/hexdump.h"
#include <bluetooth/gatt.h>
#include <btutil/bt_device.h>
extern BTErrno gatt_client_discovery_rediscover_all(const BTDeviceInternal *device);
extern void gatt_client_discovery_handle_service_range_change(GAPLEConnection *connection,
ATTHandleRange *range);
extern void gatt_client_discovery_discover_range(GAPLEConnection *connection,
ATTHandleRange *hdl_range);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Client -- Pebble consuming the remote's "Service Changed" characteristic
static void prv_rediscover_kernelbg_cb(void *data) {
// Rediscover the world:
BTDeviceInternal *device = (BTDeviceInternal *) data;
const BTErrno e = gatt_client_discovery_rediscover_all(device);
kernel_free(device);
if (e != BTErrnoOK) {
PBL_LOG(LOG_LEVEL_ERROR, "Service Changed couldn't restart discovery: %i", e);
}
}
//! @note bt_lock is assumed to be taken by the caller
bool gatt_service_changed_client_handle_indication(struct GAPLEConnection *connection,
uint16_t att_handle, const uint8_t *value,
uint16_t value_length) {
if (connection->gatt_service_changed_att_handle != att_handle) {
return false;
}
if (value_length != sizeof(ATTHandleRange)) {
PBL_LOG(LOG_LEVEL_ERROR, "Service Changed Indication incorrect length: %u", value_length);
// Pretend we ate the indication. There will be no GAPLECharacteristic in the system that will
// match this ATT handle anyway.
return true;
}
ATTHandleRange *range = (ATTHandleRange *) value;
PBL_LOG(LOG_LEVEL_DEBUG, "Service Changed Indication: %x - %x", range->start, range->end);
// Initiate rediscovery on KernelBG if the Server is asking us to rediscover everything
// (See "2.5.2 Attribute Caching" in BT Core Specification)
if ((range->start == 0x001 && range->end == 0xFFFF)) {
BTDeviceInternal *device = (BTDeviceInternal *) kernel_malloc_check(sizeof(BTDeviceInternal));
*device = connection->device;
system_task_add_callback(prv_rediscover_kernelbg_cb, device);
return true;
}
// My understanding is if we get here we will receive a range of handles for
// _one_ service. "The start Attribute Handle shall be the start Attribute
// Handle of the service definition containing the change and the end
// Attribute Handle shall be the last Attribute Handle of the service
// definition containing the change" (Core Spec 2.5.2 Attribute Caching)
// Send an event to notify us that service was removed/added
gatt_client_discovery_handle_service_range_change(connection, range);
// Let's spawn a new discovery
gatt_client_discovery_discover_range(connection, range);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Server -- Pebble serving up the "Service Changed" characteristic to the remote
// Work-around for iOS issue where sending the indication immediately when iOS subscribes to the
// characteristic is causing problems:
// BTServer: ATT Failed to locate GAP primary service on device ...
#define GATT_SERVICE_CHANGED_INDICATION_DELAY_MS (10000)
#define GATT_SERVICE_CHANGED_INDICATION_MAX_TIMES (5)
static uint32_t s_service_changed_indications_left;
// For unit testing
void gatt_service_changed_server_init(void) {
s_service_changed_indications_left = 0;
}
void gatt_service_changed_server_handle_fw_update(void) {
// Once set, just keep it set until the next "normal" reboot.
// It will cause Pebble to send the "Service Change" indication to be sent every time the other
// end subscribes to it, causing the remote cache to be invalidated each time and force the
// remote to do discover services again. However, cap the total number of times we send the
// "Service Change" indication:
s_service_changed_indications_left = GATT_SERVICE_CHANGED_INDICATION_MAX_TIMES;
}
void bt_driver_cb_gatt_service_changed_server_confirmation(
const GattServerChangedConfirmationEvent *event) {
if (event->status_code != HciStatusCode_Success) {
PBL_LOG(LOG_LEVEL_ERROR, "Service Changed indication confirmation failure (timed out?) %"PRIu32,
(uint32_t)event->status_code);
}
}
void gatt_service_changed_server_cleanup_by_connection(GAPLEConnection *connection) {
if (connection->gatt_service_changed_indication_timer != TIMER_INVALID_ID) {
new_timer_delete(connection->gatt_service_changed_indication_timer);
connection->gatt_service_changed_indication_timer = TIMER_INVALID_ID;
}
}
static void prv_send_service_changed_indication(void *ctx) {
}
static void prv_send_indication_timer_cb(void *ctx) {
GAPLEConnection *connection = (GAPLEConnection *)ctx;
system_task_add_callback(prv_send_service_changed_indication, connection);
}
void bt_driver_cb_gatt_service_changed_server_subscribe(
const GattServerSubscribeEvent *event) {
bt_lock();
{
const bool subscribed = event->is_subscribing;
if (subscribed) {
PBL_LOG(LOG_LEVEL_DEBUG, "Remote subscribed to Service Changed characteristic");
GAPLEConnection *connection = gap_le_connection_by_addr(&event->dev_address);
if (!connection || connection->has_sent_gatt_service_changed_indication) {
// Already sent indication once during the lifetime of this connection, don't send again.
goto unlock;
}
// PRF will always send a "Service Changed" indication:
#if !RECOVERY_FW
if (s_service_changed_indications_left <= 0) {
goto unlock;
}
#endif
PBL_LOG(LOG_LEVEL_INFO, "Indicating Service Changed to remote device");
// Work-around for iOS issue (see comment above), send the indication after a short delay:
connection->gatt_service_changed_indication_timer = new_timer_create();
new_timer_start(connection->gatt_service_changed_indication_timer,
GATT_SERVICE_CHANGED_INDICATION_DELAY_MS, prv_send_indication_timer_cb,
connection, 0);
--s_service_changed_indications_left;
// Don't send again for this connection:
connection->has_sent_gatt_service_changed_indication = true;
}
}
unlock:
bt_unlock();
}
void bt_driver_cb_gatt_service_changed_server_read_subscription(
const GattServerReadSubscriptionEvent *event) {
bt_lock();
{
bt_driver_gatt_respond_read_subscription(event->transaction_id, 0 /* not subscribed */);
}
bt_unlock();
}
void bt_driver_cb_gatt_client_discovery_handle_service_changed(GAPLEConnection *connection,
uint16_t handle) {
bt_lock();
{
connection->gatt_service_changed_att_handle = handle;
}
bt_unlock();
}
//////////////////////////////////
// Prompt commands
//////////////////////////////////
void command_ble_send_service_changed_indication(void) {
prv_send_service_changed_indication(gap_le_connection_any());
}
void command_ble_rediscover(void) {
// assume we only have one connection for debug
GAPLEConnection *conn_hdl = gap_le_connection_any();
BTDeviceInternal *device = (BTDeviceInternal *) kernel_malloc_check(sizeof(BTDeviceInternal));
*device = conn_hdl->device;
system_task_add_callback(prv_rediscover_kernelbg_cb, device);
}