/* mbed Microcontroller Library * Copyright (c) 2018 ARM Limited * * 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 #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ble/generic/GenericGattClient.h" #include "ble/pal/AttServerMessage.h" #include "ble/pal/SimpleAttServerMessage.h" #include "ble/DiscoveredService.h" #include "mock/MockPalGattClient.h" #include "mock/MockCallbacks.h" #include "util/ServerDescription.h" #include "util/PrettyPrinter.h" #include "util/Equality.h" #include "util/Log.h" // imports using ble::pal::SimpleAttFindByTypeValueResponse; using ble::pal::SimpleAttReadByGroupTypeResponse; using ble::pal::SimpleAttReadByTypeResponse; using ble::pal::AttErrorResponse; using ble::pal::AttributeOpcode; using ble::attribute_handle_range_t; using ble::generic::GenericGattClient; using ble::pal::vendor::mock::MockPalGattClient; using ble::connection_handle_t; using ble::attribute_handle_t; using ble::make_const_ArrayView; using ::testing::_; using ::testing::Invoke; using ::testing::InvokeWithoutArgs; using ::testing::AllOf; using ::testing::Property; using ::testing::InSequence; class LaunchDiscoveryNoServiceFilter : public ::testing::TestWithParam< std::tuple, UUID, UUID, server_description_t> > { protected: LaunchDiscoveryNoServiceFilter() : mock_client(), gatt_client(&mock_client), connection_handle(0xDEAD), termination_callback(), has_service_cb(), service_callback(), has_characteristic_cb(), characteristic_callback(), service_filter(), characteristic_filter(), server_stub() { } virtual void SetUp() { gatt_client.onServiceDiscoveryTermination( makeFunctionPointer( &termination_callback, &termination_callback_t::call ) ); std::tuple cb_combination; std::tie( cb_combination, service_filter, characteristic_filter, server_stub ) = GetParam(); std::tie(has_service_cb, has_characteristic_cb) = cb_combination; } auto reply_read_by_group_type(const std::vector& service_transactions) { return Invoke([service_transactions, this](connection_handle_t connection, attribute_handle_t handle) -> ble_error_t { //Log::info() << "discover primary service (" << connection << "," << handle << ")" << std::endl; uint8_t uuid_size = service_transactions.front().uuid.getLen(); uint8_t element_size = uuid_size + 4; size_t data_size = element_size * service_transactions.size(); std::vector result(data_size); uint8_t* data = result.data(); for (const auto& service : service_transactions) { memcpy(data, &service.start, 2); data += 2; memcpy(data, &service.end, 2); data += 2; memcpy(data, service.uuid.getBaseUUID(), uuid_size); data += uuid_size; } mock_client.on_server_event( connection, SimpleAttReadByGroupTypeResponse( element_size, make_const_ArrayView(result.data(), result.size()) ) ); return BLE_ERROR_NONE; }); } auto reply_find_by_type_value(const std::vector& service_transactions) { return Invoke([service_transactions, this](connection_handle_t connection, attribute_handle_t handle, const UUID& uuid) -> ble_error_t { //Log::info() << "discover primary service by uuid(" << connection << "," << handle << "," << uuid << ")" << std::endl; std::vector result(service_transactions.size() * 4); uint8_t* data = result.data(); for (const auto& service : service_transactions) { memcpy(data, &service.start, 2); data += 2; memcpy(data, &service.end, 2); data += 2; } mock_client.on_server_event( connection, SimpleAttFindByTypeValueResponse( make_const_ArrayView(result.data(), result.size()) ) ); return BLE_ERROR_NONE; }); } uint8_t properties_to_byte(DiscoveredCharacteristic::Properties_t p) { return ( p.broadcast() << 0 | p.read() << 1 | p.writeWoResp() << 2 | p.write() << 3 | p.notify() << 4 | p.indicate() << 5 | p.authSignedWrite() << 6 /* extented properties ignored for now */ ); } auto reply_read_by_type(const std::vector& transaction) { return Invoke([transaction, this](connection_handle_t connection, attribute_handle_range_t range) -> ble_error_t { //Log::info() << "discover characteristic(" << connection << "," << range.begin << "," << range.end << ")" << std::endl; size_t uuid_size = transaction.front().uuid.getLen(); uint8_t element_size = uuid_size + 5; std::vector result(element_size * transaction.size()); uint8_t* data = result.data(); for (const auto& characteristic : transaction) { memcpy(data, &characteristic.handle, 2); data += 2; *data++ = properties_to_byte(characteristic.properties); memcpy(data, &characteristic.value_handle, 2); data += 2; memcpy(data, characteristic.uuid.getBaseUUID(), uuid_size); data += uuid_size; } mock_client.on_server_event( connection, SimpleAttReadByTypeResponse( element_size, make_const_ArrayView(result.data(), result.size()) ) ); return BLE_ERROR_NONE; }); } auto reply_error(AttributeOpcode opcode, AttErrorResponse::AttributeErrorCode error_code) { return InvokeWithoutArgs([this, opcode, error_code]() -> ble_error_t { //Log::info() << "reply error: opcode = " << (uint8_t) opcode << ", error_code = " << error_code << std::endl; mock_client.on_server_event( connection_handle, AttErrorResponse(opcode, error_code) ); return BLE_ERROR_NONE; }); } // helper // note sequence insured by caller void set_discover_all_services_expectations() { auto services_transactions = get_services_transactions(); uint16_t next_attribute_handle = 0x0001; for (const auto& service_transactions : services_transactions) { //Log::info() << "expect discover_primary_service(" << connection_handle << ", " << next_attribute_handle << ")" < void { //Log::info() << "service_callback(" << service->getStartHandle() << ", " << service->getEndHandle() << "," << service->getUUID() << ")" < void { //Log::info() << "characteristic_callback(" << characteristic->getDeclHandle() << ", " << characteristic->getValueHandle() << "," << characteristic->getUUID() << ")" <> get_services_transactions() { std::vector working_set = get_services_discovered(); std::vector> result; for (const auto& service : working_set) { if(result.empty()) { result.push_back(std::vector()); } auto& last_group = result.back(); if (last_group.empty()) { last_group.push_back(service); } else { auto last_group_uuid = last_group.front().uuid.shortOrLong(); auto last_group_size = last_group.size(); if (last_group_uuid != service.uuid.shortOrLong()) { result.push_back(std::vector { service }); } else { if (last_group_uuid == UUID::UUID_TYPE_SHORT && last_group_size < 4) { last_group.push_back(service); } else { result.push_back(std::vector { service }); } } } } return result; } std::vector get_services_discovered() { std::vector working_set; if (service_filter != UUID()) { for (const auto& service : server_stub.services) { if (service.uuid == service_filter) { working_set.push_back(service); } } } else { working_set = server_stub.services; } return working_set; } std::vector> get_characteristics_transactions(const service_description_t& service) { std::vector> transactions; for (const auto& characteristic : service.characteristics) { if(transactions.empty()) { transactions.push_back( std::vector { characteristic } ); } else { auto& last_group = transactions.back(); auto last_group_uuid = last_group.front().uuid.shortOrLong(); auto last_group_size = last_group.size(); if (last_group_uuid != service.uuid.shortOrLong()) { transactions.push_back(std::vector { characteristic }); } else { if (last_group_uuid == UUID::UUID_TYPE_SHORT && last_group_size < 3) { last_group.push_back(characteristic); } else { transactions.push_back(std::vector { characteristic }); } } } } return transactions; } MockPalGattClient mock_client; GenericGattClient gatt_client; const Gap::Handle_t connection_handle; termination_callback_t termination_callback; bool has_service_cb; service_callback_t service_callback; bool has_characteristic_cb; characteristic_callback_t characteristic_callback; UUID service_filter; UUID characteristic_filter; server_description_t server_stub; }; TEST_P(LaunchDiscoveryNoServiceFilter, regular) { ON_CALL( mock_client, discover_primary_service(_, _) ).WillByDefault(Invoke([](connection_handle_t connection, attribute_handle_t handle) -> ble_error_t { //Log::info() << "default discover primary service (" << connection << "," << handle << ")" << std::endl; return BLE_ERROR_NONE; })); ON_CALL( mock_client, discover_primary_service_by_service_uuid(_, _, _) ).WillByDefault(Invoke([](connection_handle_t connection, attribute_handle_t handle, const UUID& uuid) -> ble_error_t { //Log::info() << "default discover primary service by service uuid(" << connection << "," << handle << ", " << uuid << ")" << std::endl; return BLE_ERROR_NONE; })); ON_CALL( mock_client, discover_characteristics_of_a_service(_, _) ).WillByDefault(Invoke([](connection_handle_t connection, attribute_handle_range_t handle) -> ble_error_t { //Log::info() << "default discover characteristic of a service(" << connection << "," << handle << ")" << std::endl; return BLE_ERROR_NONE; })); if (has_service_cb) { ON_CALL( service_callback, call(_) ).WillByDefault(Invoke([](const DiscoveredService* service) -> void { //Log::info() << "default service_callback(" << service->getEndHandle() << ", " << service->getEndHandle() << ", " << service->getUUID() << ")" << std::endl; })); } if (has_characteristic_cb) { ON_CALL( characteristic_callback, call(_) ).WillByDefault(Invoke([](const DiscoveredCharacteristic* characteristic) -> void { //Log::info() << "default characteristic_callback(" << characteristic->getDeclHandle() << ", " << characteristic->getValueHandle() << ", " << characteristic->getUUID() << ")" << std::endl; })); } { InSequence seq; if (service_filter == UUID()) { set_discover_all_services_expectations(); } else { set_discover_services_by_uuid_expectations(); } if (has_characteristic_cb) { set_discover_characteristics_expectations(); } EXPECT_CALL(termination_callback, call(connection_handle)); } ble_error_t err = gatt_client.launchServiceDiscovery( connection_handle, has_service_cb ? makeFunctionPointer( &service_callback, &decltype(service_callback)::call ) : NULL, has_characteristic_cb ? makeFunctionPointer( &characteristic_callback, &decltype(characteristic_callback)::call ) : NULL, service_filter, characteristic_filter ); EXPECT_EQ(BLE_ERROR_NONE, err); } INSTANTIATE_TEST_CASE_P( GattClient_launch_discovery_no_service_filter, LaunchDiscoveryNoServiceFilter, ::testing::Combine( ::testing::Values( std::tuple(true, false), std::tuple(false, true), std::tuple(true, true) ), ::testing::Values(UUID(), UUID(0x1452), UUID("a3d1495f-dba7-4441-99f2-d0a20f663422")), ::testing::Values(UUID(), UUID(0xBEEF), UUID("1f551ee3-aef4-4719-8c52-8b419fc4ac01")), ::testing::Values( server_description_t { }, server_description_t { { 0x0001, 0x0030, UUID(0xAAAA), { { 0x0002, { 0 }, UUID(0xBBBA) }, { 0x0006, { 1, 0 }, UUID(0xBBBB) }, { 0x0012, { 1, 0 }, UUID(0xBBBC) }, { 0x0015, { 1, 1, 1, 0 }, UUID(0xBBBD) } }, }, { 0x0031, 0x0060, UUID(0xAAAB), { { 0x0032, { 0 }, UUID(0xCBBA) }, { 0x0036, { 1, 0 }, UUID(0xCBBB) }, { 0x0042, { 1, 0 }, UUID(0xCBBC) }, { 0x0050, { 1, 1, 1, 0 }, UUID(0xCBBD) } }, }, { 0x0061, 0x0090, UUID(0xAAAB), { { 0x0062, { 0 }, UUID(0xDBBA) }, { 0x0066, { 1, 0 }, UUID(0xDBBB) }, { 0x0072, { 1, 0 }, UUID(0xDBBC) }, { 0x0082, { 1, 1, 1, 0 }, UUID(0xDBBD) } } } }, server_description_t { { 0x0001, 0x0030, UUID(0xAAAA), { { 0x0002, { 0 }, UUID(0xBBBA) }, { 0x0006, { 1, 0 }, UUID(0xBBBB) }, { 0x0012, { 1, 0 }, UUID(0xBBBC) }, { 0x0024, { 1, 1, 1, 0 }, UUID(0xBBBC) } }, }, { 0x0031, 0x0060, UUID(0xAAAB), { { 0x0032, { 0 }, UUID(0xCBBA) }, { 0x0036, { 1, 0 }, UUID(0xCBBB) }, { 0x0042, { 1, 0 }, UUID(0xCBBC) }, { 0x0045, { 1, 1, 1, 0 }, UUID(0xCBBC) } }, }, { 0x0061, 0xFFFF, UUID(0xAAAB), { { 0x0062, { 0 }, UUID(0xDBBA) }, { 0x0066, { 1, 0 }, UUID(0xDBBB) }, { 0x0072, { 1, 0 }, UUID(0xDBBC) }, { 0x0082, { 1, 1, 1, 0 }, UUID(0xDBBC) } } } } ) ) );