mbed-os/features/FEATURE_BLE/tests/generic/GattClient/TestDiscoverAllServices.cpp

656 lines
21 KiB
C++

/* 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 <algorithm>
#include <vector>
#include <array>
#include <initializer_list>
#include <tuple>
#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<std::tuple<bool, bool>, 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<bool, bool> 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_description_t>& 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<uint8_t> 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_description_t>& 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<uint8_t> 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<characteristic_description_t>& 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<uint8_t> 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 << ")" <<std::endl;
// set expectation on the discovery function
EXPECT_CALL(
mock_client, discover_primary_service(connection_handle, next_attribute_handle)
).WillOnce(
reply_read_by_group_type(service_transactions)
);
if (!has_characteristic_cb) {
set_services_callback_expectations(service_transactions);
}
next_attribute_handle = service_transactions.back().end + 1;
}
// if counter didn't wrap then send an error
if (next_attribute_handle != 0) {
// set expectation on the discovery function
//Log::info() << "expect discover_primary_service(" << connection_handle << ", " << next_attribute_handle << ") "
//<< "to fail with opcode = " << AttributeOpcode::READ_BY_GROUP_TYPE_REQUEST << " and error = " << AttErrorResponse::ATTRIBUTE_NOT_FOUND << std::endl;
EXPECT_CALL(
mock_client, discover_primary_service(connection_handle, next_attribute_handle)
).WillOnce(
reply_error(AttributeOpcode::READ_BY_GROUP_TYPE_REQUEST, AttErrorResponse::ATTRIBUTE_NOT_FOUND)
);
}
}
void set_discover_services_by_uuid_expectations() {
std::vector<std::vector<uint8_t>> services_response;
auto services_transactions = get_services_transactions();
uint16_t next_attribute_handle = 0x0001;
for (const auto& transaction : services_transactions) {
//Log::info() << "expect discover_primary_service_by_service_uuid(" << connection_handle << ", " << next_attribute_handle << "," << service_filter << ")" <<std::endl;
// set expectation on the discovery function
EXPECT_CALL(
mock_client, discover_primary_service_by_service_uuid(
connection_handle,
next_attribute_handle,
service_filter
)
).WillOnce(
reply_find_by_type_value(transaction)
);
if (!has_characteristic_cb) {
set_services_callback_expectations(transaction);
}
next_attribute_handle = transaction.back().end + 1;
}
// note if last service end handle was 0xFFFF do nothing
// otherwise set a discovery expectation and reply with an error
// if counter didn't wrap then send an error
if (next_attribute_handle != 0) {
// set expectation on the discovery function
//Log::info() << "expect discover_primary_service_by_service_uuid(" << connection_handle << ", " << next_attribute_handle << "," << service_filter << ") "
//<< "to fail with opcode = " << AttributeOpcode::FIND_BY_TYPE_VALUE_REQUEST << " and error = " << AttErrorResponse::ATTRIBUTE_NOT_FOUND << std::endl;
EXPECT_CALL(
mock_client, discover_primary_service_by_service_uuid(
connection_handle, next_attribute_handle, service_filter
)
).WillOnce(
reply_error(AttributeOpcode::FIND_BY_TYPE_VALUE_REQUEST, AttErrorResponse::ATTRIBUTE_NOT_FOUND)
);
}
}
void set_services_callback_expectations(const std::vector<service_description_t>& services) {
if (!has_service_cb) {
return;
}
for (const auto& service : services) {
set_service_callback_expectation(service);
}
}
void set_service_callback_expectation(const service_description_t& service) {
if (!has_service_cb) {
return;
}
//Log::info() << "expect service_callback(" << service.start << ", " << service.end << "," << service.uuid << ")" <<std::endl;
EXPECT_CALL(
service_callback, call(AllOf(
Property(
&DiscoveredService::getStartHandle,
service.start
),
Property(
&DiscoveredService::getEndHandle,
service.end
),
Property(
&DiscoveredService::getUUID,
service.uuid
)
))
).WillOnce(Invoke([](const DiscoveredService* service) -> void {
//Log::info() << "service_callback(" << service->getStartHandle() << ", " << service->getEndHandle() << "," << service->getUUID() << ")" <<std::endl;
}));
}
void set_discover_characteristics_expectations() {
auto services = get_services_discovered();
for (const auto& service : services) {
set_service_callback_expectation(service);
auto characteristic_transactions = get_characteristics_transactions(service);
uint16_t next_attribute_handle = service.start;
characteristic_description_t previous_characteristic { };
for (const auto& characteristic_transaction : characteristic_transactions) {
//Log::info() << "expect discover_characteristics_of_a_service(" << connection_handle << ", " << next_attribute_handle << "," << service.end << ")" <<std::endl;
EXPECT_CALL(
mock_client, discover_characteristics_of_a_service(
connection_handle,
attribute_handle_range_t { next_attribute_handle, service.end }
)
).WillOnce(
reply_read_by_type(characteristic_transaction)
);
set_characteristics_callbacks_expectations(
previous_characteristic,
characteristic_transaction
);
previous_characteristic = characteristic_transaction.back();
next_attribute_handle = characteristic_transaction.back().value_handle + 1;
}
if(next_attribute_handle < service.end) {
//Log::info() << "expect discover_characteristics_of_a_service(" << connection_handle << ", " << next_attribute_handle << "," << service.end << ") "
// << "to fail with opcode = " << AttributeOpcode::READ_BY_TYPE_REQUEST << " and error = " << AttErrorResponse::ATTRIBUTE_NOT_FOUND << std::endl;
EXPECT_CALL(
mock_client, discover_characteristics_of_a_service(
connection_handle,
attribute_handle_range_t { next_attribute_handle, service.end }
)
).WillOnce(
reply_error(AttributeOpcode::READ_BY_TYPE_REQUEST, AttErrorResponse::ATTRIBUTE_NOT_FOUND)
);
set_characteristic_callback_expectation(previous_characteristic);
}
}
}
void set_characteristics_callbacks_expectations(characteristic_description_t previous_characteristic, std::vector<characteristic_description_t> transaction) {
if (transaction.empty() == false) {
//Log::info() << "pop last characteristic in transaction" <<std::endl;
transaction.pop_back();
}
if (previous_characteristic != characteristic_description_t()) {
//Log::info() << "add previous characteristic" <<std::endl;
transaction.insert(transaction.begin(), previous_characteristic);
}
for (const auto& characteristic : transaction) {
set_characteristic_callback_expectation(characteristic);
}
}
void set_characteristic_callback_expectation(characteristic_description_t characteristic) {
if (!has_characteristic_cb) {
return;
}
if (characteristic_filter != UUID() && characteristic_filter != characteristic.uuid) {
return;
}
//Log::info() << "expect characteristic_callback(" << characteristic.uuid << ", " << characteristic.handle << "," << characteristic.value_handle << ")" <<std::endl;
EXPECT_CALL(
characteristic_callback,
call(AllOf(
Property(
&DiscoveredCharacteristic::getUUID,
characteristic.uuid
),
Property(
&DiscoveredCharacteristic::getDeclHandle,
characteristic.handle
),
Property(
&DiscoveredCharacteristic::getValueHandle,
characteristic.value_handle
),
Property(
&DiscoveredCharacteristic::getConnectionHandle,
connection_handle
)
))
).WillOnce(Invoke([](const DiscoveredCharacteristic* characteristic) -> void {
//Log::info() << "characteristic_callback(" << characteristic->getDeclHandle() << ", " << characteristic->getValueHandle() << "," << characteristic->getUUID() << ")" <<std::endl;
}));
}
std::vector<std::vector<service_description_t>> get_services_transactions() {
std::vector<service_description_t> working_set = get_services_discovered();
std::vector<std::vector<service_description_t>> result;
for (const auto& service : working_set) {
if(result.empty()) {
result.push_back(std::vector<service_description_t>());
}
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_description_t> { 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_description_t> { service });
}
}
}
}
return result;
}
std::vector<service_description_t> get_services_discovered() {
std::vector<service_description_t> 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<std::vector<characteristic_description_t>> get_characteristics_transactions(const service_description_t& service) {
std::vector<std::vector<characteristic_description_t>> transactions;
for (const auto& characteristic : service.characteristics) {
if(transactions.empty()) {
transactions.push_back(
std::vector<characteristic_description_t> { 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_description_t> { 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_description_t> { 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<bool, bool>(true, false),
std::tuple<bool, bool>(false, true),
std::tuple<bool, bool>(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) }
}
}
}
)
)
);