mirror of
https://github.com/google/pebble.git
synced 2025-03-23 04:02:19 +00:00
942 lines
38 KiB
C
942 lines
38 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 <bluetooth/gatt.h>
|
|
|
|
#include "comm/ble/gatt_client_subscriptions.h"
|
|
#include "comm/ble/gap_le_connection.h"
|
|
#include "comm/ble/gap_le_task.h"
|
|
#include "comm/ble/gatt_service_changed.h"
|
|
|
|
#include "clar.h"
|
|
|
|
#include <btutil/bt_device.h>
|
|
#include <btutil/bt_uuid.h>
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "semphr.h"
|
|
|
|
// Fakes
|
|
///////////////////////////////////////////////////////////
|
|
|
|
#include "fake_events.h"
|
|
#include "fake_pbl_malloc.h"
|
|
#include "fake_GATTAPI.h"
|
|
#include "fake_GATTAPI_test_vectors.h"
|
|
#include "fake_new_timer.h"
|
|
#include "fake_queue.h"
|
|
#include "fake_system_task.h"
|
|
|
|
#include "stubs_regular_timer.h"
|
|
|
|
static BTErrno s_write_descriptor_cccd_result;
|
|
static BLEDescriptor s_last_cccd_ref;
|
|
static uint16_t s_last_cccd_value;
|
|
|
|
BTErrno gatt_client_op_write_descriptor_cccd(BLEDescriptor cccd, const uint16_t *value) {
|
|
s_last_cccd_ref = cccd;
|
|
s_last_cccd_value = *value;
|
|
return s_write_descriptor_cccd_result;
|
|
}
|
|
|
|
// FIXME: PBL-23945
|
|
void fake_kernel_malloc_mark(void) { }
|
|
void fake_kernel_malloc_mark_assert_equal(void) { }
|
|
|
|
// Stubs
|
|
///////////////////////////////////////////////////////////
|
|
|
|
#include "stubs_analytics.h"
|
|
#include "stubs_bluetopia_interface.h"
|
|
#include "stubs_bt_driver_gatt.h"
|
|
#include "stubs_bt_lock.h"
|
|
#include "stubs_logging.h"
|
|
#include "stubs_mutex.h"
|
|
#include "stubs_passert.h"
|
|
#include "stubs_rand_ptr.h"
|
|
#include "stubs_tick.h"
|
|
|
|
void core_dump_reset(bool is_forced) {
|
|
}
|
|
|
|
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
|
callback(data);
|
|
}
|
|
|
|
uint16_t gaps_get_starting_att_handle(void) {
|
|
return 4;
|
|
}
|
|
|
|
// Helpers
|
|
///////////////////////////////////////////////////////////
|
|
|
|
extern void gatt_client_subscriptions_handle_server_notification(GAPLEConnection *connection,
|
|
uint16_t att_handle,
|
|
const uint8_t *att_value,
|
|
uint16_t att_length);
|
|
|
|
extern void gatt_client_subscriptions_handle_write_cccd_response(BLEDescriptor cccd,
|
|
BLEGATTError error);
|
|
|
|
extern uint16_t gatt_client_characteristic_get_handle_and_connection(
|
|
BLECharacteristic characteristic_ref,
|
|
GAPLEConnection **connection);
|
|
|
|
extern SemaphoreHandle_t gatt_client_subscription_get_semaphore(void);
|
|
extern void gatt_client_subscription_cleanup(void);
|
|
|
|
#define TEST_GATT_CONNECTION_ID (1234)
|
|
#define BOGUS_CHARACTERISTIC ((BLECharacteristic) 888)
|
|
#define BOGUS_ATT_HANDLE ((uint16_t) 0xffff)
|
|
|
|
static BTDeviceInternal s_device;
|
|
static GAPLEConnection *s_connection;
|
|
static uint16_t s_handle;
|
|
|
|
static BTDeviceInternal prv_dummy_device(uint8_t octet) {
|
|
BTDeviceAddress address = {
|
|
.octets = {
|
|
[0] = octet,
|
|
[1] = octet,
|
|
[2] = octet,
|
|
[3] = octet,
|
|
[4] = octet,
|
|
[5] = octet,
|
|
},
|
|
};
|
|
BTDevice device = bt_device_init_with_address(address, true /* is_random */);
|
|
return *(BTDeviceInternal *)(&device);
|
|
}
|
|
|
|
static BTDeviceInternal prv_connected_dummy_device(uint8_t octet) {
|
|
BTDeviceInternal device = prv_dummy_device(octet);
|
|
gap_le_connection_add(&device, NULL, true /* local_is_master */);
|
|
s_connection = gap_le_connection_by_device(&device);
|
|
s_connection->gatt_connection_id = TEST_GATT_CONNECTION_ID;
|
|
return device;
|
|
}
|
|
|
|
static void prv_assert_no_event(void) {
|
|
PebbleEvent event = fake_event_get_last();
|
|
cl_assert_equal_i(event.type, PEBBLE_NULL_EVENT);
|
|
}
|
|
|
|
static void prv_assert_subscription_event(BLECharacteristic characteristic,
|
|
BLESubscription subscription_type,
|
|
BLEGATTError error, bool kernel, bool app) {
|
|
PebbleEvent event = fake_event_get_last();
|
|
cl_assert_equal_i(event.type, PEBBLE_BLE_GATT_CLIENT_EVENT);
|
|
cl_assert_equal_i(event.bluetooth.le.gatt_client.subtype,
|
|
PebbleBLEGATTClientEventTypeCharacteristicSubscribe);
|
|
cl_assert_equal_i(event.bluetooth.le.gatt_client.subscription_type, subscription_type);
|
|
cl_assert_equal_i(event.bluetooth.le.gatt_client.object_ref, characteristic);
|
|
cl_assert_equal_i(event.bluetooth.le.gatt_client.gatt_error, error);
|
|
PebbleTaskBitset task_mask = ~0;
|
|
if (kernel) {
|
|
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientKernel);
|
|
}
|
|
if (app) {
|
|
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientApp);
|
|
}
|
|
cl_assert_equal_i(event.task_mask, task_mask);
|
|
}
|
|
|
|
static void prv_assert_notification_event_ext(BLECharacteristic characteristic,
|
|
const uint8_t *value, uint16_t assert_value_length,
|
|
bool kernel, bool app, bool should_consume) {
|
|
PebbleEvent event = fake_event_get_last();
|
|
cl_assert_equal_i(event.type, PEBBLE_BLE_GATT_CLIENT_EVENT);
|
|
cl_assert_equal_i(event.bluetooth.le.gatt_client.subtype,
|
|
PebbleBLEGATTClientEventTypeNotification);
|
|
PebbleTaskBitset task_mask = ~0;
|
|
if (kernel) {
|
|
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientKernel);
|
|
}
|
|
if (app) {
|
|
task_mask &= ~gap_le_pebble_task_bit_for_client(GAPLEClientApp);
|
|
}
|
|
cl_assert_equal_i(event.task_mask, task_mask);
|
|
|
|
BLECharacteristic characteristic_out;
|
|
uint8_t *buffer = (uint8_t *) malloc(assert_value_length);
|
|
memset(buffer, 0, assert_value_length);
|
|
if (kernel) {
|
|
GATTBufferedNotificationHeader header = {};
|
|
gatt_client_subscriptions_get_notification_header(GAPLEClientKernel, &header);
|
|
cl_assert_equal_i(header.value_length, assert_value_length);
|
|
cl_assert_equal_i(header.characteristic, characteristic);
|
|
|
|
if (should_consume) {
|
|
uint16_t value_length = assert_value_length;
|
|
gatt_client_subscriptions_consume_notification(&characteristic_out, buffer, &value_length,
|
|
GAPLEClientKernel, NULL);
|
|
cl_assert_equal_i(memcmp(buffer, value, value_length), 0);
|
|
cl_assert_equal_i(characteristic_out, characteristic);
|
|
}
|
|
}
|
|
memset(buffer, 0, assert_value_length);
|
|
if (app) {
|
|
GATTBufferedNotificationHeader header = {};
|
|
gatt_client_subscriptions_get_notification_header(GAPLEClientApp, &header);
|
|
cl_assert_equal_i(header.value_length, assert_value_length);
|
|
cl_assert_equal_i(header.characteristic, characteristic);
|
|
|
|
if (should_consume) {
|
|
uint16_t value_length = assert_value_length;
|
|
gatt_client_subscriptions_consume_notification(&characteristic_out, buffer, &value_length,
|
|
GAPLEClientApp, NULL);
|
|
cl_assert_equal_i(memcmp(buffer, value, value_length), 0);
|
|
cl_assert_equal_i(characteristic_out, characteristic);
|
|
}
|
|
}
|
|
free(buffer);
|
|
}
|
|
|
|
static void prv_assert_notification_event(BLECharacteristic characteristic,
|
|
const uint8_t *value, uint16_t assert_value_length,
|
|
bool kernel, bool app) {
|
|
prv_assert_notification_event_ext(characteristic, value, assert_value_length, kernel, app,
|
|
true /* should_consume */);
|
|
}
|
|
|
|
static void prv_simulate_and_assert_discovery_of_one_service(const BTDeviceInternal *device) {
|
|
// Simulate discovery of Blood Pressure service:
|
|
fake_gatt_put_discovery_indication_blood_pressure_service(TEST_GATT_CONNECTION_ID);
|
|
// Simulate discovery of random 128-bit service:
|
|
fake_gatt_put_discovery_indication_random_128bit_uuid_service(TEST_GATT_CONNECTION_ID);
|
|
fake_gatt_put_discovery_complete_event(GATT_SERVICE_DISCOVERY_STATUS_SUCCESS,
|
|
TEST_GATT_CONNECTION_ID);
|
|
}
|
|
|
|
static BLECharacteristic prv_get_indicatable_characteristic(void) {
|
|
// The Blood Pressure Service UUID:
|
|
Uuid service_uuid = bt_uuid_expand_16bit(0x1810);
|
|
BLEService service;
|
|
uint8_t num_copied = gatt_client_copy_service_refs_matching_uuid(&s_device, &service, 1,
|
|
&service_uuid);
|
|
cl_assert_equal_i(num_copied, 1);
|
|
|
|
// UUID for indicatable Pressure Measurement characteristic:
|
|
Uuid characteristic_uuid = bt_uuid_expand_16bit(0x2a35);
|
|
BLECharacteristic characteristic;
|
|
num_copied = gatt_client_service_get_characteristics_matching_uuids(service, &characteristic,
|
|
&characteristic_uuid, 1);
|
|
cl_assert_equal_i(num_copied, 1);
|
|
|
|
GAPLEConnection *connection;
|
|
s_handle = gatt_client_characteristic_get_handle_and_connection(characteristic, &connection);
|
|
|
|
return characteristic;
|
|
}
|
|
|
|
static void prv_confirm_cccd_write(BLEGATTError error) {
|
|
gatt_client_subscriptions_handle_write_cccd_response(s_last_cccd_ref, error);
|
|
}
|
|
|
|
// Tests
|
|
///////////////////////////////////////////////////////////
|
|
|
|
void test_gatt_client_subscriptions__initialize(void) {
|
|
s_write_descriptor_cccd_result = BTErrnoOK;
|
|
|
|
fake_pbl_malloc_clear_tracking();
|
|
fake_event_init();
|
|
gap_le_connection_init();
|
|
gatt_client_subscription_boot();
|
|
|
|
// Prepare connected device with Blood Pressure GATT service discovered:
|
|
s_device = prv_connected_dummy_device(1);
|
|
cl_assert_equal_i(gatt_client_discovery_discover_all(&s_device), BTErrnoOK);
|
|
prv_simulate_and_assert_discovery_of_one_service(&s_device);
|
|
|
|
fake_event_clear_last();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__cleanup(void) {
|
|
for (GAPLEClient c = 0; c < GAPLEClientNum; ++c) {
|
|
gatt_client_subscriptions_cleanup_by_client(c);
|
|
}
|
|
gap_le_connection_deinit();
|
|
gatt_client_subscription_cleanup();
|
|
|
|
fake_pbl_malloc_check_net_allocs();
|
|
fake_pbl_malloc_clear_tracking();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// gatt_client_subscriptions_subscribe
|
|
|
|
void test_gatt_client_subscriptions__subscribe_invalid_characteristic(void) {
|
|
fake_kernel_malloc_mark();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(BOGUS_CHARACTERISTIC,
|
|
BLESubscriptionAny, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_no_cccd(void) {
|
|
// The random 128-bit Service UUID:
|
|
Uuid service_uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63,
|
|
0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB0);
|
|
BLEService service;
|
|
uint8_t num_copied = gatt_client_copy_service_refs_matching_uuid(&s_device, &service, 1,
|
|
&service_uuid);
|
|
cl_assert_equal_i(num_copied, 1);
|
|
|
|
// UUID for Characteristic that has no CCCD:
|
|
Uuid characteristic_uuid = UuidMake(0xF7, 0x68, 0x09, 0x5B, 0x1B, 0xFA, 0x4F, 0x63,
|
|
0x97, 0xEE, 0xFD, 0xED, 0xAC, 0x66, 0xF9, 0xB1);
|
|
BLECharacteristic characteristic;
|
|
num_copied = gatt_client_service_get_characteristics_matching_uuids(service, &characteristic,
|
|
&characteristic_uuid, 1);
|
|
cl_assert_equal_i(num_copied, 1);
|
|
|
|
fake_kernel_malloc_mark();
|
|
// Try to subscribe to the non-subscribe-able characteristic:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic,
|
|
BLESubscriptionAny, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
|
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_unsupported_subscription_type(void) {
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
fake_kernel_malloc_mark();
|
|
// Try to subscribe for notifications to the indicatable (but not notify-able) characteristic:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNotifications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidParameter);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
|
|
// Try to subscribe for indications to the indicatable characteristic:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_already_subscribed(void) {
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
fake_event_clear_last();
|
|
fake_kernel_malloc_mark();
|
|
// Subscribe again:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidState);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__unsubscribe_pending_subscription(void) {
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
fake_event_clear_last();
|
|
fake_kernel_malloc_mark();
|
|
// Un-subscribe, while subscribing process is still pending:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidState);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_oom_for_subscription_allocation(void) {
|
|
fake_kernel_malloc_mark();
|
|
fake_malloc_set_largest_free_block(sizeof(GATTClientSubscriptionNode) - 1);
|
|
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
|
BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoNotEnoughResources);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_oom_for_buffer_allocation(void) {
|
|
fake_kernel_malloc_mark();
|
|
fake_malloc_set_largest_free_block(sizeof(GATTClientSubscriptionNode));
|
|
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
|
BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoNotEnoughResources);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_cccd_write_error(void) {
|
|
s_write_descriptor_cccd_result = -1;
|
|
|
|
fake_kernel_malloc_mark();
|
|
// Try to subscribe for indications to the indicatable characteristic:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(prv_get_indicatable_characteristic(),
|
|
BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, -1);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__unsubscribe_no_clients_subscribed(void) {
|
|
fake_kernel_malloc_mark();
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidState);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__unsubscribe_not_subscribed_but_other_client_is(void) {
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications, GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
fake_kernel_malloc_mark();
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoInvalidState);
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_first_subscriber(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
|
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
|
true /* kernel */, false /* app */);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__subscribe_not_first_subscriber(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
// Subscribe kernel:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
// Subscribe app:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
// App should get event immediately:
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
|
false /* kernel */, true /* app */);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__two_subscribers_before_cccd_write_response(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
// Subscribe kernel:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
|
|
|
s_last_cccd_value = ~0;
|
|
|
|
// Subscribe app:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
// Should not be written again, because BLESubscriptionIndications was already written:
|
|
cl_assert_equal_i(s_last_cccd_value, (uint16_t) ~0);
|
|
|
|
fake_event_clear_last();
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
|
|
// Expect one event for both Kernel + App:
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionIndications, BLEGATTErrorSuccess,
|
|
true /* kernel */, true /* app */);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__unsubscribe_last_subscriber(void) {
|
|
BTErrno e;
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
fake_kernel_malloc_mark();
|
|
|
|
// Subscribe kernel:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
// Unsubscribe kernel:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Kernel should get event immediately:
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorSuccess,
|
|
true /* kernel */, false /* app */);
|
|
|
|
// CCCD value should be 0 now:
|
|
cl_assert_equal_i(s_last_cccd_value, 0);
|
|
|
|
// Verify everything is cleaned up:
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__unsubscribe_not_last_subscriber(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
// Subscribe kernel:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
// Subscribe app:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
|
|
s_last_cccd_value = ~0;
|
|
|
|
// Unsubscribe kernel:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionNone, GAPLEClientKernel);
|
|
// Kernel should get event immediately:
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorSuccess,
|
|
true /* kernel */, false /* app */);
|
|
|
|
// CCCD value should not be written, because app is still subscribed:
|
|
cl_assert_equal_i(s_last_cccd_value, (uint16_t) ~0);
|
|
}
|
|
|
|
|
|
void test_gatt_client_subscriptions__subscribe_failed_cccd_write(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
fake_kernel_malloc_mark();
|
|
|
|
// Subscribe kernel:
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
cl_assert_equal_i(s_last_cccd_value, BLESubscriptionIndications);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorInvalidHandle);
|
|
|
|
// Expect kernel event with 'BLESubscriptionNone' type and error bubbled up:
|
|
prv_assert_subscription_event(characteristic, BLESubscriptionNone, BLEGATTErrorInvalidHandle,
|
|
true /* kernel */, false /* app */);
|
|
|
|
// Verify everything is cleaned up:
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// gatt_client_subscriptions_handle_server_notification &
|
|
// gatt_client_subscriptions_consume_notification
|
|
|
|
void test_gatt_client_subscriptions__notification_but_no_subscribers(void) {
|
|
const uint8_t value = 0xAA;
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
&value, sizeof(value));
|
|
prv_assert_no_event();
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__cccd_write_confirmation_but_no_subscription(void) {
|
|
s_last_cccd_ref = 1;
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
|
|
// This used to cause a crash:
|
|
// https://pebbletechnology.atlassian.net/browse/PBL-23455
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__notification_single_subscriber(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
// Nothing to be read before getting the notification:
|
|
bool has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp,
|
|
NULL);
|
|
cl_assert_equal_b(has_notification, false);
|
|
|
|
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, sizeof(value));
|
|
|
|
prv_assert_notification_event(characteristic, value, sizeof(value),
|
|
false /* kernel */, true /* app */);
|
|
|
|
// Nothing to be read after "consuming" it:
|
|
has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp, NULL);
|
|
cl_assert_equal_b(has_notification, false);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__zero_length_notification(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle, NULL, 0);
|
|
|
|
prv_assert_notification_event(characteristic, NULL, 0, false /* kernel */, true /* app */);
|
|
|
|
// Nothing to be read after "consuming" it:
|
|
bool has_notification = gatt_client_subscriptions_get_notification_header(GAPLEClientApp, NULL);
|
|
cl_assert_equal_b(has_notification, false);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__notification_app_and_kernel_subscribers(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Subscribe kernel:
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
fake_event_reset_count();
|
|
|
|
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, sizeof(value));
|
|
cl_assert_equal_i(fake_event_get_count(), 1);
|
|
|
|
// Send another notification, before reading out the previous one:
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, sizeof(value));
|
|
// Only one event should have been put on the queue:
|
|
cl_assert_equal_i(fake_event_get_count(), 1);
|
|
|
|
// Assert 2 events can be read out:
|
|
prv_assert_notification_event(characteristic, value, sizeof(value),
|
|
true /* kernel */, true /* app */);
|
|
prv_assert_notification_event(characteristic, value, sizeof(value),
|
|
true /* kernel */, true /* app */);
|
|
|
|
// Send the 3rd notification:
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, sizeof(value));
|
|
cl_assert_equal_i(fake_event_get_count(), 2);
|
|
prv_assert_notification_event(characteristic, value, sizeof(value),
|
|
true /* kernel */, true /* app */);
|
|
}
|
|
|
|
static TickType_t prv_taking_too_long_to_consume_yield_cb(QueueHandle_t queue) {
|
|
return milliseconds_to_ticks(1000);
|
|
}
|
|
|
|
static TickType_t prv_consume_in_time_yield_cb(QueueHandle_t queue) {
|
|
// Consume while BT task is waiting for buffer to be freed up:
|
|
BLECharacteristic characteristic_out;
|
|
uint8_t *value_out = (uint8_t *) malloc(GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE);
|
|
uint16_t value_length = GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE;
|
|
gatt_client_subscriptions_consume_notification(&characteristic_out, value_out, &value_length,
|
|
GAPLEClientApp, NULL);
|
|
free(value_out);
|
|
return milliseconds_to_ticks(5);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__notification_buffer_full(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
// We had a bug at some point where the header size was not taken into account correctly, take
|
|
// a value that won't fit by 1 byte, so this bug does not happen again:
|
|
const size_t too_big = GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE -
|
|
sizeof(GATTBufferedNotificationHeader) + 1;
|
|
uint8_t *value = (uint8_t *) malloc(too_big);
|
|
memset(value, 0x55, too_big);
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, too_big);
|
|
prv_assert_no_event();
|
|
|
|
// Receive a GATT notification that is supposed to fill up the buffer entirely:
|
|
const size_t fill_entirely_size = (GATT_CLIENT_SUBSCRIPTIONS_BUFFER_SIZE -
|
|
sizeof(GATTBufferedNotificationHeader));
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, fill_entirely_size);
|
|
prv_assert_notification_event_ext(characteristic, value, fill_entirely_size,
|
|
false /* kernel */, true /* app */, false /* should_consume */);
|
|
|
|
// Receive another GATT notification. Won't fit until consumed. Consuming is taking to long:
|
|
fake_queue_set_yield_callback(gatt_client_subscription_get_semaphore(),
|
|
prv_taking_too_long_to_consume_yield_cb);
|
|
fake_event_clear_last();
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, 1 /* one byte */);
|
|
// Data will be dropped, no event :(
|
|
prv_assert_no_event();
|
|
|
|
// Receive another GATT notification. Won't fit until consumed.
|
|
// Consuming is happening before the timeout hits (in the yield callback):
|
|
fake_queue_set_yield_callback(gatt_client_subscription_get_semaphore(),
|
|
prv_consume_in_time_yield_cb);
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, 1 /* one byte */);
|
|
prv_assert_notification_event(characteristic, value, 1 /* one byte */,
|
|
false /* kernel */, true /* app */);
|
|
|
|
free(value);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__consume_but_too_small_buffer(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
const uint8_t value[] = {0xAA, 0xBB, 0xCC};
|
|
gatt_client_subscriptions_handle_server_notification(s_connection, s_handle,
|
|
value, sizeof(value));
|
|
|
|
BLECharacteristic handle_out = ~0;
|
|
uint8_t out[1];
|
|
uint16_t value_length = sizeof(out);
|
|
bool has_more = true;
|
|
const uint16_t next_length =
|
|
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
|
GAPLEClientApp, &has_more);
|
|
cl_assert_equal_i(handle_out, BLE_CHARACTERISTIC_INVALID);
|
|
cl_assert_equal_i(value_length, 0);
|
|
// Notification will be eaten, regardless of whether it was copied:
|
|
cl_assert_equal_b(has_more, false);
|
|
cl_assert_equal_i(next_length, 0);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__consume_but_nothing_in_buffer(void) {
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
// Simulate getting confirmation from remote:
|
|
prv_confirm_cccd_write(BLEGATTErrorSuccess);
|
|
fake_event_clear_last();
|
|
|
|
BLECharacteristic handle_out = ~0;
|
|
uint8_t out[1];
|
|
uint16_t value_length = sizeof(out);
|
|
bool has_more = true;
|
|
const uint16_t next_length =
|
|
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
|
GAPLEClientApp, &has_more);
|
|
cl_assert_equal_i(handle_out, BLE_CHARACTERISTIC_INVALID);
|
|
cl_assert_equal_i(value_length, 0);
|
|
cl_assert_equal_b(has_more, false);
|
|
cl_assert_equal_i(next_length, 0);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__consume_but_buffer_client_buffer_null(void) {
|
|
bool has_more = true;
|
|
BLECharacteristic handle_out = ~0;
|
|
uint8_t out[1];
|
|
uint16_t value_length = sizeof(out);
|
|
|
|
const uint16_t next_length =
|
|
gatt_client_subscriptions_consume_notification(&handle_out, out, &value_length,
|
|
GAPLEClientApp, &has_more);
|
|
|
|
cl_assert_equal_i(next_length, 0);
|
|
cl_assert_equal_b(has_more, false);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__notification_consume_without_notification(void) {
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
|
|
uint16_t value_length;
|
|
GATTBufferedNotificationHeader header = {
|
|
.characteristic = BLE_CHARACTERISTIC_INVALID,
|
|
.value_length = 0,
|
|
};
|
|
bool has_more = gatt_client_subscriptions_get_notification_header(GAPLEClientKernel,
|
|
&header);
|
|
cl_assert_equal_i(header.value_length, 0);
|
|
cl_assert_equal_i(header.characteristic, BLE_CHARACTERISTIC_INVALID);
|
|
cl_assert_equal_b(has_more, false);
|
|
|
|
BLECharacteristic characteristic_out = ~0;
|
|
uint8_t value = 0xff;
|
|
value_length = sizeof(value);
|
|
gatt_client_subscriptions_consume_notification(&characteristic_out, &value, &value_length,
|
|
GAPLEClientKernel, &has_more);
|
|
// Expect untouched:
|
|
cl_assert_equal_i(characteristic_out, ~0);
|
|
cl_assert_equal_i(value, 0xff);
|
|
cl_assert_equal_b(has_more, false);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// gatt_client_subscriptions_cleanup_by_client
|
|
|
|
void test_gatt_client_subscriptions__cleanup_by_client(void) {
|
|
fake_kernel_malloc_mark();
|
|
|
|
// Subscribe app:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
gatt_client_subscriptions_cleanup_by_client(GAPLEClientApp);
|
|
prv_assert_no_event();
|
|
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// gatt_client_subscriptions_cleanup_by_connection
|
|
|
|
bool gatt_client_get_event_pending_state(GAPLEClient);
|
|
|
|
static void prv_pend_events_to_kernel_and_app(void) {
|
|
// fake pend an event to the kernel
|
|
gatt_client_subscriptions_reschedule(GAPLEClientKernel);
|
|
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientKernel), true);
|
|
fake_event_clear_last();
|
|
|
|
// fake pend an event to the app
|
|
gatt_client_subscriptions_reschedule(GAPLEClientApp);
|
|
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientApp), true);
|
|
fake_event_clear_last();
|
|
}
|
|
|
|
static void prv_assert_no_pending_events_to_kernel_and_app(void) {
|
|
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientKernel), false);
|
|
cl_assert_equal_b(gatt_client_get_event_pending_state(GAPLEClientApp), false);
|
|
}
|
|
|
|
void test_gatt_client_subscriptions__cleanup_by_connection(void) {
|
|
fake_kernel_malloc_mark();
|
|
|
|
// Subscribe app and kernel:
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
prv_pend_events_to_kernel_and_app();
|
|
|
|
gatt_client_subscriptions_cleanup_by_connection(s_connection, false /* should_unsubscribe */);
|
|
prv_assert_no_event();
|
|
|
|
// there should be no more subscriptions
|
|
cl_assert(s_connection->gatt_subscriptions == NULL);
|
|
prv_assert_no_pending_events_to_kernel_and_app();
|
|
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
}
|
|
|
|
extern void gatt_client_subscription_cleanup_by_att_handle_range(
|
|
struct GAPLEConnection *connection, ATTHandleRange *range);
|
|
|
|
void test_gatt_client_subscriptions__cleanup_by_att_handle_range(void) {
|
|
fake_kernel_malloc_mark();
|
|
|
|
BLECharacteristic characteristic = prv_get_indicatable_characteristic();
|
|
BTErrno e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientApp);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
e = gatt_client_subscriptions_subscribe(characteristic, BLESubscriptionIndications,
|
|
GAPLEClientKernel);
|
|
cl_assert_equal_i(e, BTErrnoOK);
|
|
|
|
cl_assert(s_connection->gatt_subscriptions != NULL);
|
|
|
|
ATTHandleRange range;
|
|
fake_gatt_get_bp_att_handle_range(&range.start, &range.end);
|
|
|
|
ATTHandleRange bogus_range = {
|
|
.start = range.end + 1,
|
|
.end = range.end + 5
|
|
};
|
|
|
|
prv_pend_events_to_kernel_and_app();
|
|
|
|
// should have no effect since service is not in this range
|
|
gatt_client_subscription_cleanup_by_att_handle_range(s_connection, &bogus_range);
|
|
prv_assert_no_event();
|
|
cl_assert(s_connection->gatt_subscriptions != NULL);
|
|
|
|
// should actually remove everything
|
|
gatt_client_subscription_cleanup_by_att_handle_range(s_connection, &range);
|
|
prv_assert_no_event();
|
|
|
|
// there should be no more subscriptions
|
|
cl_assert(s_connection->gatt_subscriptions == NULL);
|
|
prv_assert_no_pending_events_to_kernel_and_app();
|
|
|
|
fake_kernel_malloc_mark_assert_equal();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// TODO: Write tests that exercise applib/bluetooth/ble_client.c
|