| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "client.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel_test.h" |
| |
| namespace bt { |
| namespace gatt { |
| namespace { |
| |
| constexpr UUID kTestUuid1((uint16_t)0xDEAD); |
| constexpr UUID kTestUuid2((uint16_t)0xBEEF); |
| constexpr UUID kTestUuid3({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 15}); |
| |
| // clang-format off |
| const auto kDiscoverAllPrimaryRequest = CreateStaticByteBuffer( |
| 0x10, // opcode: read by group type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x00, 0x28 // type: primary service (0x2800) |
| ); |
| // clang-format on |
| |
| void NopSvcCallback(const gatt::ServiceData&) {} |
| void NopChrcCallback(const gatt::CharacteristicData&) {} |
| void NopDescCallback(const gatt::DescriptorData&) {} |
| |
| class GATT_ClientTest : public l2cap::testing::FakeChannelTest { |
| public: |
| GATT_ClientTest() = default; |
| ~GATT_ClientTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| ChannelOptions options(l2cap::kATTChannelId); |
| fake_chan_ = CreateFakeChannel(options); |
| att_ = att::Bearer::Create(fake_chan_); |
| client_ = Client::Create(att_); |
| } |
| |
| void TearDown() override { |
| client_ = nullptr; |
| att_ = nullptr; |
| } |
| |
| // |out_status| must remain valid. |
| void SendDiscoverDescriptors(att::Status* out_status, |
| Client::DescriptorCallback desc_callback, |
| att::Handle range_start = 0x0001, |
| att::Handle range_end = 0xFFFF) { |
| async::PostTask(dispatcher(), |
| [=, desc_callback = std::move(desc_callback)]() mutable { |
| client()->DiscoverDescriptors( |
| range_start, range_end, std::move(desc_callback), |
| [out_status](att::Status val) { *out_status = val; }); |
| }); |
| } |
| |
| // Blocks until the fake channel receives a Find Information request with the |
| // given handles |
| bool ExpectFindInformation(att::Handle range_start = 0x0001, |
| att::Handle range_end = 0xFFFF) { |
| return Expect(CreateStaticByteBuffer( |
| 0x04, // opcode |
| LowerBits(range_start), UpperBits(range_start), // start handle |
| LowerBits(range_end), UpperBits(range_end) // end hanle |
| )); |
| } |
| |
| att::Bearer* att() const { return att_.get(); } |
| Client* client() const { return client_.get(); } |
| l2cap::testing::FakeChannel* fake_chan() const { return fake_chan_.get(); } |
| |
| private: |
| fbl::RefPtr<l2cap::testing::FakeChannel> fake_chan_; |
| fxl::RefPtr<att::Bearer> att_; |
| std::unique_ptr<Client> client_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(GATT_ClientTest); |
| }; |
| |
| TEST_F(GATT_ClientTest, ExchangeMTUMalformedResponse) { |
| constexpr uint16_t kPreferredMTU = 100; |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| // Initialize to a non-zero value. |
| uint16_t final_mtu = kPreferredMTU; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| att()->set_preferred_mtu(kPreferredMTU); |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| ASSERT_FALSE(fake_chan()->link_error()); |
| |
| // Respond back with a malformed PDU. This should cause a link error and the |
| // MTU request should fail. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| 30 // server rx mtu is one octet too short |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| EXPECT_EQ(0, final_mtu); |
| EXPECT_TRUE(fake_chan()->link_error()); |
| } |
| |
| // Tests that the ATT "Request Not Supported" error results in the default MTU. |
| TEST_F(GATT_ClientTest, ExchangeMTUErrorNotSupported) { |
| constexpr uint16_t kPreferredMTU = 100; |
| constexpr uint16_t kInitialMTU = 50; |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| uint16_t final_mtu = 0; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| // Set the initial MTU to something other than the default LE MTU since we |
| // want to confirm that the MTU changes to the default. |
| att()->set_mtu(kInitialMTU); |
| att()->set_preferred_mtu(kPreferredMTU); |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // Respond with "Request Not Supported". This will cause us to switch to the |
| // default MTU. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x02, // request: exchange MTU |
| 0x00, 0x00, // handle: 0 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(status); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| EXPECT_EQ(att::kLEMinMTU, final_mtu); |
| EXPECT_EQ(att::kLEMinMTU, att()->mtu()); |
| } |
| |
| TEST_F(GATT_ClientTest, ExchangeMTUErrorOther) { |
| constexpr uint16_t kPreferredMTU = 100; |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| uint16_t final_mtu = kPreferredMTU; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| att()->set_preferred_mtu(kPreferredMTU); |
| EXPECT_EQ(att::kLEMinMTU, att()->mtu()); |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // Respond with an error. The MTU should remain unchanged. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x01, // opcode: error response |
| 0x02, // request: exchange MTU |
| 0x00, 0x00, // handle: 0 |
| 0x0E // error: Unlikely Error |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(att::ErrorCode::kUnlikelyError, status.protocol_error()); |
| EXPECT_EQ(0, final_mtu); |
| EXPECT_EQ(att::kLEMinMTU, att()->mtu()); |
| } |
| |
| // Tests that the client rx MTU is selected when smaller. |
| TEST_F(GATT_ClientTest, ExchangeMTUSelectLocal) { |
| constexpr uint16_t kPreferredMTU = 100; |
| constexpr uint16_t kServerRxMTU = kPreferredMTU + 1; |
| |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| uint16_t final_mtu = 0; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| att()->set_preferred_mtu(kPreferredMTU); |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| ASSERT_EQ(att::kLEMinMTU, att()->mtu()); |
| |
| // Respond with an error. The MTU should remain unchanged. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| kServerRxMTU, 0x00 // server rx mtu |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kPreferredMTU, final_mtu); |
| EXPECT_EQ(kPreferredMTU, att()->mtu()); |
| } |
| |
| // Tests that the server rx MTU is selected when smaller. |
| TEST_F(GATT_ClientTest, ExchangeMTUSelectRemote) { |
| constexpr uint16_t kPreferredMTU = 100; |
| constexpr uint16_t kServerRxMTU = kPreferredMTU - 1; |
| |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| uint16_t final_mtu = 0; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| att()->set_preferred_mtu(kPreferredMTU); |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| ASSERT_EQ(att::kLEMinMTU, att()->mtu()); |
| |
| // Respond with an error. The MTU should remain unchanged. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| kServerRxMTU, 0x00 // server rx mtu |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kServerRxMTU, final_mtu); |
| EXPECT_EQ(kServerRxMTU, att()->mtu()); |
| } |
| |
| // Tests that the default MTU is selected when one of the MTUs is too small. |
| TEST_F(GATT_ClientTest, ExchangeMTUSelectDefault) { |
| constexpr uint16_t kPreferredMTU = 100; |
| constexpr uint16_t kServerRxMTU = 5; // Smaller than the LE default MTU |
| |
| const auto kExpectedRequest = CreateStaticByteBuffer( |
| 0x02, // opcode: exchange MTU |
| kPreferredMTU, 0x00 // client rx mtu: kPreferredMTU |
| ); |
| |
| uint16_t final_mtu = 0; |
| att::Status status; |
| auto mtu_cb = [&](att::Status cb_status, uint16_t val) { |
| final_mtu = val; |
| status = cb_status; |
| }; |
| |
| att()->set_preferred_mtu(kPreferredMTU); |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), |
| [this, mtu_cb] { client()->ExchangeMTU(mtu_cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| ASSERT_EQ(att::kLEMinMTU, att()->mtu()); |
| |
| // Respond with an error. The MTU should remain unchanged. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x03, // opcode: exchange MTU response |
| kServerRxMTU, 0x00 // server rx mtu |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(att::kLEMinMTU, final_mtu); |
| EXPECT_EQ(att::kLEMinMTU, att()->mtu()); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryResponseTooShort) { |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| // Respond back with a malformed payload. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x11)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryMalformedDataLength) { |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| // Respond back with an unexpected data length. This is 6 for services with a |
| // 16-bit UUID (start (2) + end (2) + uuid (2)) and 20 for 128-bit |
| // (start (2) + end (2) + uuid (16)). |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 7, // data length: 7 (not 6 or 20) |
| 0, 1, 2, 3, 4, 5, 6 // one entry of length 7, which will be ignored |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryMalformedAttrDataList) { |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 6, // data length: 6 (16-bit UUIDs) |
| 0, 1, 2, 3, 4, 5, // entry 1: correct size |
| 0, 1, 2, 3, 4 // entry 2: incorrect size |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| // Tests that we handle an empty attribute data list properly. In practice, the |
| // server would send an "Attribute Not Found" error instead but our stack treats |
| // an empty data list as not an error. |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryEmptyDataList) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 6 // data length: 6 (16-bit UUIDs) |
| // data list is empty |
| )); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| } |
| |
| // The first request results in "Attribute Not Found". |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryAttributeNotFound) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x01, 0x00, // handle: 0x0001 |
| 0x0A // error: Attribute Not Found |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| // The procedure succeeds with no services. |
| EXPECT_TRUE(status); |
| } |
| |
| // The first request results in an error. |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryError) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x01, 0x00, // handle: 0x0001 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryMalformedServiceRange) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [this, res_cb] { |
| client()->DiscoverPrimaryServices(NopSvcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| // Return a service where start > end. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x06, // data length: 6 (16-bit UUIDs) |
| 0x02, 0x00, // svc 1 start: 0x0002 |
| 0x01, 0x00 // svc 1 end: 0x0001 |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| // The procedure should be over since the last service in the payload has |
| // end handle 0xFFFF. |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimary16BitResultsSingleRequest) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<ServiceData> services; |
| auto svc_cb = [&services](const ServiceData& svc) { |
| services.push_back(svc); |
| }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [this, svc_cb, res_cb] { |
| client()->DiscoverPrimaryServices(svc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x06, // data length: 6 (16-bit UUIDs) |
| 0x01, 0x00, // svc 1 start: 0x0001 |
| 0x05, 0x00, // svc 1 end: 0x0005 |
| 0xAD, 0xDE, // svc 1 uuid: 0xDEAD |
| 0x06, 0x00, // svc 2 start: 0x0006 |
| 0xFF, 0xFF, // svc 2 end: 0xFFFF |
| 0xEF, 0xBE // svc 2 uuid: 0xBEEF |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| // The procedure should be over since the last service in the payload has |
| // end handle 0xFFFF. |
| EXPECT_TRUE(status); |
| EXPECT_EQ(2u, services.size()); |
| EXPECT_EQ(0x0001, services[0].range_start); |
| EXPECT_EQ(0x0005, services[0].range_end); |
| EXPECT_EQ(kTestUuid1, services[0].type); |
| EXPECT_EQ(0x0006, services[1].range_start); |
| EXPECT_EQ(0xFFFF, services[1].range_end); |
| EXPECT_EQ(kTestUuid2, services[1].type); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimary128BitResultSingleRequest) { |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<ServiceData> services; |
| auto svc_cb = [&services](const ServiceData& svc) { |
| services.push_back(svc); |
| }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [this, svc_cb, res_cb] { |
| client()->DiscoverPrimaryServices(svc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kDiscoverAllPrimaryRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x14, // data length: 20 (128-bit UUIDs) |
| 0x01, 0x00, // svc 1 start: 0x0008 |
| 0xFF, 0xFF, // svc 1 end: 0xFFFF |
| |
| // UUID matches |kTestUuid3| declared above. |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); |
| |
| RunLoopUntilIdle(); |
| |
| // The procedure should be over since the last service in the payload has |
| // end handle 0xFFFF. |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, services.size()); |
| EXPECT_EQ(0x0001, services[0].range_start); |
| EXPECT_EQ(0xFFFF, services[0].range_end); |
| EXPECT_EQ(kTestUuid3, services[0].type); |
| } |
| |
| TEST_F(GATT_ClientTest, DiscoverAllPrimaryMultipleRequests) { |
| const auto kExpectedRequest1 = |
| CreateStaticByteBuffer(0x10, // opcode: read by group type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x00, 0x28 // type: primary service (0x2800) |
| ); |
| const auto kExpectedRequest2 = |
| CreateStaticByteBuffer(0x10, // opcode: read by group type request |
| 0x08, 0x00, // start handle: 0x0008 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x00, 0x28 // type: primary service (0x2800) |
| ); |
| const auto kExpectedRequest3 = |
| CreateStaticByteBuffer(0x10, // opcode: read by group type request |
| 0x0A, 0x00, // start handle: 0x000A |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x00, 0x28 // type: primary service (0x2800) |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<ServiceData> services; |
| auto svc_cb = [&services](const ServiceData& svc) { |
| services.push_back(svc); |
| }; |
| |
| // Initiate the request on the loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [this, svc_cb, res_cb] { |
| client()->DiscoverPrimaryServices(svc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest1)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x06, // data length: 6 (16-bit UUIDs) |
| 0x01, 0x00, // svc 1 start: 0x0001 |
| 0x05, 0x00, // svc 1 end: 0x0005 |
| 0xAD, 0xDE, // svc 1 uuid: 0xDEAD |
| 0x06, 0x00, // svc 2 start: 0x0006 |
| 0x07, 0x00, // svc 2 end: 0x0007 |
| 0xEF, 0xBE // svc 2 uuid: 0xBEEF |
| )); |
| |
| // The client should follow up with a second request following the last end |
| // handle. |
| ASSERT_TRUE(Expect(kExpectedRequest2)); |
| |
| // Respond with one 128-bit service UUID. |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x11, // opcode: read by group type response |
| 0x14, // data length: 20 (128-bit UUIDs) |
| 0x08, 0x00, // svc 1 start: 0x0008 |
| 0x09, 0x00, // svc 1 end: 0x0009 |
| |
| // UUID matches |kTestUuid3| declared above. |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); |
| |
| // The client should follow up with a third request following the last end |
| // handle. |
| ASSERT_TRUE(Expect(kExpectedRequest3)); |
| |
| // Terminate the procedure with an error response. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x10, // request: read by group type |
| 0x0A, 0x00, // handle: 0x000A |
| 0x0A // error: Attribute Not Found |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| // The procedure should be over since the last service in the payload has |
| // end handle 0xFFFF. |
| EXPECT_TRUE(status); |
| EXPECT_EQ(3u, services.size()); |
| |
| EXPECT_EQ(0x0001, services[0].range_start); |
| EXPECT_EQ(0x0005, services[0].range_end); |
| EXPECT_EQ(kTestUuid1, services[0].type); |
| |
| EXPECT_EQ(0x0006, services[1].range_start); |
| EXPECT_EQ(0x0007, services[1].range_end); |
| EXPECT_EQ(kTestUuid2, services[1].type); |
| |
| EXPECT_EQ(0x0008, services[2].range_start); |
| EXPECT_EQ(0x0009, services[2].range_end); |
| EXPECT_EQ(kTestUuid3, services[2].type); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryHandlesEqual) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0001; |
| |
| att::Status status(HostError::kFailed); // Initialize as error |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Should succeed immediately. |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryResponseTooShort) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // Respond back with a malformed payload. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x09)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryMalformedDataLength) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // Respond back with an unexpected data length. This is 7 for characteristics |
| // with a 16-bit UUID (handle (2) + props (1) + value handle (2) + uuid (2)) |
| // and 21 for 128-bit (handle (2) + props (1) + value handle (2) + uuid (16)). |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 8, // data length: 8 (not 7 or 21) |
| 0, 1, 2, 3, 4, 5, 6, 7 // one entry of length 8, which will be ignored |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryMalformedAttrDataList) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // Respond back with an unexpected data length. This is 7 for characteristics |
| // with a 16-bit UUID (handle (2) + props (1) + value handle (2) + uuid (2)) |
| // and 21 for 128-bit (handle (2) + props (1) + value handle (2) + uuid (16)). |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 7, // data length: 7 (16-bit UUIDs) |
| 0, 1, 2, 3, 4, 5, 6, // entry 1: correct size |
| 0, 1, 2, 3, 4, 5 // entry 2: incorrect size |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryEmptyDataList) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 7 // data length: 7 (16-bit UUIDs) |
| // data list empty |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryAttributeNotFound) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x01, 0x00, // handle: 0x0001 |
| 0x0A // error: Attribute Not Found |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| // Attribute Not Found error means the procedure is over. |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryError) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, NopChrcCallback, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x01, 0x00, // handle: 0x0001 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscovery16BitResultsSingleRequest) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x03, 0x00, // chrc 1 handle |
| 0x00, // chrc 1 properties |
| 0x04, 0x00, // chrc 1 value handle |
| 0xAD, 0xDE, // chrc 1 uuid: 0xDEAD |
| 0x05, 0x00, // chrc 2 handle (0x0005 is the end of the requested range) |
| 0x01, // chrc 2 properties |
| 0x06, 0x00, // chrc 2 value handle |
| 0xEF, 0xBE // chrc 2 uuid: 0xBEEF |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_EQ(2u, chrcs.size()); |
| EXPECT_EQ(0x0003, chrcs[0].handle); |
| EXPECT_EQ(0, chrcs[0].properties); |
| EXPECT_EQ(0x0004, chrcs[0].value_handle); |
| EXPECT_EQ(kTestUuid1, chrcs[0].type); |
| EXPECT_EQ(0x0005, chrcs[1].handle); |
| EXPECT_EQ(1, chrcs[1].properties); |
| EXPECT_EQ(0x0006, chrcs[1].value_handle); |
| EXPECT_EQ(kTestUuid2, chrcs[1].type); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscovery128BitResultsSingleRequest) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x15, // data length: 21 (128-bit UUIDs) |
| 0x05, 0x00, // chrc handle |
| 0x00, // chrc properties |
| 0x06, 0x00, // chrc value handle |
| |
| // UUID matches |kTestUuid3| declared above. |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, chrcs.size()); |
| EXPECT_EQ(0x0005, chrcs[0].handle); |
| EXPECT_EQ(0, chrcs[0].properties); |
| EXPECT_EQ(0x0006, chrcs[0].value_handle); |
| EXPECT_EQ(kTestUuid3, chrcs[0].type); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryMultipleRequests) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0xFFFF; |
| |
| const auto kExpectedRequest1 = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x01, 0x00, // start handle: 0x0001 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| const auto kExpectedRequest2 = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x06, 0x00, // start handle: 0x0006 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| const auto kExpectedRequest3 = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x08, 0x00, // start handle: 0x0008 |
| 0xFF, 0xFF, // end handle: 0xFFFF |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest1)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x03, 0x00, // chrc 1 handle |
| 0x00, // chrc 1 properties |
| 0x04, 0x00, // chrc 1 value handle |
| 0xAD, 0xDE, // chrc 1 uuid: 0xDEAD |
| 0x05, 0x00, // chrc 2 handle |
| 0x01, // chrc 2 properties |
| 0x06, 0x00, // chrc 2 value handle |
| 0xEF, 0xBE // chrc 2 uuid: 0xBEEF |
| )); |
| |
| // The client should follow up with a second request following the last |
| // characteristic declaration handle. |
| ASSERT_TRUE(Expect(kExpectedRequest2)); |
| |
| // Respond with one characteristic with a 128-bit UUID |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x15, // data length: 21 (128-bit UUIDs) |
| 0x07, 0x00, // chrc handle |
| 0x00, // chrc properties |
| 0x08, 0x00, // chrc value handle |
| |
| // UUID matches |kTestUuid3| declared above. |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); |
| |
| // The client should follow up with a third request following the last |
| // characteristic declaration handle. |
| ASSERT_TRUE(Expect(kExpectedRequest3)); |
| |
| // Terminate the procedure with an error response. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x0A, 0x00, // handle: 0x000A |
| 0x0A // error: Attribute Not Found |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(3u, chrcs.size()); |
| |
| EXPECT_EQ(0x0003, chrcs[0].handle); |
| EXPECT_EQ(0, chrcs[0].properties); |
| EXPECT_EQ(0x0004, chrcs[0].value_handle); |
| EXPECT_EQ(kTestUuid1, chrcs[0].type); |
| |
| EXPECT_EQ(0x0005, chrcs[1].handle); |
| EXPECT_EQ(1, chrcs[1].properties); |
| EXPECT_EQ(0x0006, chrcs[1].value_handle); |
| EXPECT_EQ(kTestUuid2, chrcs[1].type); |
| |
| EXPECT_EQ(0x0007, chrcs[2].handle); |
| EXPECT_EQ(0, chrcs[2].properties); |
| EXPECT_EQ(0x0008, chrcs[2].value_handle); |
| EXPECT_EQ(kTestUuid3, chrcs[2].type); |
| } |
| |
| // Expects the discovery procedure to end with an error if a batch contains |
| // results that are from before requested range. |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryResultsBeforeRange) { |
| constexpr att::Handle kStart = 0x0002; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x02, 0x00, // start handle: 0x0002 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x01, 0x00, // chrc 1 handle (handle is before the range) |
| 0x00, // chrc 1 properties |
| 0x02, 0x00, // chrc 1 value handle |
| 0xAD, 0xDE // chrc 1 uuid: 0xDEAD |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| EXPECT_TRUE(chrcs.empty()); |
| } |
| |
| // Expects the discovery procedure to end with an error if a batch contains |
| // results that are from beyond the requested range. |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryResultsBeyondRange) { |
| constexpr att::Handle kStart = 0x0002; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x02, 0x00, // start handle: 0x0002 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x06, 0x00, // chrc 1 handle (handle is beyond the range) |
| 0x00, // chrc 1 properties |
| 0x07, 0x00, // chrc 1 value handle |
| 0xAD, 0xDE // chrc 1 uuid: 0xDEAD |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| EXPECT_TRUE(chrcs.empty()); |
| } |
| |
| // Expects the characteristic value handle to immediately follow the |
| // declaration as specified in Vol 3, Part G, 3.3. |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryValueNotContiguous) { |
| constexpr att::Handle kStart = 0x0002; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x02, 0x00, // start handle: 0x0002 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x02, 0x00, // chrc 1 handle |
| 0x00, // chrc 1 properties |
| 0x04, 0x00, // chrc 1 value handle (not immediate) |
| 0xAD, 0xDE // chrc 1 uuid: 0xDEAD |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| EXPECT_TRUE(chrcs.empty()); |
| } |
| |
| TEST_F(GATT_ClientTest, CharacteristicDiscoveryHandlesNotIncreasing) { |
| constexpr att::Handle kStart = 0x0002; |
| constexpr att::Handle kEnd = 0x0005; |
| |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x08, // opcode: read by type request |
| 0x02, 0x00, // start handle: 0x0002 |
| 0x05, 0x00, // end handle: 0x0005 |
| 0x03, 0x28 // type: characteristic decl. (0x2803) |
| ); |
| |
| att::Status status; |
| auto res_cb = [&status](att::Status val) { status = val; }; |
| |
| std::vector<CharacteristicData> chrcs; |
| auto chrc_cb = [&chrcs](const CharacteristicData& chrc) { |
| chrcs.push_back(chrc); |
| }; |
| |
| // Initiate the request on the message loop since Expect() below blocks. |
| async::PostTask(dispatcher(), [&, this] { |
| client()->DiscoverCharacteristics(kStart, kEnd, chrc_cb, res_cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x09, // opcode: read by type response |
| 0x07, // data length: 7 (16-bit UUIDs) |
| 0x02, 0x00, // chrc 1 handle |
| 0x00, // chrc 1 properties |
| 0x03, 0x00, // chrc 1 value handle |
| 0xAD, 0xDE, // chrc 1 uuid: 0xDEAD |
| 0x02, 0x00, // chrc 1 handle (repeated) |
| 0x00, // chrc 1 properties |
| 0x03, 0x00, // chrc 1 value handle |
| 0xEF, 0xBE // chrc 1 uuid: 0xBEEF |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| |
| // The first characteristic should be reported. |
| EXPECT_EQ(1u, chrcs.size()); |
| } |
| |
| // Equal handles should result should not short-circuit and result in a request. |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryHandlesEqual) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0001; |
| |
| att::Status status(HostError::kFailed); // Initialize as error |
| SendDiscoverDescriptors(&status, NopDescCallback, kStart, kEnd); |
| EXPECT_TRUE(ExpectFindInformation(kStart, kEnd)); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryResponseTooShort) { |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| // Respond back with a malformed payload. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x05)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryMalformedDataLength) { |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x03 // format (must be 1 or 2) |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryMalformedAttrDataList16) { |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive( |
| CreateStaticByteBuffer(0x05, // opcode: find information response |
| 0x01, // format: 16-bit. Data length must be 4 |
| 1, 2, 3, 4, 5)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryMalformedAttrDataList128) { |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x02, // format: 128-bit. Data length must be 18 |
| 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryEmptyDataList) { |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01 // format: 16-bit. |
| // data list empty |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryAttributeNotFound) { |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x01, 0x00, // handle: 0x0001 |
| 0x0A // error: Attribute Not Found |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryError) { |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x04, // request: find information |
| 0x01, 0x00, // handle: 0x0001 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscovery16BitResultsSingleRequest) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0003; |
| |
| std::vector<DescriptorData> descrs; |
| auto desc_cb = [&descrs](const DescriptorData& desc) { |
| descrs.push_back(desc); |
| }; |
| |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, std::move(desc_cb), kStart, kEnd); |
| ASSERT_TRUE(ExpectFindInformation(kStart, kEnd)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit. Data length must be 4 |
| 0x01, 0x00, // desc 1 handle |
| 0xEF, 0xBE, // desc 1 uuid |
| 0x02, 0x00, // desc 2 handle |
| 0xAD, 0xDE, // desc 2 uuid |
| 0x03, 0x00, // desc 3 handle |
| 0xFE, 0xFE // desc 3 uuid |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_EQ(3u, descrs.size()); |
| EXPECT_EQ(0x0001, descrs[0].handle); |
| EXPECT_EQ(0x0002, descrs[1].handle); |
| EXPECT_EQ(0x0003, descrs[2].handle); |
| EXPECT_EQ((uint16_t)0xBEEF, descrs[0].type); |
| EXPECT_EQ((uint16_t)0xDEAD, descrs[1].type); |
| EXPECT_EQ((uint16_t)0xFEFE, descrs[2].type); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscovery128BitResultsSingleRequest) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0002; |
| |
| std::vector<DescriptorData> descrs; |
| auto desc_cb = [&descrs](const DescriptorData& desc) { |
| descrs.push_back(desc); |
| }; |
| |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, std::move(desc_cb), kStart, kEnd); |
| ASSERT_TRUE(ExpectFindInformation(kStart, kEnd)); |
| |
| att()->set_mtu(512); |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x02, // format: 128-bit. Data length must be 18 |
| 0x01, 0x00, // desc 1 handle |
| 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, |
| 0xEF, 0xBE, 0x00, 0x00, // desc 1 uuid |
| 0x02, 0x00, // desc 2 handle |
| 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, |
| 0xAD, 0xDE, 0x00, 0x00 // desc 2 uuid |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_EQ(2u, descrs.size()); |
| EXPECT_EQ(0x0001, descrs[0].handle); |
| EXPECT_EQ(0x0002, descrs[1].handle); |
| EXPECT_EQ((uint16_t)0xBEEF, descrs[0].type); |
| EXPECT_EQ((uint16_t)0xDEAD, descrs[1].type); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryMultipleRequests) { |
| constexpr att::Handle kEnd = 0x0005; |
| constexpr att::Handle kStart1 = 0x0001; |
| constexpr att::Handle kStart2 = 0x0003; |
| constexpr att::Handle kStart3 = 0x0004; |
| |
| std::vector<DescriptorData> descrs; |
| auto desc_cb = [&descrs](const DescriptorData& desc) { |
| descrs.push_back(desc); |
| }; |
| |
| att::Status status(HostError::kFailed); |
| SendDiscoverDescriptors(&status, std::move(desc_cb), kStart1, kEnd); |
| |
| // Batch 1 |
| ASSERT_TRUE(ExpectFindInformation(kStart1, kEnd)); |
| return; |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit. Data length must be 4 |
| 0x01, 0x00, // desc 1 handle |
| 0xEF, 0xBE, // desc 1 uuid |
| 0x02, 0x00, // desc 2 handle |
| 0xAD, 0xDE // desc 2 uuid |
| )); |
| RunLoopUntilIdle(); |
| |
| // Batch 2 |
| ASSERT_TRUE(ExpectFindInformation(kStart2, kEnd)); |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x02, // format: 128-bit. Data length must be 18 |
| 0x03, 0x00, // desc 3 handle |
| 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, |
| 0xFE, 0xFE, 0x00, 0x00 // desc 3 uuid |
| )); |
| RunLoopUntilIdle(); |
| |
| // Batch 3 |
| ASSERT_TRUE(ExpectFindInformation(kStart3, kEnd)); |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x08, // request: read by type |
| 0x04, 0x00, // handle: kStart3 (0x0004) |
| 0x0A // error: Attribute Not Found |
| )); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_EQ(3u, descrs.size()); |
| EXPECT_EQ(0x0001, descrs[0].handle); |
| EXPECT_EQ(0x0002, descrs[1].handle); |
| EXPECT_EQ(0x0003, descrs[2].handle); |
| EXPECT_EQ((uint16_t)0xBEEF, descrs[0].type); |
| EXPECT_EQ((uint16_t)0xDEAD, descrs[1].type); |
| EXPECT_EQ((uint16_t)0xFEFE, descrs[2].type); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryResultsBeforeRange) { |
| constexpr att::Handle kStart = 0x0002; |
| |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback, kStart); |
| ASSERT_TRUE(ExpectFindInformation(kStart)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit. |
| 0x01, 0x00, // handle is before the range |
| 0xEF, 0xBE // uuid |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryResultsBeyondRange) { |
| constexpr att::Handle kStart = 0x0001; |
| constexpr att::Handle kEnd = 0x0002; |
| |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback, kStart, kEnd); |
| ASSERT_TRUE(ExpectFindInformation(kStart, kEnd)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit. |
| 0x03, 0x00, // handle is beyond the range |
| 0xEF, 0xBE // uuid |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, DescriptorDiscoveryHandlesNotIncreasing) { |
| att::Status status; |
| SendDiscoverDescriptors(&status, NopDescCallback); |
| ASSERT_TRUE(ExpectFindInformation()); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x05, // opcode: find information response |
| 0x01, // format: 16-bit. |
| 0x01, 0x00, // handle: 0x0001 |
| 0xEF, 0xBE, // uuid |
| 0x01, 0x00, // handle: 0x0001 (repeats) |
| 0xAD, 0xDE // uuid |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteRequestMalformedResponse) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| |
| att::Status status; |
| auto cb = [&status](att::Status cb_status) { status = cb_status; }; |
| |
| // Initiate the request in a message loop task, as Expect() below blocks on |
| // the message loop. |
| async::PostTask(dispatcher(), |
| [&, this] { client()->WriteRequest(kHandle, kValue, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| ASSERT_FALSE(fake_chan()->link_error()); |
| |
| // Respond back with a malformed PDU. This should result in a link error. |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x013, // opcode: write response |
| 0 // One byte payload. The write request has no parameters. |
| )); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| EXPECT_TRUE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteRequestExceedsMtu) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| constexpr att::Handle kHandle = 0x0001; |
| constexpr size_t kMtu = 5; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| ASSERT_EQ(kMtu + 1, kExpectedRequest.size()); |
| |
| att()->set_mtu(kMtu); |
| |
| att::Status status; |
| auto cb = [&status](att::Status cb_status) { status = cb_status; }; |
| |
| client()->WriteRequest(kHandle, kValue, cb); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kPacketMalformed, status.error()); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteRequestError) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| |
| att::Status status; |
| auto cb = [&status](att::Status cb_status) { status = cb_status; }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&, this] { client()->WriteRequest(kHandle, kValue, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x12, // request: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteRequestSuccess) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x12, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| |
| att::Status status; |
| auto cb = [&status](att::Status cb_status) { status = cb_status; }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&, this] { client()->WriteRequest(kHandle, kValue, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer(0x13 // opcode: write response |
| )); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteWithoutResponseExceedsMtu) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| constexpr att::Handle kHandle = 0x0001; |
| constexpr size_t kMtu = 5; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x52, // opcode: write command |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| ASSERT_EQ(kMtu + 1, kExpectedRequest.size()); |
| |
| att()->set_mtu(kMtu); |
| |
| bool called = false; |
| fake_chan()->SetSendCallback([&](auto) { called = true; }, dispatcher()); |
| |
| client()->WriteWithoutResponse(kHandle, kValue); |
| RunLoopUntilIdle(); |
| |
| // No packet should be sent. |
| EXPECT_FALSE(called); |
| } |
| |
| TEST_F(GATT_ClientTest, WriteWithoutResponseSuccess) { |
| const auto kValue = CreateStaticByteBuffer('f', 'o', 'o'); |
| const auto kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x52, // opcode: write request |
| 0x01, 0x00, // handle: 0x0001 |
| 'f', 'o', 'o' // value: "foo" |
| ); |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&] { client()->WriteWithoutResponse(kHandle, kValue); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadRequestEmptyResponse) { |
| constexpr att::Handle kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0A, // opcode: read request |
| 0x01, 0x00 // handle: 0x0001 |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto cb = [&status](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| |
| // We expect an empty value |
| EXPECT_EQ(0u, value.size()); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&, this] { client()->ReadRequest(kHandle, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // ATT Read Response with no payload. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x0B)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadRequestSuccess) { |
| constexpr att::Handle kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0A, // opcode: read request |
| 0x01, 0x00 // handle: 0x0001 |
| ); |
| |
| const auto kExpectedResponse = |
| CreateStaticByteBuffer(0x0B, // opcode: read response |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto cb = [&](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kExpectedResponse.view(1), value)); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&, this] { client()->ReadRequest(kHandle, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(kExpectedResponse); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadRequestError) { |
| constexpr att::Handle kHandle = 0x0001; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0A, // opcode: read request |
| 0x01, 0x00 // handle: 0x0001 |
| ); |
| |
| att::Status status; |
| auto cb = [&](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| |
| // Value should be empty due to the error. |
| EXPECT_EQ(0u, value.size()); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), |
| [&, this] { client()->ReadRequest(kHandle, cb); }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0A, // request: read request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x06 // error: Request Not Supported |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadBlobRequestEmptyResponse) { |
| constexpr att::Handle kHandle = 1; |
| constexpr uint16_t kOffset = 5; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0C, // opcode: read blob request |
| 0x01, 0x00, // handle: 1 |
| 0x05, 0x00 // offset: 5 |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto cb = [&](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| |
| // We expect an empty value |
| EXPECT_EQ(0u, value.size()); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), [&, this] { |
| client()->ReadBlobRequest(kHandle, kOffset, cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| // ATT Read Blob Response with no payload. |
| fake_chan()->Receive(CreateStaticByteBuffer(0x0D)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadBlobRequestSuccess) { |
| constexpr att::Handle kHandle = 1; |
| constexpr uint16_t kOffset = 5; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0C, // opcode: read blob request |
| 0x01, 0x00, // handle: 1 |
| 0x05, 0x00 // offset: 5 |
| ); |
| const auto kExpectedResponse = |
| CreateStaticByteBuffer(0x0D, // opcode: read blob response |
| 't', 'e', 's', 't' // value: "test" |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto cb = [&](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| |
| // We expect an empty value |
| EXPECT_TRUE(ContainersEqual(kExpectedResponse.view(1), value)); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), [&, this] { |
| client()->ReadBlobRequest(kHandle, kOffset, cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| fake_chan()->Receive(kExpectedResponse); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, ReadBlobRequestError) { |
| constexpr att::Handle kHandle = 1; |
| constexpr uint16_t kOffset = 5; |
| const auto kExpectedRequest = |
| CreateStaticByteBuffer(0x0C, // opcode: read blob request |
| 0x01, 0x00, // handle: 1 |
| 0x05, 0x00 // offset: 5 |
| ); |
| |
| att::Status status(HostError::kFailed); |
| auto cb = [&](att::Status cb_status, const ByteBuffer& value) { |
| status = cb_status; |
| |
| // We expect an empty value |
| EXPECT_EQ(0u, value.size()); |
| }; |
| |
| // Initiate the request in a loop task, as Expect() below blocks |
| async::PostTask(dispatcher(), [&, this] { |
| client()->ReadBlobRequest(kHandle, kOffset, cb); |
| }); |
| |
| ASSERT_TRUE(Expect(kExpectedRequest)); |
| |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x01, // opcode: error response |
| 0x0C, // request: read blob request |
| 0x01, 0x00, // handle: 0x0001 |
| 0x07 // error: Invalid Offset |
| )); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kInvalidOffset, status.protocol_error()); |
| EXPECT_FALSE(fake_chan()->link_error()); |
| } |
| |
| TEST_F(GATT_ClientTest, EmptyNotification) { |
| constexpr att::Handle kHandle = 1; |
| |
| bool called = false; |
| client()->SetNotificationHandler( |
| [&](bool ind, auto handle, const auto& value) { |
| called = true; |
| EXPECT_FALSE(ind); |
| EXPECT_EQ(kHandle, handle); |
| EXPECT_EQ(0u, value.size()); |
| }); |
| |
| // clang-format off |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x1B, // opcode: notification |
| 0x01, 0x00 // handle: 1 |
| )); |
| // clang-format on |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(called); |
| } |
| |
| TEST_F(GATT_ClientTest, Notification) { |
| constexpr att::Handle kHandle = 1; |
| |
| bool called = false; |
| client()->SetNotificationHandler( |
| [&](bool ind, auto handle, const auto& value) { |
| called = true; |
| EXPECT_FALSE(ind); |
| EXPECT_EQ(kHandle, handle); |
| EXPECT_EQ("test", value.AsString()); |
| }); |
| |
| // clang-format off |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x1B, // opcode: notification |
| 0x01, 0x00, // handle: 1 |
| 't', 'e', 's', 't' // value: "test" |
| )); |
| // clang-format on |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(called); |
| } |
| |
| TEST_F(GATT_ClientTest, Indication) { |
| constexpr att::Handle kHandle = 1; |
| |
| bool called = false; |
| client()->SetNotificationHandler( |
| [&](bool ind, auto handle, const auto& value) { |
| called = true; |
| EXPECT_TRUE(ind); |
| EXPECT_EQ(kHandle, handle); |
| EXPECT_EQ("test", value.AsString()); |
| }); |
| |
| // clang-format off |
| fake_chan()->Receive(CreateStaticByteBuffer( |
| 0x1D, // opcode: indication |
| 0x01, 0x00, // handle: 1 |
| 't', 'e', 's', 't' // value: "test" |
| )); |
| // clang-format on |
| |
| // Wait until a confirmation gets sent. |
| EXPECT_TRUE(Expect(CreateStaticByteBuffer(0x1E))); |
| EXPECT_TRUE(called); |
| } |
| |
| } // namespace |
| } // namespace gatt |
| } // namespace bt |