/* * 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 "comm/ble/gatt_service_changed.h" #include "comm/ble/gap_le_connection.h" #include "bluetopia_interface.h" #include "kernel/events.h" #include "clar.h" #include extern void gatt_service_changed_server_init(void); // Fakes /////////////////////////////////////////////////////////// #include "fake_GAPAPI.h" #include "fake_GATTAPI.h" #include "fake_GATTAPI_test_vectors.h" #include "fake_pbl_malloc.h" #include "fake_new_timer.h" #include "fake_rtc.h" #include "fake_system_task.h" // Stubs /////////////////////////////////////////////////////////// #include "stubs_bluetopia_interface.h" #include "stubs_bt_driver_gatt.h" #include "stubs_bt_driver_gatt_client_discovery.h" #include "stubs_bt_lock.h" #include "stubs_events.h" #include "stubs_gatt_client_subscriptions.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_prompt.h" #include "stubs_rand_ptr.h" #include "stubs_regular_timer.h" uint16_t gaps_get_starting_att_handle(void) { return 4; } BLEService gatt_client_att_handle_get_service( GAPLEConnection *connection, uint16_t att_handle, const GATTServiceNode **service_node_out) { return 0; } uint8_t gatt_client_copy_service_refs_by_discovery_generation( const BTDeviceInternal *device, BLEService services_out[], uint8_t num_services, uint8_t discovery_gen) { return 0; } void gatt_client_service_get_all_characteristics_and_descriptors( GAPLEConnection *connection, GATTService *service, BLECharacteristic *characteristic_hdls_out, BLEDescriptor *descriptor_hdls_out) { } void launcher_task_add_callback(void (*callback)(void *data), void *data) { callback(data); } // Helpers /////////////////////////////////////////////////////////// static const BTDeviceInternal s_device = { .address = { .octets = { 1, 2, 3, 4, 5, 6, }, }, }; static uint32_t s_connection_id = 1; static GAPLEConnection *s_connection; static void prv_cccd_write(bool is_subscribing) { GattServerSubscribeEvent event = { .connection_id = s_connection_id, .dev_address = s_device.address, .is_subscribing = is_subscribing, }; bt_driver_cb_gatt_service_changed_server_subscribe(&event); } static void prv_process_pending_callbacks(GAPLEConnection *connection) { if (connection && connection->gatt_service_changed_indication_timer) { stub_new_timer_fire(connection->gatt_service_changed_indication_timer); } fake_system_task_callbacks_invoke_pending(); } #define prv_expect_service_changed_indication_api_call_count(expected_count) \ { \ prv_process_pending_callbacks(s_connection); \ cl_assert_equal_i(fake_gatt_get_service_changed_indication_count(), expected_count); \ } // Tests /////////////////////////////////////////////////////////// void test_gatt_service_changed_server__initialize(void) { gatt_service_changed_server_init(); fake_gatt_init(); gap_le_connection_init(); gap_le_connection_add(&s_device, NULL, false /* local_is_master */); s_connection = gap_le_connection_by_device(&s_device); cl_assert(s_connection); s_connection->gatt_connection_id = s_connection_id; } void test_gatt_service_changed_server__cleanup(void) { if (s_connection) { gap_le_connection_remove(&s_device); s_connection = NULL; } gap_le_connection_deinit(); stub_new_timer_cleanup(); } void test_gatt_service_changed_server__unsubscribe(void) { prv_cccd_write(false /* is_subscribing */); prv_expect_service_changed_indication_api_call_count(0); } void test_gatt_service_changed_server__subscribe_event_but_no_connection(void) { gap_le_connection_remove(&s_device); s_connection = NULL; prv_cccd_write(true /* is_subscribing */); prv_expect_service_changed_indication_api_call_count(0); } void test_gatt_service_changed_server__subscribe_fw_not_updated(void) { prv_cccd_write(true /* is_subscribing */); prv_expect_service_changed_indication_api_call_count(0); } void test_gatt_service_changed_server__resubscribe_indication_already_pending(void) { gatt_service_changed_server_handle_fw_update(); prv_cccd_write(true /* is_subscribing */); prv_cccd_write(true /* is_subscribing */); prv_expect_service_changed_indication_api_call_count(1); } void test_gatt_service_changed_server__reconnect_resubscribe_stop_sending_after_n_times(void) { gatt_service_changed_server_handle_fw_update(); gap_le_connection_remove(&s_device); s_connection = NULL; static const int max_times = 5; for (int i = 0; i < max_times + 1; ++i) { gap_le_connection_add(&s_device, NULL, false /* local_is_master */); s_connection = gap_le_connection_by_device(&s_device); cl_assert(s_connection); s_connection->gatt_connection_id = s_connection_id; prv_cccd_write(true /* is_subscribing */); if (i < max_times) { prv_expect_service_changed_indication_api_call_count(i + 1); } else { prv_expect_service_changed_indication_api_call_count(max_times); } gap_le_connection_remove(&s_device); s_connection = NULL; } } void test_gatt_service_changed_server__disconnect_during_delay(void) { gatt_service_changed_server_handle_fw_update(); prv_cccd_write(true /* is_subscribing */); // Grab the timer ID: TimerID t = s_connection->gatt_service_changed_indication_timer; s_connection->gatt_service_changed_indication_timer = TIMER_INVALID_ID; // Simulate disconnection: gap_le_connection_remove(&s_device); s_connection = NULL; // Timer fires: stub_new_timer_fire(t); prv_expect_service_changed_indication_api_call_count(0); }