diff --git a/.travis.yml b/.travis.yml index 8259c65d7a..b4e2f5c56e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,15 @@ cache: - $HOME/.cache/apt - $HOME/gcc-arm-none-eabi-6-2017-q2-update +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-6 + - g++-6 + - cmake + before_install: - bash -c "$STATUS" pending "Local $NAME testing is in progress" # Make sure pipefail @@ -134,6 +143,25 @@ matrix: fi - bash -c "$STATUS" success "$STATUSM" + - env: + - NAME=ble-host-tests + - BLE_HOST_TESTS=$PWD/features/FEATURE_BLE/tests + install: + # Install dependencies + - sudo apt-get install cmake + # Print versions we use + - gcc --version + - cmake --version + script: + # Compile the tests + - mkdir $BLE_HOST_TESTS/build + - cd $BLE_HOST_TESTS/build && CC=gcc-6 CXX=g++-6 cmake .. -G "Unix Makefiles" + - ls $BLE_HOST_TESTS + - ls $BLE_HOST_TESTS/build + - cd $BLE_HOST_TESTS/build && make + # Run ble host tests + - $BLE_HOST_TESTS/build/gatt-client-tests + - env: - NAME=littlefs - LITTLEFS=features/filesystem/littlefs diff --git a/features/FEATURE_BLE/tests/CMakeLists.txt b/features/FEATURE_BLE/tests/CMakeLists.txt new file mode 100644 index 0000000000..df2d856b71 --- /dev/null +++ b/features/FEATURE_BLE/tests/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 2.8.11) + +# Make PROJECT_SOURCE_DIR, PROJECT_BINARY_DIR, and PROJECT_NAME available. +set(PROJECT_NAME ble-tests) +project(${PROJECT_NAME}) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +################################ +# GTEST +################################ + +# Download and unpack googletest at configure time +configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Prevent overriding the parent project's compiler/linker +# settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +# Add googletest directly to our build. This defines +# the gtest and gtest_main targets. +add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src + ${CMAKE_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) + +# The gtest/gtest_main targets carry header search path +# dependencies automatically when using CMake 2.8.11 or +# later. Otherwise we have to add them here ourselves. +if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories(BEFORE SYSTEM + "${gtest_SOURCE_DIR}/include" "${gmock_SOURCE_DIR}/include") +else() + target_include_directories(gmock_main SYSTEM BEFORE INTERFACE + "${gtest_SOURCE_DIR}/include" "${gmock_SOURCE_DIR}/include") +endif() + + +################################ +# Testing +################################ + +enable_testing() + +############################### +# GattClient test +############################### + +add_executable(gatt-client-tests + mbed_os_stub/mbed_assert.c + generic/GattClient/mock/MockCallbacks.cpp + generic/GattClient/mock/MockPalGattClient.cpp + generic/GattClient/util/Equality.cpp + generic/GattClient/TestCharacteristicDesctiptorDiscovery.cpp + generic/GattClient/TestDiscoverAllServices.cpp + generic/GattClient/TestNoCb.cpp + generic/GattClient/TestRead.cpp + generic/GattClient/TestServerEvent.cpp + generic/GattClient/TestWrite.cpp + ${PROJECT_SOURCE_DIR}/../source/generic/GenericGattClient.cpp +) + +target_include_directories(gatt-client-tests PRIVATE + "${PROJECT_SOURCE_DIR}/.." + "${PROJECT_SOURCE_DIR}/../../.." + "${PROJECT_SOURCE_DIR}/generic/GattClient" +) + +# Standard linking to gtest stuff. +target_link_libraries(gatt-client-tests gmock_main) + +# This is so you can do 'make gatt-client-tests' to see all your tests run, instead of +# manually running the executable runUnitTests to see those specific tests. +add_test(NAME AllUnitTests COMMAND gatt-client-tests) + + diff --git a/features/FEATURE_BLE/tests/CMakeLists.txt.in b/features/FEATURE_BLE/tests/CMakeLists.txt.in new file mode 100644 index 0000000000..6766ddc6c6 --- /dev/null +++ b/features/FEATURE_BLE/tests/CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.11) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) \ No newline at end of file diff --git a/features/FEATURE_BLE/tests/README.md b/features/FEATURE_BLE/tests/README.md new file mode 100644 index 0000000000..f2f9d71163 --- /dev/null +++ b/features/FEATURE_BLE/tests/README.md @@ -0,0 +1,35 @@ +# mbed BLE host tests + +This folder contains tests for mbed BLE that can be run on an x86 host. + +## Getting started + +Run the following instructions to build the tests: + +``` +cd features/FEATURE_BLE/tests +mkdir build +cd build +cmake .. +make +``` + +The various tests applications are present at the root of the test folder; +execute them to run the tests. As an examples gatt client related tests can be +run with: + +``` +./gatt-client-tests +``` + +## Requirements + +These tests requires cmake on the host and a compliant C++14 compiler. + +## Resources + +These tests use extensively google test (gtest) and google mock. Please refer to +the documentation of each products to get more insight: +* gtest: https://github.com/google/googletest/tree/master/googletest/docs +* gmock: https://github.com/google/googletest/tree/master/googlemock/docs + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestCharacteristicDesctiptorDiscovery.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestCharacteristicDesctiptorDiscovery.cpp new file mode 100644 index 0000000000..b8f25ff78c --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestCharacteristicDesctiptorDiscovery.cpp @@ -0,0 +1,669 @@ +/* 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 + +#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/PrettyPrinter.h" +#include "util/Equality.h" +#include "util/Log.h" + +// using declarations +using ble::pal::AttErrorResponse; +using ble::pal::AttributeOpcode; +using ble::pal::AttFindInformationResponse; +using ble::generic::GenericGattClient; +using ble::pal::vendor::mock::MockPalGattClient; + +using ble::connection_handle_t; +using ble::attribute_handle_range; +using ble::attribute_handle_range_t; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::AllOf; +using ::testing::ResultOf; +using ::testing::InSequence; +using ::testing::Pointee; + +using std::vector; +using std::tuple; +using std::pair; + +struct ConstructibleDiscoveredCharacteristic : public DiscoveredCharacteristic { + ConstructibleDiscoveredCharacteristic( + GattClient* client, + Gap::Handle_t connection_handle, + const UUID& uuid, + uint16_t declaration_handle, + uint16_t value_handle, + uint16_t last_handle + ) : DiscoveredCharacteristic() { + this->gattc = client; + this->uuid = uuid; + declHandle = declaration_handle; + valueHandle = value_handle; + lastHandle = last_handle; + connHandle = connection_handle; + } + + void set_last_handle(uint16_t last_handle) { + lastHandle = last_handle; + } + + void set_value_handle(uint16_t value_handle) { + valueHandle = value_handle; + } +}; + +/** + * Test fixture used for client descriptor discovery testing. + */ +class TestGattClientDescriptorDiscovery : public ::testing::Test { +protected: + TestGattClientDescriptorDiscovery() : + _mock_client(), + _gatt_client(&_mock_client), + _connection_handle(0xDEAD), + _attribute_handle(0x5645), + _descriptor_cb(), + _termination_cb(), + _mtu_size(23), + _characteristic( + &_gatt_client, + _connection_handle, + UUID(0xDEAF), + _attribute_handle, + _attribute_handle + 1, + _attribute_handle + 1 + ) { + } + + virtual void SetUp() { + ON_CALL( + _mock_client, get_mtu_size(_connection_handle, _) + ).WillByDefault(Invoke([&](auto, auto& size){ + size = this->_mtu_size; + return BLE_ERROR_NONE; + })); + } + + void set_mtu_size_stub() { + EXPECT_CALL( + _mock_client, get_mtu_size(_connection_handle, _) + ).WillRepeatedly(::testing::DoDefault()); + } + + MockPalGattClient _mock_client; + GenericGattClient _gatt_client; + const Gap::Handle_t _connection_handle; + uint16_t _attribute_handle; + descriptor_callback_t _descriptor_cb; + descriptor_termination_callback_t _termination_cb; + uint16_t _mtu_size; + ConstructibleDiscoveredCharacteristic _characteristic; +}; + +// errors: Invalid handle if: +// starting handle > ending handle +// stating handle == 00 +// ending handle > last handle on the server + +// if no handle will be return => ATTRIBUTE NOT FOUND + +// Complete when ATTRIBUTE NOT FOUND is returned or an attribute handle +// in the response is equal to the ending handle in the request. + + +// Find information response: +// format & [(handle, UUID)] +// format == 1 => 16 bit UUID +// format == 2 => 128 bit UUID + +/* + * Given a discovered characteristic with the value handle equal to the last + * handle of the characteristic. + * When the client discover the descriptor of the characteristic + * Then: + * - no request is issued to the PAL. + * - the function doesn't return any error. + * - the termination callback is called immediately + */ +TEST_F(TestGattClientDescriptorDiscovery, descriptor_discovery_on_characteristics_without_descriptor_shall_return_immediatelly) { + _characteristic.set_last_handle(_characteristic.getValueHandle()); + + EXPECT_CALL( + _mock_client, discover_characteristics_descriptors(_, _) + ).Times(0); + + EXPECT_CALL( + _termination_cb, call(Pointee(CharacteristicDescriptorDiscovery::TerminationCallbackParams_t { + _characteristic, + BLE_ERROR_NONE, + 0x00 + }) + ) + ); + + ble_error_t err = _gatt_client.discoverCharacteristicDescriptors( + _characteristic, + makeFunctionPointer(&_descriptor_cb, &descriptor_callback_t::call), + makeFunctionPointer(&_termination_cb, &descriptor_termination_callback_t::call) + ); + + EXPECT_EQ(err, BLE_ERROR_NONE); +} + +/** + * Test parameter pass into tests using TestGattClientDescriptorDiscoveryP fixture. + * - first element: value handle of the characteristic. + * - second element: last handle of the characteristic + * - third element: expected transactions; each transaction can contain multiple + * pair containing the handle of the descriptor and its UUID. + */ +typedef tuple>>> test_param_t; + +/** + * Parametric fixture used for descriptor discovery testing. + */ +class TestGattClientDescriptorDiscoveryP : + public TestGattClientDescriptorDiscovery, + public ::testing::WithParamInterface { + +protected: + TestGattClientDescriptorDiscoveryP() : + TestGattClientDescriptorDiscovery(), + _value_handle(0), + _last_handle(0), + _descriptors() { + } + + virtual void SetUp() { + TestGattClientDescriptorDiscovery::SetUp(); + std::tie(_value_handle, _last_handle, _descriptors) = GetParam(); + _characteristic.set_value_handle(_value_handle); + _characteristic.set_last_handle(_last_handle); + } + + uint16_t _value_handle; + uint16_t _last_handle; + vector>> _descriptors; +}; + +struct MockFindInformationResponse : public AttFindInformationResponse { + MockFindInformationResponse(vector> response) : + _response(response) { } + + virtual size_t size() const { + return _response.size(); + } + + virtual information_data_t operator[](size_t index) const { + auto& element = _response[index]; + return { + element.first, + element.second + }; + } + + vector> _response; +}; + +/** + * Helper returning a DiscoveredCharacteristic from a DiscoveryCallbackParams_t* + */ +static const DiscoveredCharacteristic& get_characteristic(const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t* p) { + return p->characteristic; +} + +/** + * Helper returning a DiscoveredCharacteristicDescriptor from a DiscoveryCallbackParams_t* + */ +static const DiscoveredCharacteristicDescriptor& get_descriptor(const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t* p) { + return p->descriptor; +} + +/* + * Given a discovered characteristic with the value handle not equal to the + * last handle of the characteristic. + * when the client launch the discovery of the descriptor of the characteristic. + * Then: + * - the client invoke the pal function discover_characteristics_descriptors + * with an handle range starting at characteristic value handle + 1 and ending + * at the last characteristic handle. + * - The pal will reply with a FindInformationResponse containing the + * descriptors discovered. + * The operation is repeated until the response contains an attribute with an + * handle equal to the last handle of the range or the pal reply with an error + * (attribute not found). The termination callback is then call with a status + * containing BLE_ERROR_NONE. + */ +TEST_P(TestGattClientDescriptorDiscoveryP, descriptor_discovery) { + uint16_t current_handle = _characteristic.getValueHandle() + 1; + auto descriptors = _descriptors; + + InSequence seq; + + while (true) { + //Log::info() << "expect discover_characteristics_descriptors(" << current_handle << ", " << _last_handle << ")" << std::endl; + EXPECT_CALL( + _mock_client, discover_characteristics_descriptors( + _connection_handle, + attribute_handle_range(current_handle, _last_handle) + ) + ).WillOnce(Invoke([&, descriptors, current_handle](auto connection, auto range) { + if (descriptors.empty()) { + // send an attribute not found response + _mock_client.on_server_event( + connection, + AttErrorResponse( + AttributeOpcode::FIND_INFORMATION_REQUEST, + current_handle, + AttErrorResponse::ATTRIBUTE_NOT_FOUND + ) + ); + } else { + _mock_client.on_server_event( + connection, + MockFindInformationResponse(descriptors.front()) + ); + } + return BLE_ERROR_NONE; + })); + + if (descriptors.empty() == false) { + for (const auto& descriptor : descriptors.front()) { + DiscoveredCharacteristicDescriptor discovered_descriptor( + &_gatt_client, + _connection_handle, + descriptor.first, + descriptor.second + ); + + //Log::info() << "expect _descriptor_cb(" << _connection_handle << ", " << descriptor.first << ", " << descriptor.second << ")" << std::endl; + + EXPECT_CALL( + _descriptor_cb, call(AllOf( + ResultOf(get_characteristic, _characteristic), + ResultOf(get_descriptor, discovered_descriptor) + )) + ); + } + } + + if (descriptors.empty() || + std::any_of( + begin(descriptors.front()), + end(descriptors.front()), + [&](auto& val) { return val.first == _last_handle; } + ) + ) { + //Log::info() << "expect termination" << std::endl; + EXPECT_CALL( + _termination_cb, call(Pointee(CharacteristicDescriptorDiscovery::TerminationCallbackParams_t { + _characteristic, + BLE_ERROR_NONE, + 0x00 + })) + ); + break; + } else { + current_handle = descriptors.front().back().first + 1; + descriptors.erase(begin(descriptors)); + } + } + + ble_error_t err = _gatt_client.discoverCharacteristicDescriptors( + _characteristic, + makeFunctionPointer(&_descriptor_cb, &descriptor_callback_t::call), + makeFunctionPointer(&_termination_cb, &descriptor_termination_callback_t::call) + ); + + EXPECT_EQ(err, BLE_ERROR_NONE); +} + +// Instantiation of the tests cases relying on the parametric fixture +INSTANTIATE_TEST_CASE_P( + TestGattClientDescriptorDiscoveryP_combination, + TestGattClientDescriptorDiscoveryP, + ::testing::Values( + // single transaction, single 16 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) } + } + } + }, + // single transaction, single 16 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1001, + { + { + { 0x1001, UUID(0xFFAA) } + } + } + }, + // single transaction, single 128 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID("1881e01d-02af-41e9-abe9-bc940c09ca65") } + } + } + }, + // single transaction, single 128 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1001, + { + { + { 0x1001, UUID("8a239fd3-61a3-44a4-8b7c-2db9a9baa3c8") } + } + } + }, + + // single transaction, multiple 16 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) }, + { 0x1030, UUID(0xFFAC) }, + { 0x1040, UUID(0xFFAD) } + } + } + }, + // single transaction, multiple 16 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1040, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) }, + { 0x1030, UUID(0xFFAC) }, + { 0x1040, UUID(0xFFAD) } + } + } + }, + // single transaction, multiple 128 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") }, + { 0x1020, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") }, + { 0x1040, UUID("86c7c947-c079-46e4-8bc4-7bfc011b7ffe") } + } + } + }, + // single transaction, multiple 128 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1040, + { + { + { 0x1001, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") }, + { 0x1020, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") }, + { 0x1040, UUID("86c7c947-c079-46e4-8bc4-7bfc011b7ffe") } + } + } + }, + + // multiple transaction, single 16 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) } + }, + { + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1030, UUID(0xFFAC) } + } + } + }, + // single transaction, single 16 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1030, + { + { + { 0x1001, UUID(0xFFAA) } + }, + { + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1030, UUID(0xFFAC) } + } + } + }, + // multiple transaction, single 128 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID("1881e01d-02af-41e9-abe9-bc940c09ca65") } + }, + { + { 0x1015, UUID("0eb7f338-cd2a-4220-9f41-b61d95485b8d") } + }, + { + { 0x1050, UUID("ac77d105-73eb-4cfd-959e-c191feb592b8") } + } + } + }, + // multiple transaction, single 128 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1050, + { + { + { 0x1001, UUID("1881e01d-02af-41e9-abe9-bc940c09ca65") } + }, + { + { 0x1015, UUID("0eb7f338-cd2a-4220-9f41-b61d95485b8d") } + }, + { + { 0x1050, UUID("ac77d105-73eb-4cfd-959e-c191feb592b8") } + } + } + }, + + // multiple transaction, multiple 16 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1030, UUID(0xFFAC) }, + { 0x1040, UUID(0xFFAD) } + }, + { + { 0x1055, UUID(0xFFAE) }, + { 0x1075, UUID(0xFFAF) } + } + } + }, + // multiple transaction, multiple 16 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1075, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1030, UUID(0xFFAC) }, + { 0x1040, UUID(0xFFAD) } + }, + { + { 0x1055, UUID(0xFFAE) }, + { 0x1075, UUID(0xFFAF) } + } + } + }, + // multiple transaction, multiple 128 bit value in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") } + }, + { + { 0x1020, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") }, + }, + { + { 0x1040, UUID("86c7c947-c079-46e4-8bc4-7bfc011b7ffe") } + } + } + }, + // multiple transaction, multiple 128 bit value in the transaction, ends without error + test_param_t { + 0x1000, + 0x1040, + { + { + { 0x1001, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") } + }, + { + { 0x1020, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") }, + }, + { + { 0x1040, UUID("86c7c947-c079-46e4-8bc4-7bfc011b7ffe") } + } + } + }, + + // multiple transaction, mixed UUID in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) } + }, + { + { 0x1020, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") } + }, + { + { 0x1025, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") } + }, + { + { 0x1030, UUID(0xFFAC) } + } + } + }, + // multiple transaction, mixed UUID in the transaction, ends without error + test_param_t { + 0x1000, + 0x1030, + { + { + { 0x1001, UUID(0xFFAA) } + }, + { + { 0x1020, UUID("0b20ef0c-578b-4b8a-8c59-065d4aff5b2e") } + }, + { + { 0x1025, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") } + }, + { + { 0x1030, UUID(0xFFAC) } + } + } + }, + + // multiple transaction, mixed multiple UUIDs in the transaction, ends with error + test_param_t { + 0x1000, + 0x1100, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1025, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") } + }, + { + { 0x1055, UUID(0xFFAE) }, + { 0x1075, UUID(0xFFAF) } + } + } + }, + // multiple transaction, mixed multiple UUIDs in the transaction, ends without error + test_param_t { + 0x1000, + 0x1075, + { + { + { 0x1001, UUID(0xFFAA) }, + { 0x1020, UUID(0xFFAB) } + }, + { + { 0x1025, UUID("66ef3556-8889-4741-adfc-c89d7ff22710") }, + { 0x1030, UUID("96a16d34-46d6-4081-82c3-25420ba01e5b") } + }, + { + { 0x1055, UUID(0xFFAE) }, + { 0x1075, UUID(0xFFAF) } + } + } + } + ) +); + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestDiscoverAllServices.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestDiscoverAllServices.cpp new file mode 100644 index 0000000000..dadec0694f --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestDiscoverAllServices.cpp @@ -0,0 +1,722 @@ +/* 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; + +/* + * Parametric fixture used for service discovery. + * + * Parameters are modeled by a tuple where: + * - [0] std::tuple: pair of boolean indicating the presence of the + * service callback and the characteristic callback. + * - [1] UUID: UUID used for service filtering. + * - [2] UUID: UUID used for characteristic filtering. + * - [3] server_description_t: Discovered service layout. + */ +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() { + } + + // setup the test environment + 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; + } + + /* + * Return a closure that sends a read by group type response to a client. + * @param[in] service_transactions The services to include in the response. + */ + 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; + }); + } + + /* + * Return a closure that sends a find by type value response to the client. + * @param[in] service_transactions Services to include in the response sent + * to the client. + */ + 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; + }); + } + + /** + * Convert a DiscoveredCharacteristic::Properties_t into an uint8_t + */ + 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 */ + ); + } + + /** + * Return a closure that sends a read by type to a client. + * @param[in] transaction Characteristics to include in the response send to + * the client. + */ + 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; + }); + } + + /* + * Return a closure that send an error response to the client. + * @param[in] opcode Opcode that caused the error. + * @param[in] error_code Error code. + */ + 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; + }); + } + + /* + * Set service discovery expectation when the procedure discover all services + * is used. + */ + 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; + } + + /* + * Compute the list of services discovered during service discovery. + */ + 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; + } + + /* + * Compute the transactions present during the characteristic discovery process. + */ + 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) { + ///////////////////////////// + // Install default calls + 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; + })); + } + + ///////////////////////////// + // Install expectations + { + 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)); + } + + ///////////////////////////// + // Launch the discovery process. + 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, + // Yield all combination of each generator + ::testing::Combine( + // service cb generator + ::testing::Values( + std::tuple(true, false), + std::tuple(false, true), + std::tuple(true, true) + ), + // service UUID filter generator + ::testing::Values(UUID(), UUID(0x1452), UUID("a3d1495f-dba7-4441-99f2-d0a20f663422")), + // characteristic UUID filter generator + ::testing::Values(UUID(), UUID(0xBEEF), UUID("1f551ee3-aef4-4719-8c52-8b419fc4ac01")), + // server layout generator. + ::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) } + } + } + } + ) + ) +); + + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestNoCb.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestNoCb.cpp new file mode 100644 index 0000000000..463d21eeae --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestNoCb.cpp @@ -0,0 +1,178 @@ +/* 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/MockCallbacks.h" +#include "mock/MockPalGattClient.h" + +using std::nullptr_t; + +using ble::generic::GenericGattClient; +using ble::pal::vendor::mock::MockPalGattClient; + +using ::testing::_; + +/* + * Parametric fixture used to test service discovery when no callback is registered. + * Parameters are: + * - [0]: service UUID filter + * - [1]: characteristic UUID filter + */ +class LaunchDiscoveryNoCb : public ::testing::TestWithParam> { +protected: + typedef std::tuple parameters_t; + + LaunchDiscoveryNoCb() : + _mock_client(), + _gatt_client(&_mock_client), + _connection_handle(0xDEAD), + _termination_callback(), + _service_cb(), + _characteristic_cb(), + _service_filter(), + _characteristic_filter() { + } + + virtual void SetUp() { + _gatt_client.onServiceDiscoveryTermination( + makeFunctionPointer( + &_termination_callback, + &termination_callback_t::call + ) + ); + + std::tie(_service_filter, _characteristic_filter) = GetParam(); + } + + MockPalGattClient _mock_client; + GenericGattClient _gatt_client; + + const Gap::Handle_t _connection_handle; + termination_callback_t _termination_callback; + nullptr_t _service_cb; + nullptr_t _characteristic_cb; + UUID _service_filter; + UUID _characteristic_filter; +}; + + +/* Given initialised GattClient + * When the service discovery is launched with service and characteristic + * callbacks set to NULL. + * Then + * - The function shall return BLE_ERROR_NONE + * - No call to any discovery function shall have been made + * - The termination callback shall have been called once with the connection + * handle used to launch the discovery. + */ + +// no pending transaction +TEST_P(LaunchDiscoveryNoCb, shall_never_launch_any_discovery) { + EXPECT_FALSE(_gatt_client.isServiceDiscoveryActive()); + + // no call on _mock_client expected + EXPECT_CALL(_mock_client, discover_primary_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_primary_service_by_service_uuid(_, _, _)).Times(0); + EXPECT_CALL(_mock_client, find_included_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_characteristics_of_a_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_characteristics_descriptors(_, _)).Times(0); + + // termination callback shall be call once with connection handle as parameter + EXPECT_CALL(_termination_callback, call(_)).Times(0); + EXPECT_CALL(_termination_callback, call(_connection_handle)).Times(1); + + ble_error_t err = _gatt_client.launchServiceDiscovery( + _connection_handle, + _service_cb, + _characteristic_cb, + _service_filter, + _characteristic_filter + ); + + EXPECT_EQ(BLE_ERROR_NONE, err); + EXPECT_FALSE(_gatt_client.isServiceDiscoveryActive()); +} + +// no CB +// pending transaction +TEST_P(LaunchDiscoveryNoCb, shall_not_change_discovery_status) { + EXPECT_FALSE(_gatt_client.isServiceDiscoveryActive()); + + // launch a discovery on another connection + const Gap::Handle_t dummy_connection_handle = _connection_handle + 1; + void (*dummy_service_cb)(const DiscoveredService*) = [](const DiscoveredService *) { }; + + EXPECT_CALL(_mock_client, discover_primary_service(dummy_connection_handle, _)) + .WillOnce(::testing::Return(BLE_ERROR_NONE)); + + _gatt_client.launchServiceDiscovery( + dummy_connection_handle, + dummy_service_cb, + NULL, + UUID((uint16_t) 0), + UUID((uint16_t) 0) + ); + + EXPECT_TRUE(_gatt_client.isServiceDiscoveryActive()); + + // no call on _mock_client expected + EXPECT_CALL(_mock_client, discover_primary_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_primary_service_by_service_uuid(_, _, _)).Times(0); + EXPECT_CALL(_mock_client, find_included_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_characteristics_of_a_service(_, _)).Times(0); + EXPECT_CALL(_mock_client, discover_characteristics_descriptors(_, _)).Times(0); + + // termination callback shall be call once with connection handle as parameter + EXPECT_CALL(_termination_callback, call(_)).Times(0); + EXPECT_CALL(_termination_callback, call(_connection_handle)).Times(1); + + ble_error_t err = _gatt_client.launchServiceDiscovery( + _connection_handle, + _service_cb, + _characteristic_cb, + _service_filter, + _characteristic_filter + ); + + EXPECT_EQ(BLE_ERROR_NONE, err); + EXPECT_TRUE(_gatt_client.isServiceDiscoveryActive()); +} + +// Instantiate tests cases with the given parameters +INSTANTIATE_TEST_CASE_P( + GattClient_launch_discovery_no_cb, + LaunchDiscoveryNoCb, + // Yield combination of each generator value + ::testing::Combine( + // service UUID filter + ::testing::Values(UUID(), UUID(0x1452), UUID("a3d1495f-dba7-4441-99f2-d0a20f663422")), + // characteristic UUID filter + ::testing::Values(UUID(), UUID(0xBEEF), UUID("1f551ee3-aef4-4719-8c52-8b419fc4ac01")) + ) +); diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestRead.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestRead.cpp new file mode 100644 index 0000000000..3e811606c8 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestRead.cpp @@ -0,0 +1,570 @@ +/* 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 + +#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/PrettyPrinter.h" +#include "util/Equality.h" + +using ble::pal::AttErrorResponse; +using ble::pal::AttributeOpcode; +using ble::pal::AttReadResponse; +using ble::pal::AttReadBlobResponse; +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::InSequence; +using ::testing::Mock; +using ::testing::Pointee; + +using std::vector; +using std::tuple; + +static vector make_char_value(uint16_t length) { + vector characteristic_value(length); + for (auto& byte : characteristic_value) { + byte = std::rand(); + } + return characteristic_value; +} + +/* + * fixture used to test GattClient::read. + */ +class TestGattClientRead : public ::testing::Test { +protected: + TestGattClientRead() : + _mock_client(), + _gatt_client(&_mock_client), + _connection_handle(0xDEAD), + _attribute_handle(0x5645), + _read_cb(), + _mtu_size(23) { + } + + virtual void SetUp() { + _gatt_client.onDataRead( + makeFunctionPointer(&_read_cb, &read_callback_t::call) + ); + + ON_CALL(_mock_client, get_mtu_size(_connection_handle, _)). + WillByDefault(Invoke([&](auto, auto& size){ + size = this->_mtu_size; + return BLE_ERROR_NONE; + })); + } + + void set_mtu_size_stub() { + EXPECT_CALL(_mock_client, get_mtu_size(_connection_handle, _)) + .WillRepeatedly(::testing::DoDefault()); + } + + MockPalGattClient _mock_client; + GenericGattClient _gatt_client; + const Gap::Handle_t _connection_handle; + uint16_t _attribute_handle; + read_callback_t _read_cb; + uint16_t _mtu_size; +}; + +/* + * Given an attribute on a server with a value with a length less than ATT_MTU - 1. + * When the client issue tries to read is with GattClient::read with an offset + * of 0. + * Then: + * - The function read_attribute_value from the pal is called with + * connection handle and attribute handle used in GattClient::read. + * - The pal::GattClient fire an AttReadResponse containing the value of the + * attribute. + * - The generic GattClient invoke the read handler with a content equal to + * the value received and an error _status set to BLE_ERROR_NONE. + */ +TEST_F(TestGattClientRead, read_short_characteristic_without_offset_shall_succeed) { + uint16_t offset = 0; // force offset to 0 for this test + + auto char_value = make_char_value(1000); + + // iterate over possible MTU size + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 5) { + // iterate over possible characteristic value size + for (uint16_t char_len = 0; char_len < (_mtu_size - 1); ++char_len) { + set_mtu_size_stub(); + + InSequence seq; + + EXPECT_CALL( + _mock_client, read_attribute_value(_connection_handle, _attribute_handle) + ).WillOnce(Invoke([&](auto connection, auto attribute) { + _mock_client.on_server_event( + connection, + AttReadResponse( + make_const_ArrayView(char_value.data(), char_len) + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + (uint16_t) char_len, + char_value.data(), + BLE_ERROR_NONE + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); + + bool can_continue = Mock::VerifyAndClearExpectations(&_read_cb) && Mock::VerifyAndClearExpectations(&_mock_client); + if (!can_continue) { + return; + } + } + } +} + +/* + * Given an attribute on a server with a value with a length less than ATT_MTU - 1. + * When the client issue tries to reads it with GattClient::read with an offset + * not equal to 0 and in the range of the attribute length. + * Then: + * - The function read_attribute_blob from the pal is called with + * connection handle, the attribute handle and the offset used in + * GattClient::read. + * - The pal::GattClient fire an AttErrorresponse with: + * * opcode equal to READ_BLOB_REQUEST + * * _error_code equal to ATTRIBUTE_NOT_LONG + * - The generic GattClient invoke the read handler with the _status set to + * BLE_ERROR_PARAM_OUT_OF_RANGE and an _error_code set to ATTRIBUTE_NOT_LONG. + */ +TEST_F(TestGattClientRead, read_short_characteristic_with_offset_shall_fail) { + // iterate over possible MTU size + for (_mtu_size = 23; _mtu_size < 517; _mtu_size+=10) { + // iterate over possible characteristic value size + for (uint16_t char_len = 1; char_len < (_mtu_size - 1); char_len += 10) { + // iterate over possible offsets + for (uint16_t offset = 1; offset <= char_len; offset += 10) { + + InSequence seq; + + EXPECT_CALL( + _mock_client, read_attribute_blob(_connection_handle, _attribute_handle, offset) + ).WillOnce(Invoke([&](auto connection, auto attribute, auto off) { + _mock_client.on_server_event( + connection, + AttErrorResponse( + AttributeOpcode::READ_BLOB_REQUEST, + attribute, + AttErrorResponse::ATTRIBUTE_NOT_LONG + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + AttErrorResponse::ATTRIBUTE_NOT_LONG, + NULL, + BLE_ERROR_PARAM_OUT_OF_RANGE + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); + + bool can_continue = Mock::VerifyAndClearExpectations(&_read_cb) && Mock::VerifyAndClearExpectations(&_mock_client); + if (!can_continue) { + return; + } + } + } + } +} + +/* + * Given an attribute on a server with a value with a length less than ATT_MTU - 1. + * When the client tries to reads it with GattClient::read with an offset + * out of the range of the attribute length. + * Then: + * - The function read_attribute_blob from the pal is called with + * connection handle, the attribute handle and the offset used in + * GattClient::read. + * - The pal::GattClient fire an AttErrorresponse with: + * * opcode equal to READ_BLOB_REQUEST + * * _error_code equal to INVALID_OFFSET + * - The generic GattClient invoke the read handler with the _status set to + * BLE_ERROR_PARAM_OUT_OF_RANGE and an _error_code set to INVALID_OFFSET. + */ +TEST_F(TestGattClientRead, read_with_out_of_range_offset_shall_fail) { + // iterate over possible MTU size + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + // iterate over possible characteristic value size + for (uint16_t char_len = 1; char_len < (_mtu_size - 1); char_len += 10) { + // iterate over possible offsets + for (uint16_t offset = char_len + 1; offset < 518; offset += 10) { + InSequence seq; + + EXPECT_CALL( + _mock_client, read_attribute_blob(_connection_handle, _attribute_handle, offset) + ).WillOnce(Invoke([&](auto connection, auto attribute, auto off) { + _mock_client.on_server_event( + connection, + AttErrorResponse( + AttributeOpcode::READ_BLOB_REQUEST, + attribute, + AttErrorResponse::INVALID_OFFSET + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + AttErrorResponse::INVALID_OFFSET, + NULL, + BLE_ERROR_PARAM_OUT_OF_RANGE, + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); + + bool can_continue = Mock::VerifyAndClearExpectations(&_read_cb) && Mock::VerifyAndClearExpectations(&_mock_client); + if (!can_continue) { + return; + } + } + } + } +} + +/* + * Parametric fixture used to test error generated during GattClient::read. + * Parameters are: + * - [0] The attribute error code + * - [1] Expected status returned in the read callback. + */ +class TestGattClientReadAttributeError : + public TestGattClientRead, + public ::testing::WithParamInterface> { +protected: + TestGattClientReadAttributeError() : + TestGattClientRead(), + _error_code((AttErrorResponse::AttributeErrorCode) 0x00), + _status(BLE_ERROR_NONE) { + } + + virtual void SetUp() { + TestGattClientRead::SetUp(); + std::tie(_error_code, _status) = GetParam(); + } + + AttErrorResponse::AttributeErrorCode _error_code; + ble_error_t _status; +}; + +/* + * Given an attribute which cannot be read. + * When the client tries to reads it with GattClient::read with an offset + * of 0 + * Then: + * - The function read_attribute_value from the pal is called with + * connection handle, the attribute handle in GattClient::read. + * - The pal::GattClient fire an AttErrorresponse with: + * * opcode equal to READ_REQUEST + * * _error_code equal to the expected error + * - The generic GattClient invoke the read handler with the _status set to + * the expected _status and an _error_code set to error code in the + * AttErrorResponse. + */ +TEST_P(TestGattClientReadAttributeError, read_no_offset) { + uint16_t offset = 0; + + InSequence seq; + EXPECT_CALL( + _mock_client, read_attribute_value(_connection_handle, _attribute_handle) + ).WillOnce(Invoke([&](auto connection, auto attribute) { + _mock_client.on_server_event( + connection, + AttErrorResponse( + AttributeOpcode::READ_REQUEST, + attribute, + _error_code + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + _error_code, + /* data */ NULL, + _status + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); +} + +/* + * Given an attribute which cannot be read. + * When the client tries to reads it with GattClient::read with an offset + * of 0 + * Then: + * - The function read_attribute_blob from the pal is called with + * connection handle, the attribute handle and the offset passed to + * GattClient::read. + * - The pal::GattClient fire an AttErrorresponse with: + * * opcode equal to READ_BLOB_REQUEST + * * _error_code equal to the expected error + * - The generic GattClient invoke the read handler with the _status set to + * the expected _status and an _error_code set to error code in the + * AttErrorResponse. + */ +TEST_P(TestGattClientReadAttributeError, read_with_offset) { + uint16_t offset = 10; + + InSequence seq; + + EXPECT_CALL( + _mock_client, read_attribute_blob(_connection_handle, _attribute_handle, offset) + ).WillOnce(Invoke([&](auto connection, auto attribute, auto off) { + _mock_client.on_server_event( + connection, + AttErrorResponse( + AttributeOpcode::READ_BLOB_REQUEST, + attribute, + _error_code + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + _error_code, + /* data */ NULL, + _status + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); +} + +// Instantiate test cases using TestGattClientReadAttributeError +INSTANTIATE_TEST_CASE_P( + TestGattClientReadAttributeError_combination, + TestGattClientReadAttributeError, + ::testing::Values( + std::make_tuple(AttErrorResponse::INVALID_HANDLE, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHORIZATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHENTICATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::READ_NOT_PERMITTED, BLE_ERROR_OPERATION_NOT_PERMITTED) + ) +); + +/* + * Given a readable attribute with a value longer than ATT_MTU - 2. + * When the GattClient::read is called without offset + * Then: + * - The client call the pal function read_attribute_value with the connection + * handle and the the attribute handle used in GattClient::read. + * When the pal::GattClient fire an AttReadResponse containing the first + * (ATT_MTU - 1) bytes of the attribute value. + * Then: + * - The client call the function read_attribute_blob with the connection + * handle and the attribute handle used in GattClient::read. The offset is + * equal of the number of bytes read in the characteristic. + * When the pal::Client fire an AttReadBlobResponse containing the partial value + * of the characteristic starting at the offset requested. If the length of + * the remaining bytes is greater than ATT_MTU - 1 then the length of the + * partial value in the response shall be ATT_MTU - 1. Otherwise it is the + * length of the remaining bytes. + * Then: + * - If the value received by the client has a len greater than ATT_MTU - 1. + * Then the client issue a new read_attribute_blob with an updated index. + * - Else the read callback is called with the value of the attribute read. + */ +TEST_F(TestGattClientRead, read_long_characteristic_without_offset_shall_succeed) { + const uint16_t offset = 0; // force offset to 0 for this test + + auto char_value = make_char_value(517 * 6); + + // iterate over possible MTU size + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 5) { + // iterate over possible characteristic value size + for (uint16_t char_len = (_mtu_size - 1); char_len < (_mtu_size * 6); char_len += 10) { + set_mtu_size_stub(); + + InSequence seq; + EXPECT_CALL( + _mock_client, read_attribute_value(_connection_handle, _attribute_handle) + ).WillOnce(Invoke([&](auto connection, auto attribute) { + _mock_client.on_server_event( + connection, + AttReadResponse( + make_const_ArrayView(char_value.data(), _mtu_size - 1) + ) + ); + return BLE_ERROR_NONE; + })); + + for(uint16_t blob_offset = (_mtu_size - 1); blob_offset <= char_len; blob_offset += (_mtu_size - 1)) { + EXPECT_CALL( + _mock_client, read_attribute_blob(_connection_handle, _attribute_handle, blob_offset) + ).WillOnce(Invoke([&](auto connection, auto attribute, uint16_t offset) { + _mock_client.on_server_event( + connection, + AttReadBlobResponse( + make_const_ArrayView( + char_value.data() + offset, + (char_len - offset) > (_mtu_size - 1) ? (_mtu_size - 1) : (char_len - offset) + ) + ) + ); + + return BLE_ERROR_NONE; + })); + } + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + (uint16_t) char_len, + char_value.data(), + BLE_ERROR_NONE + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); + + bool can_continue = Mock::VerifyAndClearExpectations(&_mock_client) && Mock::VerifyAndClearExpectations(&_read_cb); + if (!can_continue) { + return; + } + } + } +} + +/* + * Given a readable attribute with a value longer than ATT_MTU - 2. + * When the GattClient::read is called without offset + * Then: + * - The client call the pal function read_attribute_blob with the connection + * handle, the attribute handle and the offset used in GattClient::read. + * When the pal::GattClient fire an AttReadResponse containing the first bytes + * of the attribute value. + * Then: + * - If the response length is less than (ATT_MTU - 1), the read callback is + * called with the value of the attribute read. + * - The client call the function read_attribute_blob with the connection + * handle and the attribute handle used in GattClient::read. The offset is + * equal of the number of bytes read in the characteristic plus the + * first offset. + * When the pal::Client fire an AttReadBlobResponse containing the partial value + * of the characteristic starting at the offset requested. If the length of + * the remaining bytes is greater than ATT_MTU - 1 then the length of the + * partial value in the response shall be ATT_MTU - 1. Otherwise it is the + * length of the remaining bytes. + * Then: + * - If the value received by the client has a len greater than ATT_MTU - 1. + * Then the client issue a new read_attribute_blob with an updated index. + * - Else the read callback is called with the value of the attribute read. + */ +TEST_F(TestGattClientRead, read_long_characteristic_with_offset_shall_succeed) { + auto char_value = make_char_value(517 * 6); + + // iterate over possible MTU size + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 35) { + // iterate over possible characteristic value size + for (uint16_t char_len = (_mtu_size - 1); char_len < (_mtu_size * 4); char_len += 30) { + for(uint16_t offset = 1; offset < char_len; offset += 20) { + set_mtu_size_stub(); + + InSequence seq; + for(uint16_t blob_offset = offset; blob_offset <= char_len; blob_offset += (_mtu_size - 1)) { + EXPECT_CALL( + _mock_client, read_attribute_blob(_connection_handle, _attribute_handle, blob_offset) + ).WillOnce(Invoke([&](auto connection, auto attribute, auto offset) { + _mock_client.on_server_event( + connection, + AttReadBlobResponse( + make_const_ArrayView( + char_value.data() + offset, + (char_len - offset) > (_mtu_size - 1) ? (_mtu_size - 1) : (char_len - offset) + ) + ) + ); + + return BLE_ERROR_NONE; + })); + } + + EXPECT_CALL(_read_cb, call(Pointee(GattReadCallbackParams { + _connection_handle, + _attribute_handle, + offset, + (uint16_t) (char_len - offset), + char_value.data() + offset, + BLE_ERROR_NONE + }))); + + ble_error_t err = _gatt_client.read(_connection_handle, _attribute_handle, offset); + EXPECT_EQ(err, BLE_ERROR_NONE); + + bool can_continue = Mock::VerifyAndClearExpectations(&_mock_client) && Mock::VerifyAndClearExpectations(&_read_cb); + if (!can_continue) { + return; + } + } + } + } +} + + +// TODO: Add test for failure in the middle of the transaction diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestServerEvent.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestServerEvent.cpp new file mode 100644 index 0000000000..0f4b1d2347 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestServerEvent.cpp @@ -0,0 +1,158 @@ +/* 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/MockCallbacks.h" +#include "mock/MockPalGattClient.h" + +#include "util/PrettyPrinter.h" +#include "util/Equality.h" + +using ble::pal::AttHandleValueIndication; +using ble::pal::AttHandleValueNotification; +using ble::generic::GenericGattClient; +using ble::pal::vendor::mock::MockPalGattClient; + +using ble::connection_handle_t; +using ble::make_const_ArrayView; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Pointee; + +using std::vector; + +static vector make_char_value(uint16_t length) { + vector characteristic_value(length); + for (auto& byte : characteristic_value) { + byte = std::rand(); + } + return characteristic_value; +} + +/* + * Fixture use to test server notification or indication. + */ +class TestGattServerEvent : public ::testing::Test { +protected: + TestGattServerEvent() : + _mock_client(), + _gatt_client(&_mock_client), + _connection_handle(0xDEAD), + _attribute_handle(0x5645), + _mtu_size(23) { + } + + virtual void SetUp() { + _gatt_client.onHVX( + makeFunctionPointer(&_event_cb, &server_event_callback_t::call) + ); + + ON_CALL(_mock_client, get_mtu_size(_connection_handle, _)). + WillByDefault(Invoke([&](auto, auto& mtu_size ){ + mtu_size = this->_mtu_size; + return BLE_ERROR_NONE; + })); + } + + void set_mtu_size_stub() { + EXPECT_CALL(_mock_client, get_mtu_size(_connection_handle, _)) + .WillRepeatedly(::testing::DoDefault()); + } + + MockPalGattClient _mock_client; + GenericGattClient _gatt_client; + const Gap::Handle_t _connection_handle; + uint16_t _attribute_handle; + server_event_callback_t _event_cb; + uint16_t _mtu_size; +}; + +/* + * Ensure the right callback is called with the correct parameters when an + * indication is received. + */ +TEST_F(TestGattServerEvent, event_callback_shall_be_called_on_indication) { + auto value = make_char_value(5000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for (uint16_t len = 0; len < (_mtu_size - 3); len += 10) { + + EXPECT_CALL( + _event_cb, call(Pointee(GattHVXCallbackParams { + _connection_handle, + _attribute_handle, + HVXType_t::BLE_HVX_INDICATION, + len, + value.data() + })) + ); + + _mock_client.on_server_event( + _connection_handle, + AttHandleValueIndication( + _attribute_handle, + make_const_ArrayView(value.data(), len) + ) + ); + } + } +} + +/* + * Ensure the right callback is called with the correct parameters when a + * notification is received. + */ +TEST_F(TestGattServerEvent, event_callback_shall_be_called_on_notification) { + auto value = make_char_value(5000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for (uint16_t len = 0; len < (_mtu_size - 3); len += 10) { + + EXPECT_CALL( + _event_cb, call(Pointee(GattHVXCallbackParams { + _connection_handle, + _attribute_handle, + HVXType_t::BLE_HVX_NOTIFICATION, + len, + value.data() + })) + ); + + _mock_client.on_server_event( + _connection_handle, + AttHandleValueNotification( + _attribute_handle, + make_const_ArrayView(value.data(), len) + ) + ); + } + } +} + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/TestWrite.cpp b/features/FEATURE_BLE/tests/generic/GattClient/TestWrite.cpp new file mode 100644 index 0000000000..b4386450f3 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/TestWrite.cpp @@ -0,0 +1,719 @@ +/* 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 + +#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/PrettyPrinter.h" +#include "util/Equality.h" + +using ble::pal::AttErrorResponse; +using ble::pal::AttributeOpcode; +using ble::pal::AttWriteResponse; +using ble::pal::AttPrepareWriteResponse; +using ble::pal::AttExecuteWriteResponse; +using ble::generic::GenericGattClient; +using ble::pal::vendor::mock::MockPalGattClient; + +using ble::connection_handle_t; +using ble::attribute_handle_t; +using ble::ArrayView; +using ble::make_const_ArrayView; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Mock; +using ::testing::Pointee; + +using std::vector; +using std::tuple; + +static vector make_char_value(uint16_t length) { + vector characteristic_value(length); + for (auto& byte : characteristic_value) { + byte = std::rand(); + } + return characteristic_value; +} + +/* + * Fixture used for GattClient::write tests. + */ +class TestGattClientWrite : public ::testing::Test { +protected: + TestGattClientWrite() : + _mock_client(), + _gatt_client(&_mock_client), + _connection_handle(0xDEAD), + _attribute_handle(0x5645), + _write_cb(), + _mtu_size(23) { + } + + virtual void SetUp() { + _gatt_client.onDataWritten( + makeFunctionPointer(&_write_cb, &write_callback_t::call) + ); + + ON_CALL( + _mock_client, get_mtu_size(_connection_handle, _) + ).WillByDefault(Invoke([&](auto, auto& mtu_size){ + mtu_size = _mtu_size; + return BLE_ERROR_NONE; + })); + } + + void set_mtu_size_stub() { + EXPECT_CALL(_mock_client, get_mtu_size(_connection_handle, _)) + .WillRepeatedly(::testing::DoDefault()); + } + + MockPalGattClient _mock_client; + GenericGattClient _gatt_client; + const Gap::Handle_t _connection_handle; + uint16_t _attribute_handle; + write_callback_t _write_cb; + uint16_t _mtu_size; +}; + +/** + * Given a payload shorter than the current MTU size - 2. + * When the payload is sent in a write command + * Then the payload is sent to the pal without error. + */ +TEST_F(TestGattClientWrite, write_command_should_succeed_if_it_fit_in_the_payload) { + auto value = make_char_value(1000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for(uint16_t len = 0; len <= (_mtu_size - 3); len += 10) { + set_mtu_size_stub(); + + EXPECT_CALL( + _mock_client, write_without_response( + _connection_handle, + _attribute_handle, + make_const_ArrayView(value.data(), len) + ) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_CMD, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); + + bool can_continue = + Mock::VerifyAndClearExpectations(&_write_cb) && + Mock::VerifyAndClearExpectations(&_mock_client); + + if (!can_continue) { + return; + } + } + } +} + +/** + * Given a payload with a length longer than MTU size -1 + * When the payload is sent in a write command + * Then the function returns an error. + */ +TEST_F(TestGattClientWrite, write_command_should_fail_if_it_doesnt_fit_in_the_payload) { + auto value = make_char_value(1000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for(size_t len = (_mtu_size - 3) + 1; len < 517; len += 10) { + set_mtu_size_stub(); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_CMD, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_PARAM_OUT_OF_RANGE); + + bool can_continue = + Mock::VerifyAndClearExpectations(&_write_cb) && + Mock::VerifyAndClearExpectations(&_mock_client) && + (error == BLE_ERROR_PARAM_OUT_OF_RANGE); + + if (!can_continue) { + return; + } + } + } +} + +// TODO: test unexpected results from write_without_response + +/** + * Given a payload shorter than the current MTU size - 2. + * When the payload is sent in a write request + * Then if the operation succeed the write callback shall be invoked. + */ +TEST_F(TestGattClientWrite, write_request_short_characteristics) { + auto value = make_char_value(1000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for(uint16_t len = 0; len <= (_mtu_size - 3); len += 10) { + set_mtu_size_stub(); + + EXPECT_CALL( + _mock_client, write_attribute( + _connection_handle, + _attribute_handle, + make_const_ArrayView(value.data(), len) + ) + ).WillOnce(Invoke([&](auto conn_handle, auto, const auto&) { + _mock_client.on_server_event( + conn_handle, + AttWriteResponse() + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL(_write_cb, call(Pointee(GattWriteCallbackParams { + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + BLE_ERROR_NONE, + /* error code */ 0, + }))); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); + + bool can_continue = + Mock::VerifyAndClearExpectations(&_write_cb) && + Mock::VerifyAndClearExpectations(&_mock_client); + + if (!can_continue) { + return; + } + } + } +} + +/* + * Fixture used to test error handling during a characteristic write. + * Parameters are: + * [0] AttErrorResponse::AttributeErrorCode The attribute error code to push in + * the AttErrorresponse sent to the client. + * [1] ble_error_t The expected error status present in the parameters received + * by the callback handling GattClient::write. + */ +class TestGattClientWriteAttributeError : + public TestGattClientWrite, + public ::testing::WithParamInterface> { +protected: + TestGattClientWriteAttributeError() : + TestGattClientWrite(), + error_code((AttErrorResponse::AttributeErrorCode) 0x00), + status(BLE_ERROR_NONE) { + } + + virtual void SetUp() { + TestGattClientWrite::SetUp(); + std::tie(error_code, status) = GetParam(); + } + + AttErrorResponse::AttributeErrorCode error_code; + ble_error_t status; +}; + +/** + * Given a payload shorter than the current MTU size - 2. + * When the payload is sent in a write request + * Then if the operation fail the write callback shall report the error + */ +TEST_P(TestGattClientWriteAttributeError, write_request_short_characteristics) { + auto value = make_char_value(1000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for(uint16_t len = 0; len <= (_mtu_size - 3); len += 10) { + set_mtu_size_stub(); + + EXPECT_CALL( + _mock_client, write_attribute( + _connection_handle, + _attribute_handle, + make_const_ArrayView(value.data(), len) + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto&) { + _mock_client.on_server_event( + conn_handle, + AttErrorResponse( + AttributeOpcode::WRITE_REQUEST, + att_handle, + error_code + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _write_cb, call(Pointee(GattWriteCallbackParams{ + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_PREP_WRITE_REQ, + status, + error_code + })) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); + + bool can_continue = + Mock::VerifyAndClearExpectations(&_write_cb) && + Mock::VerifyAndClearExpectations(&_mock_client); + + if (!can_continue) { + return; + } + } + } +} + +// Instantiate test cases using the TestGattClientWriteAttributeError fixture +INSTANTIATE_TEST_CASE_P( + TestGattClientWriteAttributeError_combination, + TestGattClientWriteAttributeError, + ::testing::Values( + std::make_tuple(AttErrorResponse::INVALID_ATTRIBUTE_VALUE_LENGTH, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::INVALID_HANDLE, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHORIZATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHENTICATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::WRITE_NOT_PERMITTED, BLE_ERROR_OPERATION_NOT_PERMITTED), + std::make_tuple((AttErrorResponse::AttributeErrorCode)/* application error */ 0x80, BLE_ERROR_UNSPECIFIED) + ) +); + +/** + * Given a payload with a length bigger than the current MTU size - 2. + * When the payload is written as using Gattclient::write + * Then the payload is split into multiple chunks of a maximum size equal to + * MTU_size - 5. Those chunks are sent sequentially using a prepare write + * request which is answered by a prepare write response by the server. + * Once all the chunks have been received by the server, the client sends an + * Execute write request. Once the client received an Execute Write request, + * the server shall invoke the write callback. + */ +TEST_F(TestGattClientWrite, write_request_long_characteristics) { + auto value = make_char_value(10000); + + for (_mtu_size = 23; _mtu_size < 517; _mtu_size += 10) { + for(uint16_t len = (_mtu_size - 2); len <= (_mtu_size * 4); len += 10) { + set_mtu_size_stub(); + + InSequence seq; + + for (uint16_t offset = 0; offset < len; offset += (_mtu_size - 5)) { + EXPECT_CALL( + _mock_client, queue_prepare_write( + _connection_handle, + _attribute_handle, + make_const_ArrayView( + value.data() + offset, + ((len - offset) > (_mtu_size - 5)) ? (_mtu_size - 5) : (len - offset) + ), + offset + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto& data, auto offset) { + _mock_client.on_server_event( + conn_handle, + AttPrepareWriteResponse( + att_handle, + offset, + data + ) + ); + return BLE_ERROR_NONE; + })); + } + + EXPECT_CALL( + _mock_client, execute_write_queue( + _connection_handle, + true + ) + ).WillOnce(Invoke([&](auto conn_handle, bool execute) { + _mock_client.on_server_event( + conn_handle, + AttExecuteWriteResponse() + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _write_cb, call(Pointee(GattWriteCallbackParams{ + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + BLE_ERROR_NONE, + /* error code */ 0, + })) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); + + bool can_continue = + Mock::VerifyAndClearExpectations(&_write_cb) && + Mock::VerifyAndClearExpectations(&_mock_client); + + if (!can_continue) { + return; + } + } + } +} + +/* + * Fixture used to test long write errors. + * Parameter are similar to the one used in TestGattClientWriteAttributeError + */ +class TestGattClientPrepareWriteAttributeError : public TestGattClientWriteAttributeError { }; + +/** + * Test errors received by a client at the beginning of a long write procedure. + */ +TEST_P(TestGattClientPrepareWriteAttributeError, prepare_write_error) { + auto value = make_char_value(10000); + + _mtu_size = 23; + set_mtu_size_stub(); + + InSequence seq; + + EXPECT_CALL( + _mock_client, queue_prepare_write( + _connection_handle, + _attribute_handle, + make_const_ArrayView( + value.data(), + _mtu_size - 5 + ), + 0 + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto&, auto offset) { + _mock_client.on_server_event( + conn_handle, + AttErrorResponse( + AttributeOpcode::PREPARE_WRITE_REQUEST, + att_handle, + error_code + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _mock_client, execute_write_queue(_connection_handle, false) + ).WillOnce(Invoke([&](auto conn_handle, bool execute) { + _mock_client.on_server_event( + conn_handle, + AttExecuteWriteResponse() + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _write_cb, call(Pointee(GattWriteCallbackParams{ + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + status, + /* error code */ error_code, + })) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + _mtu_size * 10, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); +} + +// Instantiate test cases relying on TestGattClientPrepareWriteAttributeError +INSTANTIATE_TEST_CASE_P( + TestGattClientPrepareWriteAttributeError_combination, + TestGattClientPrepareWriteAttributeError, + ::testing::Values( + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHORIZATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_AUTHENTICATION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION_KEY_SIZE, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INSUFFICIENT_ENCRYPTION, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::PREPARE_QUEUE_FULL, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INVALID_HANDLE, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::WRITE_NOT_PERMITTED, BLE_ERROR_OPERATION_NOT_PERMITTED), + std::make_tuple((AttErrorResponse::AttributeErrorCode)/* application error */ 0x80, BLE_ERROR_UNSPECIFIED) + ) +); + + +/* + * Fixture used to test errors in the middle of a long write transaction. + * Parameter are similar to the one used in TestGattClientWriteAttributeError + */ +class TestGattClientPrepareWriteAttributeErrorInTransaction : public TestGattClientWriteAttributeError { }; + +/** + * Test that errors are correctly reported to the client if an error occur in the + * middle of a long write transaction. + */ +TEST_P(TestGattClientPrepareWriteAttributeErrorInTransaction, prepare_write_error_in_transaction) { + auto value = make_char_value(10000); + + _mtu_size = 23; + set_mtu_size_stub(); + + InSequence seq; + + EXPECT_CALL( + _mock_client, queue_prepare_write( + _connection_handle, + _attribute_handle, + make_const_ArrayView( + value.data(), + _mtu_size - 5 + ), + /* offset */ 0 + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto&, auto) { + _mock_client.on_server_event( + conn_handle, + AttPrepareWriteResponse( + att_handle, + 0, + make_const_ArrayView(value.data(), _mtu_size - 5) + ) + ); + return BLE_ERROR_NONE; + })); + + + uint16_t offset = _mtu_size - 5; + + EXPECT_CALL( + _mock_client, queue_prepare_write( + _connection_handle, + _attribute_handle, + make_const_ArrayView( + value.data() + offset, + _mtu_size - 5 + ), + offset + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto&, auto offset) { + _mock_client.on_server_event( + conn_handle, + AttErrorResponse( + AttributeOpcode::PREPARE_WRITE_REQUEST, + att_handle, + error_code + ) + ); + return BLE_ERROR_NONE; + })); + + + EXPECT_CALL( + _mock_client, execute_write_queue(_connection_handle, false) + ).WillOnce(Invoke([&](auto conn_handle, bool execute) { + _mock_client.on_server_event( + conn_handle, + AttExecuteWriteResponse() + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _write_cb, call(Pointee(GattWriteCallbackParams{ + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + status, + /* error code */ error_code, + })) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + _mtu_size * 10, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); +} + +// Instantiate test cases relying on TestGattClientPrepareWriteAttributeErrorInTransaction +INSTANTIATE_TEST_CASE_P( + TestGattClientPrepareWriteAttributeErrorInTransaction_combination, + TestGattClientPrepareWriteAttributeErrorInTransaction, + ::testing::Values( + std::make_tuple(AttErrorResponse::PREPARE_QUEUE_FULL, BLE_ERROR_INVALID_STATE), + std::make_tuple((AttErrorResponse::AttributeErrorCode)/* application error */ 0x80, BLE_ERROR_UNSPECIFIED) + ) +); + +/* + * Fixture used to test errors at the end of a long write transaction. + * Parameter are similar to the one used in TestGattClientWriteAttributeError + */ +class TestGattClientExecuteWriteRequestError : public TestGattClientWriteAttributeError { }; + +/** + * Test that errors are correctly reported to the client if an error occurs when + * a write queue is executed. + */ +TEST_P(TestGattClientExecuteWriteRequestError, prepare_write_error_in_transaction) { + auto value = make_char_value(10000); + + _mtu_size = 23; + set_mtu_size_stub(); + const uint16_t len = _mtu_size * 5; + + InSequence seq; + + for (uint16_t offset = 0; offset < len; offset += (_mtu_size - 5)) { + EXPECT_CALL( + _mock_client, queue_prepare_write( + _connection_handle, + _attribute_handle, + make_const_ArrayView( + value.data() + offset, + ((len - offset) > (_mtu_size - 5)) ? (_mtu_size - 5) : (len - offset) + ), + offset + ) + ).WillOnce(Invoke([&](auto conn_handle, auto att_handle, const auto& data, auto offset) { + _mock_client.on_server_event( + conn_handle, + AttPrepareWriteResponse( + att_handle, + offset, + data + ) + ); + return BLE_ERROR_NONE; + })); + } + + EXPECT_CALL( + _mock_client, execute_write_queue( + _connection_handle, true + ) + ).WillOnce(Invoke([&](connection_handle_t conn_handle, bool execute) { + _mock_client.on_server_event( + conn_handle, + AttErrorResponse( + AttributeOpcode::EXECUTE_WRITE_REQUEST, + _attribute_handle, + error_code + ) + ); + return BLE_ERROR_NONE; + })); + + EXPECT_CALL( + _write_cb, call(Pointee(GattWriteCallbackParams{ + _connection_handle, + _attribute_handle, + GattWriteCallbackParams::OP_WRITE_REQ, + status, + error_code + })) + ); + + ble_error_t error = _gatt_client.write( + GattClient::GATT_OP_WRITE_REQ, + _connection_handle, + _attribute_handle, + len, + value.data() + ); + + EXPECT_EQ(error, BLE_ERROR_NONE); +} + +// Instantiate test cases relying on TestGattClientExecuteWriteRequestError +INSTANTIATE_TEST_CASE_P( + TestGattClientExecuteWriteRequestError_combination, + TestGattClientExecuteWriteRequestError, + ::testing::Values( + std::make_tuple(AttErrorResponse::INVALID_HANDLE, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::WRITE_NOT_PERMITTED, BLE_ERROR_OPERATION_NOT_PERMITTED), + std::make_tuple(AttErrorResponse::PREPARE_QUEUE_FULL, BLE_ERROR_INVALID_STATE), + std::make_tuple(AttErrorResponse::INVALID_OFFSET, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::INVALID_ATTRIBUTE_VALUE_LENGTH, BLE_ERROR_INVALID_PARAM), + std::make_tuple(AttErrorResponse::WRITE_REQUEST_REJECTED, BLE_ERROR_OPERATION_NOT_PERMITTED), + std::make_tuple((AttErrorResponse::AttributeErrorCode)/* application error */ 0x80, BLE_ERROR_UNSPECIFIED) + ) +); + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.cpp b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.cpp new file mode 100644 index 0000000000..d21edd7782 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.cpp @@ -0,0 +1,47 @@ +/* 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 "MockCallbacks.h" + +//////////////////////////////////////////////////////////////////////////////// +// Constructor implementation of the mocked callbacks +// +// WARNING: Do not remove; it speedup compile time. + +service_callback_t::service_callback_t() { } +service_callback_t::~service_callback_t() { } + +characteristic_callback_t::characteristic_callback_t() { } +characteristic_callback_t::~characteristic_callback_t() { } + +termination_callback_t::termination_callback_t() { } +termination_callback_t::~termination_callback_t() { } + +read_callback_t::read_callback_t() { } +read_callback_t::~read_callback_t() { } + +write_callback_t::write_callback_t() { } +write_callback_t::~write_callback_t() { } + +server_event_callback_t::server_event_callback_t() { } +server_event_callback_t::~server_event_callback_t() { } + +descriptor_callback_t::descriptor_callback_t() { } +descriptor_callback_t::~descriptor_callback_t() { } + +descriptor_termination_callback_t::descriptor_termination_callback_t() { } +descriptor_termination_callback_t::~descriptor_termination_callback_t() { } + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.h b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.h new file mode 100644 index 0000000000..0affee57d1 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockCallbacks.h @@ -0,0 +1,109 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_MOCK_MOCKCALLBACKS_H_ +#define TESTS_GENERIC_GATTCLIENT_MOCK_MOCKCALLBACKS_H_ + +#include "gmock/gmock.h" +#include "ble/Gap.h" +#include "ble/DiscoveredService.h" +#include "ble/DiscoveredCharacteristic.h" +#include "ble/GattCallbackParamTypes.h" +#include "ble/CharacteristicDescriptorDiscovery.h" + +//////////////////////////////////////////////////////////////////////////////// +// Mocks declaration for callbacks objects used in GattClient +// + +/* + * Mock of the service discovery callback. + */ +struct service_callback_t { + service_callback_t(); + virtual ~service_callback_t(); + MOCK_METHOD1(call, void(const DiscoveredService*)); +}; + +/* + * Mock of the characteristic discovery callback. + */ +struct characteristic_callback_t { + characteristic_callback_t(); + virtual ~characteristic_callback_t(); + MOCK_METHOD1(call, void(const DiscoveredCharacteristic*)); +}; + +/* + * Mock of the discovery termination callback. + */ +struct termination_callback_t { + termination_callback_t(); + virtual ~termination_callback_t(); + MOCK_METHOD1(call, void(Gap::Handle_t)); +}; + +/* + * Mock of the read attribute callback. + */ +struct read_callback_t { + read_callback_t(); + virtual ~read_callback_t(); + MOCK_METHOD1(call, void(const GattReadCallbackParams*)); +}; + +/* + * Mock of the write attribute callback. + */ +struct write_callback_t { + write_callback_t(); + virtual ~write_callback_t(); + MOCK_METHOD1(call, void(const GattWriteCallbackParams*)); +}; + +/* + * Mock of the server initiated events callback (notification and indication). + */ +struct server_event_callback_t { + server_event_callback_t(); + virtual ~server_event_callback_t(); + MOCK_METHOD1(call, void(const GattHVXCallbackParams*)); +}; + +/* + * Mock of the characteristic descriptor discovery callback. + */ +struct descriptor_callback_t { + descriptor_callback_t(); + virtual ~descriptor_callback_t(); + MOCK_METHOD1( + call, + void(const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t*) + ); +}; + +/* + * Mock of the characteristic descriptor termination callback. + */ +struct descriptor_termination_callback_t { + descriptor_termination_callback_t(); + virtual ~descriptor_termination_callback_t(); + MOCK_METHOD1( + call, + void(const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t*) + ); +}; + +#endif /* TESTS_GENERIC_GATTCLIENT_MOCK_MOCKCALLBACKS_H_ */ diff --git a/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.cpp b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.cpp new file mode 100644 index 0000000000..7c0b77b7ea --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.cpp @@ -0,0 +1,35 @@ +/* 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 "MockPalGattClient.h" + +namespace ble { +namespace pal { +namespace vendor { +namespace mock { + +//////////////////////////////////////////////////////////////////////////////// +// Constructor implementation of the mocked pal gatt client +// +// WARNING: Do not remove; it speedup compile time. + +MockPalGattClient::MockPalGattClient() { } +MockPalGattClient::~MockPalGattClient() { } + +} // namespace ble +} // namespace pal +} // namespace vendor +} // namespace mock diff --git a/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.h b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.h new file mode 100644 index 0000000000..19db079e7f --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/mock/MockPalGattClient.h @@ -0,0 +1,141 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_MOCK_MOCKPALGATTCLIENT_H_ +#define TESTS_GENERIC_GATTCLIENT_MOCK_MOCKPALGATTCLIENT_H_ + +#include "gmock/gmock.h" +#include "ble/pal/PalGattClient.h" + +namespace ble { +namespace pal { +namespace vendor { +namespace mock { + +/* + * Mock of ble::pal::GattClient + */ +class MockPalGattClient : public ble::pal::GattClient { +public: + MockPalGattClient(); + + virtual ~MockPalGattClient(); + + MOCK_METHOD0(initialize, ble_error_t()); + + MOCK_METHOD0(terminate, ble_error_t()); + + MOCK_METHOD1(exchange_mtu, ble_error_t(connection_handle_t)); + + MOCK_METHOD2(get_mtu_size, ble_error_t(connection_handle_t, uint16_t&)); + + MOCK_METHOD2( + discover_primary_service, + ble_error_t(connection_handle_t, attribute_handle_t) + ); + + MOCK_METHOD3( + discover_primary_service_by_service_uuid, + ble_error_t(connection_handle_t, attribute_handle_t, const UUID&) + ); + + MOCK_METHOD2( + find_included_service, + ble_error_t(connection_handle_t, attribute_handle_range_t) + ); + + MOCK_METHOD2( + discover_characteristics_of_a_service, + ble_error_t(connection_handle_t, attribute_handle_range_t) + ); + + MOCK_METHOD2( + discover_characteristics_descriptors, + ble_error_t(connection_handle_t, attribute_handle_range_t) + ); + + MOCK_METHOD2( + read_attribute_value, + ble_error_t(connection_handle_t, attribute_handle_t) + ); + + MOCK_METHOD3( + read_using_characteristic_uuid, + ble_error_t(connection_handle_t, attribute_handle_range_t, const UUID&) + ); + + MOCK_METHOD3( + read_attribute_blob, + ble_error_t(connection_handle_t, attribute_handle_t, uint16_t) + ); + + MOCK_METHOD2( + read_multiple_characteristic_values, + ble_error_t( + connection_handle_t, const ArrayView& + ) + ); + + MOCK_METHOD3( + write_without_response, + ble_error_t( + connection_handle_t, + attribute_handle_t, + const ArrayView& + ) + ); + + MOCK_METHOD3( + signed_write_without_response, + ble_error_t( + connection_handle_t, + attribute_handle_t, + const ArrayView& + ) + ); + + MOCK_METHOD3( + write_attribute, + ble_error_t( + connection_handle_t, + attribute_handle_t, + const ArrayView& + ) + ); + + MOCK_METHOD4( + queue_prepare_write, + ble_error_t( + connection_handle_t, + attribute_handle_t, + const ArrayView&, + uint16_t + ) + ); + + MOCK_METHOD2(execute_write_queue, ble_error_t(connection_handle_t, bool)); + + // let event injection functions be public so it can be used inside the tests. + using pal::GattClient::on_server_event; + using pal::GattClient::on_transaction_timeout; +}; + +} // namespace ble +} // namespace pal +} // namespace vendor +} // namespace mock + +#endif /* TESTS_GENERIC_GATTCLIENT_MOCK_MOCKPALGATTCLIENT_H_ */ diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/CharacteristicDescription.h b/features/FEATURE_BLE/tests/generic/GattClient/util/CharacteristicDescription.h new file mode 100644 index 0000000000..8381cc36fc --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/CharacteristicDescription.h @@ -0,0 +1,59 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_UTIL_CHARACTERISTICDESCRIPTION_H_ +#define TESTS_GENERIC_GATTCLIENT_UTIL_CHARACTERISTICDESCRIPTION_H_ + +#include +#include "ble/UUID.h" +#include "ble/DiscoveredCharacteristic.h" + +struct characteristic_description_t { + characteristic_description_t() : + handle(0), properties(), value_handle(0), uuid() { } + + characteristic_description_t( + uint16_t handle, + DiscoveredCharacteristic::Properties_t properties, + UUID uuid + ) : handle(handle), + properties(properties), + value_handle(handle + 1), + uuid(uuid) { + } + + friend bool operator==( + const characteristic_description_t& lhs, const characteristic_description_t& rhs + ) { + return (lhs.handle == rhs.handle) + && (lhs.properties == rhs.properties) + && (lhs.value_handle == rhs.value_handle) + && (lhs.uuid == rhs.uuid); + } + + friend bool operator !=( + const characteristic_description_t& lhs, const characteristic_description_t& rhs + ) { + return !(lhs == rhs); + } + + uint16_t handle; + DiscoveredCharacteristic::Properties_t properties; + uint16_t value_handle; + UUID uuid; +}; + +#endif /* TESTS_GENERIC_GATTCLIENT_UTIL_CHARACTERISTICDESCRIPTION_H_ */ diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.cpp b/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.cpp new file mode 100644 index 0000000000..d078d277b9 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.cpp @@ -0,0 +1,127 @@ +/* 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 "ble/DiscoveredCharacteristic.h" +#include "ble/DiscoveredCharacteristicDescriptor.h" +#include "ble/CharacteristicDescriptorDiscovery.h" +#include "ble/GattCallbackParamTypes.h" +#include "ble/ArrayView.h" + +bool operator==( + const DiscoveredCharacteristicDescriptor& lhs, + const DiscoveredCharacteristicDescriptor& rhs +) { + if (lhs.getGattClient() != rhs.getGattClient() || + lhs.getConnectionHandle() != rhs.getConnectionHandle() || + lhs.getUUID() != rhs.getUUID() || + lhs.getAttributeHandle() != rhs.getAttributeHandle() + ) { + return false; + } + + return true; +} + +bool operator!=( + const DiscoveredCharacteristicDescriptor& lhs, + const DiscoveredCharacteristicDescriptor& rhs +) { + return !(lhs == rhs); +} + +bool operator==( + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& rhs +) { + if (lhs.characteristic != rhs.characteristic || + lhs.status != rhs.status || + lhs.error_code != rhs.error_code + ) { + return false; + } + + return true; +} + +bool operator!=( + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& rhs +) { + return !(lhs == rhs); +} + +bool operator==( + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& rhs +) { + if (lhs.characteristic != rhs.characteristic || + lhs.descriptor != rhs.descriptor + ) { + return false; + } + + return true; +} + +bool operator!=( + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& rhs +) { + return !(lhs == rhs); +} + +bool operator==(const GattReadCallbackParams& lhs, const GattReadCallbackParams& rhs) { + if ((lhs.connHandle != rhs.connHandle) || (lhs.handle != rhs.handle) || + (lhs.offset != rhs.offset) || (lhs.status != rhs.status)) { + return false; + } + + if (lhs.status == BLE_ERROR_NONE) { + if (lhs.len != rhs.len) { + return false; + } + + if (memcmp(lhs.data, rhs.data, lhs.len) != 0) { + return false; + } + } else if (lhs.error_code != rhs.error_code) { + return false; + } + + return true; +} + +bool operator==(const GattHVXCallbackParams& lhs, const GattHVXCallbackParams& rhs) { + if (lhs.connHandle != rhs.connHandle && + lhs.handle != rhs.handle && + lhs.type != rhs.type && + ble::make_const_ArrayView(lhs.data, lhs.len) != ble::make_const_ArrayView(rhs.data, rhs.len)) { + return false; + } + + return true; +} + +bool operator==(const GattWriteCallbackParams& lhs, const GattWriteCallbackParams& rhs) { + if (lhs.connHandle != rhs.connHandle && + lhs.handle != rhs.handle && + lhs.writeOp != rhs.writeOp && + lhs.error_code != rhs.error_code && + lhs.status != rhs.status) { + return false; + } + return true; +} diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.h b/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.h new file mode 100644 index 0000000000..21c30d1dc8 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/Equality.h @@ -0,0 +1,60 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_UTIL_EQUALITY_H_ +#define TESTS_GENERIC_GATTCLIENT_UTIL_EQUALITY_H_ + +#include "ble/DiscoveredCharacteristicDescriptor.h" +#include "ble/CharacteristicDescriptorDiscovery.h" +#include "ble/GattCallbackParamTypes.h" + +bool operator==( + const DiscoveredCharacteristicDescriptor& lhs, + const DiscoveredCharacteristicDescriptor& rhs +); + +bool operator!=( + const DiscoveredCharacteristicDescriptor& lhs, + const DiscoveredCharacteristicDescriptor& rhs +); + +bool operator==( + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& rhs +); + +bool operator!=( + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::TerminationCallbackParams_t& rhs +); + +bool operator==( + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& rhs +); + +bool operator!=( + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& lhs, + const CharacteristicDescriptorDiscovery::DiscoveryCallbackParams_t& rhs +); + +bool operator==(const GattReadCallbackParams& lhs, const GattReadCallbackParams& rhs); + +bool operator==(const GattHVXCallbackParams& lhs, const GattHVXCallbackParams& rhs); + +bool operator==(const GattWriteCallbackParams& lhs, const GattWriteCallbackParams& rhs); + +#endif /* TESTS_GENERIC_GATTCLIENT_UTIL_EQUALITY_H_ */ diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/Log.h b/features/FEATURE_BLE/tests/generic/GattClient/util/Log.h new file mode 100644 index 0000000000..594a5cdde8 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/Log.h @@ -0,0 +1,35 @@ + +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_UTIL_LOG_H_ +#define TESTS_GENERIC_GATTCLIENT_UTIL_LOG_H_ + +#include + +// logger +class Log { +public: + static std::ostream& info() { + std::cout << "[info ] "; + return std::cout; + } + +}; + +#endif /* TESTS_GENERIC_GATTCLIENT_UTIL_LOG_H_ */ + + diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/PrettyPrinter.h b/features/FEATURE_BLE/tests/generic/GattClient/util/PrettyPrinter.h new file mode 100644 index 0000000000..12d59f67e9 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/PrettyPrinter.h @@ -0,0 +1,124 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_UTIL_PRETTYPRINTER_H_ +#define TESTS_GENERIC_GATTCLIENT_UTIL_PRETTYPRINTER_H_ + +#include +#include +#include +#include + +/* + * UUID pretty printer + */ +inline ::std::ostream& operator<<(::std::ostream& os, const UUID& uuid) { + char str[60]; + if (uuid.shortOrLong() == UUID::UUID_TYPE_SHORT) { + sprintf(str, "0x%04X", uuid.getShortUUID()); + } else { + const uint8_t* data = uuid.getBaseUUID(); + sprintf( + str, + "\"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X\"", + data[15], data[14], data[13], data[12], data[11], data[10], data[9], data[8], + data[7], data[6], data[5], data[4], data[3], data[2], data[1], data[0] + ); + } + return os << str; +} + +/* + * GattReadCallbackParams pretty printer + */ +inline ::std::ostream& operator<<(::std::ostream& os, const GattReadCallbackParams& p) { + os << "connection_handle: " << p.connHandle << + ", handle: " << p.handle << + ", offset: " << p.offset << + ", status: " << p.status; + + if (p.status) { + os << ", error_code: " << p.error_code; + } else { + os << ", len: " << p.len; + os << ", data: "; + for (size_t i = 0; i < p.len; ++i) { + os << (int) p.data[i] << " "; + } + } + + return os; +} + +/* + * attribute_handle_range_t pretty printer + */ +inline ::std::ostream& operator<<(::std::ostream& os, const ble::attribute_handle_range_t& handle_range) { + return os << "(" << handle_range.begin << "," << handle_range.end << ")"; +} + +/* + * Pretty printer of an view to an array of uint8_t. + */ +inline ::std::ostream& operator<<(::std::ostream& os, const ble::ArrayView& array) { + os << "size: " << array.size() << " value: { "; + for (size_t i = 0; i +#include +#include "ServiceDescription.h" + +struct server_description_t { + server_description_t() { } + server_description_t(std::initializer_list services) : + services(services) { } + + std::vector services; +}; + +#endif /* TESTS_GENERIC_GATTCLIENT_UTIL_SERVERDESCRIPTION_H_ */ diff --git a/features/FEATURE_BLE/tests/generic/GattClient/util/ServiceDescription.h b/features/FEATURE_BLE/tests/generic/GattClient/util/ServiceDescription.h new file mode 100644 index 0000000000..3540534c46 --- /dev/null +++ b/features/FEATURE_BLE/tests/generic/GattClient/util/ServiceDescription.h @@ -0,0 +1,44 @@ +/* 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. + */ + +#ifndef TESTS_GENERIC_GATTCLIENT_UTIL_SERVICEDESCRIPTION_H_ +#define TESTS_GENERIC_GATTCLIENT_UTIL_SERVICEDESCRIPTION_H_ + +#include +#include +#include +#include "ble/UUID.h" +#include "CharacteristicDescription.h" + +struct service_description_t { + service_description_t( + uint16_t start, + uint16_t end, + UUID uuid, + std::initializer_list characteristics + ) : start(start), + end(end), + uuid(uuid), + characteristics(characteristics) { + } + + uint16_t start; + uint16_t end; + UUID uuid; + std::vector characteristics; +}; + +#endif /* TESTS_GENERIC_GATTCLIENT_UTIL_SERVICEDESCRIPTION_H_ */ diff --git a/features/FEATURE_BLE/tests/mbed_os_stub/mbed_assert.c b/features/FEATURE_BLE/tests/mbed_os_stub/mbed_assert.c new file mode 100644 index 0000000000..561b0c1828 --- /dev/null +++ b/features/FEATURE_BLE/tests/mbed_os_stub/mbed_assert.c @@ -0,0 +1,24 @@ +/* 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 + +void mbed_assert_internal(const char *expr, const char *file, int line) +{ + printf("mbed assertation failed: %s, file: %s, line %d \n", expr, file, line); + abort(); +}