mirror of
https://github.com/google/pebble
synced 2026-10-17 00:34:11 +02:00
214 lines
8.1 KiB
C
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);
|
|
}
|