/* * 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 "clar.h" #include "comm/ble/gap_le_advert.h" #include "comm/ble/gap_le_task.h" #include "comm/ble/gatt_client_subscriptions.h" #include "comm/ble/kernel_le_client/kernel_le_client.h" #include "comm/ble/kernel_le_client/test/test_definition.h" #include "kernel/events.h" #include "util/size.h" // Stubs //////////////////////////////////////////////////////////////////////////////////////////////////// #include "fake_system_task.h" #include "stubs_logging.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_rand_ptr.h" #include "stubs_rtc.h" void ams_create(void) { } void ams_destroy(void) { } void ancs_create(void) { } void ancs_destroy(void) { } void app_launch_handle_disconnection(void) { } BTBondingID bt_persistent_storage_get_ble_ancs_bonding(void) { return 1; } bool bt_persistent_storage_is_ble_ancs_bonding(BTBondingID bonding) { return true; } void gap_le_advert_unschedule_job_types(GAPLEAdvertisingJobTag *tag_types, size_t num_types) { } void gap_le_connect_cancel_all(GAPLEClient client) { } BTErrno gap_le_connect_cancel_by_bonding(BTBondingID bonding_id, GAPLEClient client) { return BTErrnoOK; } BTErrno gap_le_connect_connect_by_bonding(BTBondingID bonding_id, bool auto_reconnect, bool is_pairing_required, GAPLEClient client) { return BTErrnoOK; } void gap_le_slave_reconnect_start(void) { } void gap_le_slave_reconnect_stop(void) { } BTErrno gatt_client_discovery_discover_all(const BTDeviceInternal *device) { return BTErrnoOK; } uint16_t gatt_client_subscriptions_consume_notification(BLECharacteristic *characteristic_ref_out, uint8_t *value_out, uint16_t *value_length_in_out, GAPLEClient client, bool *has_more_out) { return 0; } bool gatt_client_subscriptions_get_notification_header(GAPLEClient client, GATTBufferedNotificationHeader *header_out) { return false; } void gatt_client_subscriptions_reschedule(GAPLEClient c) { } void launcher_task_add_callback(CallbackEventCallback callback, void *data) { // Use fake_system_task as mock: system_task_add_callback(callback, data); } void ppogatt_create(void) { } void ppogatt_destroy(void) { } void ppogatt_handle_buffer_empty(void) { } void bt_driver_reconnect_try_now(bool ignore_paused) { } void gatt_client_op_cleanup(GAPLEClient client) { } void ppogatt_reset_disconnect_counter(void) { } // Fakes & Helpers //////////////////////////////////////////////////////////////////////////////////////////////////// static const BTDeviceInternal s_test_device = { .address = (const BTDeviceAddress) { .octets = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}, }, }; typedef enum { TestServiceInstanceComplete = 1, TestServiceInstanceIncomplete = 2, TestServiceInstanceUnsupported = 3, } TestServiceInstance; static BLEService s_service_handles[] = { TestServiceInstanceComplete, TestServiceInstanceIncomplete, TestServiceInstanceUnsupported, }; typedef enum { TestCharacteristicInstanceCompleteOne = 11, TestCharacteristicInstanceCompleteTwo = 12, TestCharacteristicInstanceIncompleteOne = 21, TestCharacteristicInstanceUnsupported = 33, } TestCharacteristicInstance; Uuid gatt_client_service_get_uuid(BLEService service_ref) { switch (service_ref) { case TestServiceInstanceComplete: case TestServiceInstanceIncomplete: return s_test_service_uuid; case TestServiceInstanceUnsupported: default: return UUID_INVALID; } } uint8_t gatt_client_service_get_characteristics_matching_uuids(BLEService service_ref, BLECharacteristic characteristics_out[], const Uuid matching_characteristic_uuids[], uint8_t num_characteristics) { cl_assert_equal_i(num_characteristics, TestCharacteristicCount); switch (service_ref) { case TestServiceInstanceComplete: characteristics_out[0] = TestCharacteristicInstanceCompleteOne; characteristics_out[1] = TestCharacteristicInstanceCompleteTwo; return 2; case TestServiceInstanceIncomplete: characteristics_out[0] = TestCharacteristicInstanceIncompleteOne; return 1; case TestCharacteristicInstanceUnsupported: characteristics_out[0] = TestCharacteristicInstanceUnsupported; return 1; default: return 0; } } static int s_read_responses_consumed_count; void gatt_client_consume_read_response(uintptr_t object_ref, uint8_t value_out[], uint16_t value_length, GAPLEClient client) { ++s_read_responses_consumed_count; } static int s_services_discovered_count; void test_client_handle_service_discovered(BLECharacteristic *characteristics) { ++s_services_discovered_count; } void test_client_invalidate_all_references(void) { } void test_client_handle_service_removed(BLECharacteristic *characteristics, uint8_t num_characteristics) { } static bool s_can_handle_characteristic; bool test_client_can_handle_characteristic(BLECharacteristic characteristic) { return s_can_handle_characteristic; } void test_client_handle_write_response(BLECharacteristic characteristic, BLEGATTError error) { } void test_client_handle_subscribe(BLECharacteristic characteristic, BLESubscription subscription_type, BLEGATTError error) { } void test_client_handle_read_or_notification(BLECharacteristic characteristic, const uint8_t *value, size_t value_length, BLEGATTError error) { } // Tests //////////////////////////////////////////////////////////////////////////////////////////////////// void test_kernel_le_client__initialize(void) { s_services_discovered_count = 0; s_read_responses_consumed_count = 0; s_can_handle_characteristic = false; kernel_le_client_init(); } void test_kernel_le_client__cleanup(void) { kernel_le_client_deinit(); fake_system_task_callbacks_cleanup(); } void test_kernel_le_client__read_response_consumed_even_if_client_is_gone(void) { // Simulate the client goes away: s_can_handle_characteristic = false; PebbleEvent e = (PebbleEvent) { .type = PEBBLE_BLE_GATT_CLIENT_EVENT, .bluetooth.le.gatt_client = { .object_ref = TestCharacteristicInstanceCompleteOne, .value_length = 1, .gatt_error = BLEGATTErrorSuccess, .subtype = PebbleBLEGATTClientEventTypeCharacteristicRead, }, }; kernel_le_client_handle_event(&e); cl_assert_equal_i(s_read_responses_consumed_count, 1); // When value_length is zero, the read response should NOT be consumed: e.bluetooth.le.gatt_client.value_length = 0; s_read_responses_consumed_count = 0; kernel_le_client_handle_event(&e); cl_assert_equal_i(s_read_responses_consumed_count, 0); } void test_kernel_le_client__service_added(void) { uint8_t num_services_added = ARRAY_LENGTH(s_service_handles); PebbleBLEGATTClientServiceEventInfo *info = kernel_malloc(sizeof(PebbleBLEGATTClientServiceEventInfo) + (num_services_added * sizeof(BLEService))); *info = (PebbleBLEGATTClientServiceEventInfo) { .status = BTErrnoOK, .type = PebbleServicesAdded, .device = s_test_device, }; info->services_added_data.num_services_added = num_services_added; memcpy(info->services_added_data.services, s_service_handles, sizeof(s_service_handles)); PebbleEvent e = (PebbleEvent) { .type = PEBBLE_BLE_GATT_CLIENT_EVENT, .bluetooth.le.gatt_client_service = { .info = info, .subtype = PebbleBLEGATTClientEventTypeServiceChange, }, }; kernel_le_client_handle_event(&e); // Found one complete service instance: cl_assert_equal_i(s_services_discovered_count, 1); kernel_free(info); } // FIXME: PBL-27751: Improve test coverage of kernel_le_client.c